Closed albertosantini closed 7 years ago
<select onchange="${events}">${
state.items.map(item => hyperHTML.wire(item, ":option")`
<option
value="${item.id}"
selected="${state.selectedItem.id === item.id}"
> ${item.text} </option>
`)}</select>
TL;DR checked, disabled, etc ... all booleans and/or special attributes present in an element prototype can be assigned like any other attribute as shown in this documentation example: https://github.com/WebReflection/hyperHTML/blob/master/GETTING_STARTED.md#how-to-define-hyperhtml-templates
Ops... I read that document a few times, I supposed to read that doc one time more. :)
You are very patience. Thanks. You may add that advice in the GitHub issue Template.
As sign of peace, I share the last version of my proof-of-concept, showing wrist
interfacing an Introspected
object (toggling the visibility of a list, style inline for demo purpose), sharing Introspected
object with the services, nested components, and other.
https://plnkr.co/edit/j21P6fMGGwQhaVHNqhaK?p=preview
h.js
"use strict";
// util.js
class Util {
static query(selector) {
return document.querySelector(selector) ||
console.error(selector, "not found");
}
}
// foo.template.js - like an external html file
class FooTemplate {
static update(render, state, events) {
render`
<select id="selectItem" onchange="${events}">${
state.items.map(item => hyperHTML.wire(item, ":option")`
<option value="${item.id}"
selected="${state.selectedItem.id === item.id}">
${item.text}
</option>
`)}</select>
<p>Selected: ${state.selectedItem.text}</p>
<button id="addItem" type="button"
onclick="${events}">Add minions to the list
</button>
<button id="alertSomething" type="button"
onclick="${events.bind({ text: "dummy text" })}">Alert something
</button>
<input id="toggleList" type="checkbox"
checked="${state.toggleList}">Toggle the list</input>
<ul style="${state.toggleList
? "display: block;" : "display: none;"}"
>${state.items.map(item => hyperHTML.wire(item, ":li")`
<li> ${item.id} / ${item.text} </li>
`)}</ul>
`;
}
}
// foo.service.js
class FooService {
constructor(items) {
this.items = items;
}
getItems() {
return this.items;
}
refresh() {
setTimeout(() => { // simulating an async update of the model
const lastItem = this.items.slice(-1)[0];
this.items.push({ id: lastItem.id + 1, text: "minions" });
}, 2000);
}
}
// foo.controller.js
class FooController {
constructor(render, template) {
const events = e => this.handleEvent(e);
const items = [
{ id: 1, text: "foo" },
{ id: 2, text: "bar" },
{ id: 3, text: "baz" }
];
this.state = Introspected({
items,
selectedItem: items[1],
toggleList: true
}, state => template.update(render, state, events));
this.fooService = new FooService(this.state.items);
wrist.watch(Util.query("#toggleList"), "checked",
this.onToggleList.bind(this.state));
}
refresh() {
this.fooService.refresh();
}
handleEvent(e) {
const type = e.type;
const id = e.target.id || console.warn(e.target, "target without id");
const method = `on${id[0].toUpperCase()}${id.slice(1)}` +
`${type[0].toUpperCase()}${type.slice(1)}`;
return method in this ? this[method](e)
: console.warn(e.type, "not implemented");
}
onSelectItemChange(e) {
this.state.selectedItem = this.state.items.find(
item => +e.target.value === item.id
);
}
onAddItemClick() {
this.refresh();
}
onAlertSomethingClick(e, text) {
console.warn("to be implemented");
}
onToggleList(prop, prev, curr) {
this.toggleList = curr;
}
}
// foo.component.js
class FooComponent {
constructor() {
const render = hyperHTML.bind(Util.query("foo"));
this.fooController = new FooController(render, FooTemplate);
}
refresh() {
this.fooController.refresh();
}
}
// root.template.js
class RootTemplate {
static update(render) {
render`<foo></foo>`;
}
}
// root.controller.js
class RootController {
constructor(render, template) {
template.update(render);
}
}
// root.component.js
class RootComponent {
constructor() {
const render = hyperHTML.bind(Util.query("root"));
this.rootController = new RootController(render, RootTemplate);
}
}
// root.module.js
new RootComponent();
// foo.module.js
// the order is important, otherwise the tag "foo" doesn't exist
const fooComponent = new FooComponent();
// whatever.js - maybe using a singleton for the service underlying used
setTimeout(() => { // simulating an async update without user interaction
fooComponent.refresh();
}, 3000);
h.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Getting Started with HyperHTML</title>
</head>
<body>
<root></root>
<script src="https://unpkg.com/hyperhtml"></script>
<script src="https://unpkg.com/introspected"></script>
<script src="https://unpkg.com/wrist"></script>
<script src="h.js"></script>
</body>
</html>
two quick hints.
<button id="alertSomething" type="button"
Watch out, events is an arrow function.
One does not simply bind an arrow function
this does not do what you think it does
onclick="${events.bind({ text: "dummy text" })}">Alert something
... better ...
onclick="${(e) => events(e, "dummy text")}">Alert something
</button>
and
<input id="toggleList" type="checkbox"
onchange="${(e) => 'you do not need wrist here'}"
checked="${state.toggleList}">Toggle the list</input>
wrist
was born before hyperHTML
and it can be used for some part of the page that does not use hyperHTML
but it makes little sense to use wrist for elements created via hyperHTML
.
On top of that, don't be shy with events
, it can be an object of events to simplify some call.
events = {onchange: e => this.onInputChange(e)}
and similar
Awesome. Reading minds here. :)
I was just filling an issue how to pass a payload to handleEvent.
Thanks for the tips.
Just modified the code, removing also wrist, and it works perfectly. Very elegant.
And you answered me before I wrote the question (about that events.bind
) in a new issue. :)
In the template:
<button id="alertSomething" type="button"
onclick="${e => events(e, "ok")}">Alert something
</button>
<input id="toggleList" type="checkbox"
onchange="${e => state.toggleList = e.target.checked}"
checked="${state.toggleList}">Toggle the list</input>
In the controller:
const events = (e, payload) => this.handleEvent(e, payload);
...
handleEvent(e, payload) {
const type = e.type;
const id = e.target.id || console.warn(e.target, "target without id");
const method = `on${id[0].toUpperCase()}${id.slice(1)}` +
`${type[0].toUpperCase()}${type.slice(1)}`;
return method in this ? this[method](e, payload)
: console.warn(e.type, "not implemented");
}
Correctly this doesn't work, getting
Uncaught TypeError: updates[(i - 1)] is not a function
:Also I successfully used a conditional statement in the literal templates, wrapped in a function, but it is ugly.
Any magic hint?
P.S.: auto label this issue as
help wanted
. :)