Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dealing with eventing #4

Closed
marcoscaceres opened this issue Mar 6, 2017 · 26 comments
Closed

Dealing with eventing #4

marcoscaceres opened this issue Mar 6, 2017 · 26 comments
Labels

Comments

@marcoscaceres
Copy link
Contributor

marcoscaceres commented Mar 6, 2017

If I'm understanding correctly (from the forms example), the eventing depends on stringification ... that means that it's super fragile and things like closures are, obviously, forgotten. It seems like the only way to slightly hack around this is to put functions on the Window object so:

const foo = 123;
const evt = {
  onchange(ev) {
    const onNo = "" + foo; // Aside: double quotes breaks everything
}}

Doesn't really work, because it gets transpiled to:

function onchange(ev){
  const onNo = "" + foo; // double quotes break everything
}

Because it's now just declaring a function, and not calling it.

      <select onchange="${evt.onchange}">
        ${makeOptions(someData)}
      </select>

Would become:

      <select onchange="
function onchange(ev){
    const onNo = "" + foo; // double quotes break everything, foo reference error!
}">
         <option>
      </select>

Maybe there is no way around this?

@marcoscaceres
Copy link
Contributor Author

(updated above)

@WebReflection
Copy link
Owner

If I'm understanding correctly (from the forms example), the eventing depends on stringification

you didn't understand correctly, events are set as functions, no stringification at all

@WebReflection
Copy link
Owner

WebReflection commented Mar 6, 2017

To expand, this:

<select onchange="${evt.onchange}">
  ${makeOptions(someData)}
</select>

will do select[attribute.name] = evt.onchange; where attribute.name is "onchange"

The quotes are explained in the README
https://github.com/WebReflection/hyperHTML#caveats

Are needed for parsing, but all attributes starting win on will be threated differently.

More details in here:
https://github.com/WebReflection/hyperHTML/blob/master/DEEPDIVE.md

P.S. very sad you thought I'm that stupid 😝

@WebReflection
Copy link
Owner

Added a F.A.Q. about it, so others won't think stringification is what happens.
https://github.com/WebReflection/hyperHTML#faqs

@marcoscaceres
Copy link
Contributor Author

hmmm... must be doing something wrong.

@WebReflection
Copy link
Owner

I'll test the quotes in other attributes but I think that's automatically sanitized when you attribute.value = anyString. Worth a test in the list of tests though.

You can start simple with other tests too like tick or article
https://github.com/WebReflection/hyperHTML/tree/master/test

@marcoscaceres
Copy link
Contributor Author

Ok, will try again, because I ended up with this... which was kinda sad:

  const evt = {
    onchange: "return updateTotals(event);",
  };

But if you are saying that actual function should work there, then will try to figure out why it's not. In dev tools, it looked it was copying the function text. In case you are interested:

https://gist.github.com/marcoscaceres/f896268dd86186fde47a71c7c3c6e674

@marcoscaceres
Copy link
Contributor Author

If I try changing to a function it's definitely converting it to a string:

screenshot 2017-03-06 19 31 21

@marcoscaceres
Copy link
Contributor Author

I get the same with:

  const evt = {
    onchange(ev){
      updateTotals(ev);
    },
  };

Which just appears as (transpiled) text:

screenshot 2017-03-06 19 38 40

@marcoscaceres
Copy link
Contributor Author

ah, I see what I did wrong now... it's because toTableRow() doesn't call into render, which looks for the "on" in setAttribute().

You should probably make a note about that (i.e., how to combine multiple template strings).

@WebReflection
Copy link
Owner

how to combine multiple template strings

you don't combine multiple template strings because hyperHTML returns the rendered context, not even a string.

It's different from anything you've used before so I rather would like to understand, with a little example, what are you trying to do.

Remember, each template needs a node to be rendered in, even a document fragment would do

@marcoscaceres
Copy link
Contributor Author

So, what I'm trying to do is pretty simple. I have a table of shopping items, and when I change the number of items, I want the the total to reflect that. Here is literally the table (which is populated nicely):

screenshot 2017-03-06 20 56 08

So when I pick the number of items in the select, I want to detect the "onchange". I was hoping to do that on the select elements.

@marcoscaceres
Copy link
Contributor Author

This is the data:

const inventoryData = [
  {
    colors: "Bright yellow",
    img: "images/faux.jpg",
    price: "100",
    ref: "3046/023/322",
    sizes: ["m"],
    title: "Faux Leather Jacket",
  },
  {
    colors: "Bright yellow",
    img: "images/double.jpg",
    price: "90",
    ref: "6254/022/382",
    sizes: ["s", "m", "l"],
    title: "Double Faced Coat",
  },
];

Now, I was hoping to be able to bind the event on the "select" elements, which are dynamically created. But, I guess I can add the event listener directly on the form that wraps the whole table.

@WebReflection
Copy link
Owner

I don't have time now but will check later. It looks like you should be able to do that anyway.

See if this answer somehow helps you solve your issue too:
https://gist.github.com/FremyCompany/4677aca44c04a86bbf2b8185d0a76717#gistcomment-2018915

Also bear in mind this project was born yesterday, there are surely things to simplify or improve.

@marcoscaceres
Copy link
Contributor Author

marcoscaceres commented Mar 6, 2017

Also bear in mind this project was born yesterday, there are surely things to simplify or improve.

I know, which is why I'm excited about it. Please don't think I'm criticizing it in any way - honestly just trying to get my little thing working and hopefully will be helpful to you in seeing how people use it.

@WebReflection
Copy link
Owner

absolutely!

@WebReflection
Copy link
Owner

I don't know if this is what you were looking for, but it works for me.

  var render = hyperHTML.wire();

  var events = {
    onchange: function (e) {
      console.log(items.find(function (item) {
        return e.target.value === item.ref;
      }));
    }
  };

  var items = [
    {
      colors: "Bright yellow",
      img: "images/faux.jpg",
      price: "100",
      ref: "3046/023/322",
      sizes: ["m"],
      title: "Faux Leather Jacket",
    },
    {
      colors: "Bright yellow",
      img: "images/double.jpg",
      price: "90",
      ref: "6254/022/382",
      sizes: ["s", "m", "l"],
      title: "Double Faced Coat",
    },
  ];

  var select = update(
    render,
    items,
    events
  );

  document.body.appendChild(select);

  function update(render, items, events) {
    return render`
      <select onchange="${events.onchange}">${items.map(item => `
        <option value="${item.ref}">
          ${item.title}
        </option>
      `)}</select>
    `;
  }

@marcoscaceres
Copy link
Contributor Author

Will try it. What I'm looking for is the ability to "nest" renderers, so that I can generate trs, which themselves contain render'ed things. From the example you put on Twitter, I think wire() solves it.

It would be helpful to include such an example (if you haven't added it already), as it's going to be fairly common.

@WebReflection
Copy link
Owner

Beter example than this for tables it's hard to imagine:
https://github.com/WebReflection/hyperHTML/blob/master/test/dbmonster.html#L32-L61

@WebReflection
Copy link
Owner

Please note not only TRs are dynamic, but TDs per TRs are dynamic too and concatenated through wires.

😉

@marcoscaceres
Copy link
Contributor Author

The example is good (in that it proves that it can be done!), but it suffers from being complicated by mixing multiple template strings and calls to render (it makes the code fragile and potentially hard to maintain - in that it makes it hard to tell where one render starts and another ends, etc).

Now, it might just be a matter of stylistic preference, but I would expect:

const render = (arr, i) => arr[i] || (arr[i] = hyperHTML.wire());
const renderTABLE = hyperHTML.bind(document.body);
function updateTable(dbs) {
    renderTABLE`
    <table class="table table-striped latest-data">
      <tbody>${dbs.map(((db, i) => toTableRow(render, db, i)}</tbody>
    </table>`;
}

function toTableRow(render, data){
   return render`<tr><td>data.whatever</td> <td>${makeOtherThing(data.otherThing)}</tr>`;
}

function makeOtherThing(otherThing){
   return ???`<input onclick="${otherThing.onclick}">`
}

And if makeOtherThing can also somehow "render" (to, again, for example, bind a function), then you are onto a winner.

@WebReflection
Copy link
Owner

WebReflection commented Mar 7, 2017

OK, it took a while, but I've finally understood your issue.
It's easy to forget about hyperHTML magic and use template strings as if hyperHTML was native (I wish).

So, there are two solutions to your problem, the one that comes with unbeatable performance, and the one good enough for 90% of use cases.

Performance

Everything you render through hyperHTML should be cached because it might be recycled.
This is the code.

const render = (arr, i) => arr[i] || (arr[i] = hyperHTML.wire());
const TABLE = hyperHTML.bind(document.body);
const TRs = [];
const THINGs = [];

function updateTable(dbs) {
    TABLE`
    <table class="table table-striped latest-data">
      <tbody>${dbs.map(toTableRow)}</tbody>
    </table>`;
}

function toTableRow(data, i){
  return render(TRs, i)`
  <tr>
    <td>
      ${data.whatever}
    </td>
    <td>${makeOtherThing(data.otherThing, i)}</td>
  </tr>`;
}

function makeOtherThing(otherThing, i){
  return render(THINGs, i)`
  <a
    onclick="${otherThing.onclick}"
    href="${otherThing.link}"
  >
    ${otherThing.name}
  </a>`;
}

function onclick(e) {
  e.preventDefault();
  alert(this.textContent);
}

updateTable([{
  whatever: 'Whatever 1',
  otherThing: {
    link: '#whatever-1',
    name: 'This is another thing 1',
    onclick: onclick
  }
}, {
  whatever: 'Whatever 2',
  otherThing: {
    link: '#whatever-2',
    name: 'This is another thing 2',
    onclick: onclick
  }
}]);

Good enough, who cares

If you want hyperHTML magic in the wild without caching anything else, which is OK for things that don't blow up to thousands of items updated per seconds, you just drop previous THINGs reference, and when it comes to a one-off node, you simply use the .wire()

function makeOtherThing(otherThing, i){
  return hyperHTML.wire()`
  <a
    onclick="${otherThing.onclick}"
    href="${otherThing.link}"
  >
    ${otherThing.name}
  </a>`;
}

That's it.

@WebReflection
Copy link
Owner

Actually, for the sake of documentation, this is the "let it go" version:

const updateTable = dbs => hyperHTML.bind(document.body)`
    <table class="table table-striped latest-data">
      <tbody>${dbs.map(toTableRow)}</tbody>
    </table>
`;

const toTableRow = data => hyperHTML.wire()`
  <tr>
    <td>
      ${data.whatever}
    </td>
    <td>${makeOtherThing(data.otherThing)}</td>
  </tr>
`;

const makeOtherThing = otherThing => hyperHTML.wire()`
  <a
    onclick="${otherThing.onclick}"
    href="${otherThing.link}"
  >
    ${otherThing.name}
  </a>
`;

function onclick(e) {
  e.preventDefault();
  alert(this.textContent);
}

updateTable([{
  whatever: 'Whatever 1',
  otherThing: {
    link: '#whatever-1',
    name: 'This is another thing 1',
    onclick: onclick
  }
}, {
  whatever: 'Whatever 2',
  otherThing: {
    link: '#whatever-2',
    name: 'This is another thing 2',
    onclick: onclick
  }
}]);

@WebReflection
Copy link
Owner

FYI this discussion inspired me an improvement to the .wire() helper.
#6

@marcoscaceres
Copy link
Contributor Author

Awesome! Will try to plug in the above into the thing I'm working on. I'll send you a link to it once I have it working so you can have a look. There might be more common patterns that can come out of it.

@WebReflection
Copy link
Owner

FYI, hyperHTML.wire(obj) is now a thing. Whenever you have a list of known objects, i.e. not a JSON result, just weakly wire them up, like in this cars demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants