WebReflection / hyperHTML-Element

An extensible class to define hyperHTML based Custom Elements.
ISC License
202 stars 25 forks source link

nest custom element #6

Closed zwz closed 7 years ago

zwz commented 7 years ago

I modified a little (just on the Myself class)on the source code of the test example https://webreflection.github.io/hyperHTML-Element/test/

class MySelf extends HyperHTMLElement { static get observedAttributes() { return ['name', 'age']; } created() { this.render(); } attributeChangedCallback() { this.render(); } changed(e){ if(e.target) this.age = e.target.value; this.render(); } render() { return this.html<p>Hi, my name is ${this.name} and I am ${this.age}<p><my-input value="${this.age}" oninput="${this.changed}"></my-input> ; } }

When I change the value of the nested "my-input" element, the content of "my-self" does not change.

WebReflection commented 7 years ago

Few issues there.

  1. if you render on attribute changes attributeChangedCallback() { this.render(); }, and you are observing attributes ['name', 'age'], you don't need to call the render inside the onchange
  2. if you set a callback directly, the context is not what you expect. oninput="${this.changed}" will have the input itself as a context.

To solve problem 2, you need to understand how events work.

node.addEventListener('input', obj.method); will not invoke obj.method with obj as context, it will invoke it with the node as context.

Accordingly, this.age = ... does not point to the instance and the attribute you are observing, it will just set an expando age property to that input.

You have few options here:

  1. the old-style inefficient bind inside the created method. this.changed = this.changed.bind(this)
  2. the extremely slow and inefficient runtime bind each render via oninput="${this.changed.bind(this)}"
  3. the HyperHTMLElement way

What's that? It's based on handleEvent, a native behavior defined by specs since year 2000.

Basically, instead of passing O(n) bound methods, you just pass the instance that inherits a handleEvent method which will be invoked with these info (see the example)

screenshot from 2017-08-18 17-19-57

With HyperHTMLElement everything is simplified in a way that you just define a method called exactly like the event type you are listening to, and it will be invoked with the right context, without needing to bind anything.

To do that, you need to pass the instance as listener, instead of its method.

class MySelf extends HyperHTMLElement {
  static get observedAttributes() { return ['name', 'age']; }
  created() { this.render(); }
  attributeChangedCallback() { this.render(); }
  oninput(e){
    if(e.target) this.age = e.target.value;
  }
  render() {
    return this.html`
    <p>Hi, my name is ${this.name} and I am ${this.age}<p>
    <my-input value=${this.age} oninput=${this}></my-input>`;
  }
}

For any other use case, when you want to use the instance in many places and even if the event is the same you want to call something else, you can use the data-call attribute as shown in the following example:

screenshot from 2017-08-18 23-35-41

Hope I've explained what was wrong, and the way to solve it.

zwz commented 7 years ago

Very clear! Thanks