#2779 Elem.fromNative() - where do I get a native obj from?

SlimerDude Fri 6 Dec 2019

I was very excited to find Elem.fromNative() but I'm struggling to get it to work...

I want to run Fantom code to alter an existing / already rendered webpage...

// <input id="email" type="text">

input := Win.cur.doc.elemById("email")
textf := Elem.fromNative(input, TextField#)

echo(textf.typeof)  // --> domkit::TextField

field := textf as TextField
field.val = "NEW VALUE"
field.style->backgroundColor = "red"
field.style->color = "green"
field.style.addClass("newCssClass")

fromNative() does return an instance of TextField but nothing I set on it changes the element on the screen - so I think I'm passing in the wrong native object.

How do I query the HTML page for a native object that I may pass to Elem.fromNative()?


dom::Elem.fromNative() JS source code:

fan.dom.ElemPeer.fromNative = function(obj, type)
{
  return fan.dom.ElemPeer.wrap(obj, type.make());
}

/*
 * Native only method to wrap an existing DOM node.  If this node
 * has already been wrapped by an Elem instance, return the
 * existing instance.
 */
fan.dom.ElemPeer.wrap = function(elem, fanElem)
{
  if (elem == null) throw fan.sys.ArgErr.make("elem is null")

  if (elem._fanElem != undefined)
    return elem._fanElem;

  if (fanElem && !(fanElem instanceof fan.dom.Elem))
    throw fan.sys.ArgErr.make("Type does not subclass Elem: " + fanElem);

  var x = fanElem || fan.dom.Elem.make();
  x.peer.elem = elem;
  elem._fanElem = x;
  return x;
}

andy Sat 7 Dec 2019

All Fantom API's always return an instance of dom::Elem already.

When you need to use fromNative is when you are bridging with JavaScript:

<button onclick="fan.myPod.MyClass.foo(this)">Click Me</button>

static Void foo(Obj target)
{
  elem := Elem.fromNative(target)
  ...
}

Here fromNative converts a "raw" DOM node into the dom::Elem instance.

SlimerDude Sat 7 Dec 2019

Hi Andy,

Thanks for the clarification - I've never really mixed Fantom and Javascript like that before.

So... I guess what I'm asking for then, is a method like this:

input := Win.cur.doc.elemById("email", TextField#)

So I can query the dom and bring back an arbitrary subclass of Elem (if it's not been mapped already).

With Doc.elemById being defined as:

native Elem? elemById(Str id, Type type := Elem)

Looking at the JS source, elemById() could be updated pretty easily to take the optional Type parameter:

fan.dom.DocPeer.prototype.elemById = function(self, id, type)
{
  var elem = this.doc.getElementById(id);
  if (elem == null) return null;
  return fan.dom.ElemPeer.wrap(elem, type.make());  // <-- Only this line changes!
}

Although type.make() would need to be moved inside ElemPeer.wrap for performance reasons.

What do you think? Something like the above would really help integrate Fantom in to existing webpages.

SlimerDude Thu 12 Dec 2019

Hi, I've just noticed the @NoDoc Doc.querySelectorAllType(...) method which appears to be just what I was asking for:

// <input id="email" type="text">

text := doc.querySelectorAllType("#email", TextField#).first

But initial trials have had sporadic success; while I can change properties like val I don't seem to be able to change the styling.

Mmm... it maybe easier for now to write one's own wrapper classes around Elem that use composition over inheritance.

andy Thu 12 Dec 2019

That doesn't quite work the way you probably want it to :)

DomKit is not always a 1:1 match to the raw HTML. So there is no official support for basically parsing HTML into DomKit. Which is I think is what you're after?

SlimerDude Fri 13 Dec 2019

Kindof, yes. I want to query different parts of HTML and manipulate it through domkit. While I realise it wouldn't be possible to utilise all of domkit in this way, it would have been useful to have access to the simple Form Inputs.

Using TextField as an example, this is the approach I've taken...

TextFields can still be created on the fly, ala domkit, but instances are cached on the Elem itself so any future queries on the HTML dom return the same objects.

using dom

@Js class TextField {

    Elem elem { private set }
    
    ** Value of text field.
    Str val {
        get { elem->value }
        set { elem->value = it }
    }

    private new _make(Elem elem) {
        this.elem = elem
        ...
    }

    ** The classic *domkit* ctor.    
    static new make() {
        fromElem(Elem("input") {
            it["type"] = "text"
        })
    }

    ** Wraps the queried Elem in a TextField.
    static new fromSelector(Str selector) {
        elem := Win.cur.doc.querySelector(selector)
        if (elem == null) throw Err("Could not find TextField: ${selector}")
        return fromElem(elem)
    }

    ** Caches the TextField instance on the wrapped Elem.
    static new fromElem(Elem elem) {
        if (elem.prop(TextField#.qname) == null)
            elem.setProp(TextField#.qname, TextField._make(elem))
        return elem.prop(TextField#.qname)
    }
    
    ...
    ...
    ...
}

andy Fri 13 Dec 2019

I've been (very slowly) working on a "Fantom on Rails" framework -- and I know I am going to run up against this kind of stuff -- will be later next year (hopefully...) -- but I'll give some thought to how it might work.

Login or Signup to reply.