Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active May 14, 2017 11:14
Show Gist options
  • Save WebReflection/113b136c3a0e49b2ac96c6d9f55615e6 to your computer and use it in GitHub Desktop.
Save WebReflection/113b136c3a0e49b2ac96c6d9f55615e6 to your computer and use it in GitHub Desktop.
"use strict";
class Foo {
constructor(root) {
this.render = hyperHTML.bind(root);
this.state = Introspected({}, $ctrl => this.update($ctrl));
this.state.items = [
{ id: 1, text: "foo" },
{ id: 2, text: "bar" },
{ id: 3, text: "baz" }
];
}
update($ctrl) {
this.render`
<ul>${$ctrl.items.map(item => hyperHTML.wire(item)`
<li> ${item.id} / ${item.text} </li>
`)}</ul>
`;
}
}
const foo = new Foo(document.body);
setTimeout(() => {
foo.state.items.push({ id: 4, text: "minions" });
}, 3000);
@albertosantini
Copy link

albertosantini commented May 13, 2017

@albertosantini
Copy link

Thanks @WebReflection for improving the demo.

I add these explanations to the code.

  • We may postpone the initialization of the Introspected object like
        const data = {
          items: [
              { id: 1, text: "foo" },
              { id: 2, text: "bar" },
              { id: 3, text: "baz" }
          ]
        }
        this.state = Introspected(data, $ctrl => this.update($ctrl));

But then you should need to call manually the update method for the first rendering like

const foo = new Foo(document.body);
foo.update(); // and how to pass the state? foo.update(foo.state); ? 

If the Introspected object contains the state of the component from the beginning, there is no need to add any update call.

  • We may define the callback of the Introspected object like
this.state = Introspected({}, this.update.bind(this));

We need to bind this otherwise the call this.render, in the update method, would not work correctly because this would not be the class instance.

Using an explicit callback, $ctrl => this.update($ctrl), for the Introspected object, avoids the need to bind to this.

  • We may use hyperHTML.wire()... and the example would work fine yet.

But the html fragment, defined by the wire, would be completely updated when an item is added.
The initial fragment contains <ul><li>...</li><li>...</li><li>...</li></ul>.
When the fourth item is added, all the relevant dom would be updated like <ul><li>...</li><li>...</li><li>...</li><li>...</li></ul>.

If we use hyperHTML.wire(item), the dom would be updated only for the corresponding item: only the fourth item, the <li> element, would be added to <ul>. If you inspect the dom, via devtools, you can see that behaviour.

@albertosantini
Copy link

Borrowing the select code from hyperHTML issue #4, this is the component example with events in the state:

https://plnkr.co/edit/x4ECHgYQLUyJOEFbTxLK?p=preview

@WebReflection
Copy link
Author

the previous comment was OK, all right, but the latest example assumes the options returned as map are part of hyperHTML, while those are just regulare tempalte string because there's no wire or bound tag associated.

The problem there is that you cannot use a single object in two different wires (at least not yet) but a map returning strings literal is just an innerHTML operation so value and text are no t sanitized at all, it's an anti pattern without using an hyperHTML tag.

If I could understand the use case or what that example is trying to show/achieve, I could propose a better solution fully based on hyperHTML and not a hybrid template like that one is.

@albertosantini
Copy link

The primary aim of the second example was showing an Introspect object with properties and functions.

The rest is an hyperHTML affair.

I started without returning strings literal (borrowing the code from your comment in that issue), but suddenly the value of the select didn't change (always "foo" value).

            <select onchange="${$ctrl.events.onchange}">${$ctrl.items.map(item => `
              <option value="${item.id}">
                ${item.text}
              </option>
            `)}</select>

https://plnkr.co/edit/oZYrWnkcupjngHpa0bep?p=preview

Then I used the suboptimal workaround returning strings literal just to create a working example from the Introspect point of view. :)

@WebReflection
Copy link
Author

issue is here:

//                       THIS is a regular template literal
${$ctrl.items.map(item => `
  <option value="${item.id}">
    ${item.text}
  </option>
`)}

You should use a wire there to have a fully correct behavior/expectation, but in that demo you cannot use a wire twice because objects are mapped only once per template.

This means you need a helper to map a single item to multiple different wires, like I've done in here:
https://plnkr.co/edit/ozBlLEeqD4MekU4FaTya?p=preview

This also gave me a hint on how to improve, enhance hyperHTML: thank you!

@WebReflection
Copy link
Author

I have updated hyperHTML with a nice feature such multi wire per same object and by IDs.

You can see a live example of your use-case in this page.

I have also added the ability to set object with handleEvent methods as events to simplify, and reuse, as many callbacks per component as possible.

  class Foo {
  
    constructor(root) {

      this.render = hyperHTML.bind(root);

      this.state = Introspected(
        {
          items: [
            { id: 1, text: 'foo' },
            { id: 2, text: 'bar' },
            { id: 3, text: 'baz' }
          ]
        },
        state => this.update(state)
      );

      // any change to the introspected state will update
      this.state.selectedItem = this.state.items[0];
    }

    // any object with an handleEvent method can be used for any event
    // like it is already for DOM Level 3 via addEventListener
    handleEvent(e) {
      switch (e.type) {
        case 'change':
          this.state.selectedItem = this.state.items.find(
            item => +e.target.value === item.id
          );
          break;
      }
    }

    update(state) {
      this.render`
        <select onchange="${this}">${
          state.items.map(item => hyperHTML.wire(item, ':option')`
          <option value="${item.id}">
            ${item.text}
          </option>
        `)}</select>

        <p>Selected: ${state.selectedItem.text}</p>

        <ul>${state.items.map(item => hyperHTML.wire(item, ':li')`
            <li> ${item.id} / ${item.text} </li>
        `)}</ul>
      `;
    }

  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment