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?
(updated above)
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
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 馃槤
Added a F.A.Q. about it, so others won't think stringification is what happens.
https://github.com/WebReflection/hyperHTML#faqs
hmmm... must be doing something wrong.
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
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
If I try changing to a function it's definitely converting it to a string:

I get the same with:
const evt = {
onchange(ev){
updateTotals(ev);
},
};
Which just appears as (transpiled) text:

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).
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
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):

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.
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.
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.
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.
absolutely!
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>
`;
}
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.
Beter example than this for tables it's hard to imagine:
https://github.com/WebReflection/hyperHTML/blob/master/test/dbmonster.html#L32-L61
Please note not only TRs are dynamic, but TDs per TRs are dynamic too and concatenated through wires.
馃槈
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.
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.
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
}
}]);
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.
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
}
}]);
FYI this discussion inspired me an improvement to the .wire() helper.
https://github.com/WebReflection/hyperHTML/issues/6
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.
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