Sources
index.js
const hyptiotes = require("hyptiotes");
const configuration = require("./configuration");
const App = require("./app");
hyptiotes.setElementInitializer(configuration.elementInitializer);
hyptiotes.setItemHandlers(configuration.itemHandlers);
hyptiotes.setAttributeHandlers(configuration.attributeHandlers);
hyptiotes.setElementFinalizer(configuration.elementFinalizer);
const domTree = hyptiotes.castWeb(App);
document.getElementById("root").appendChild(domTree);
configuration.js
const hyptiotes = require("hyptiotes");
module.exports = {
elementInitializer: hyptiotes.DEFAULT_ELEMENT_INITIALIZER,
itemHandlers: [
hyptiotes.PLUGINS.functionToHookInserts,
...hyptiotes.DEFAULT_ITEM_HANDLERS,
],
attributeHandlers: hyptiotes.DEFAULT_ATTRIBUTE_HANDLERS,
elementFinalizer: hyptiotes.DEFAULT_ELEMENT_FINALIZER,
};
app.js
const TodoStore = require("../TodoStore");
module.exports = [
":div",
{ id: "main-content" },
[":h1", "Hyptiotes To-Do"],
TodoList,
AddTodo,
];
function TodoList({ recast, onCleanup }) {
onCleanup(TodoStore.subscribe(recast));
return [
":ul",
...TodoStore.get().map((todo) => {
return [":li", todo];
}),
];
}
function AddTodo({ recast }) {
let inputValue = "";
return [
":div",
[
":input",
{
onchange: (e) => {
inputValue = e.target.value;
},
},
],
[
":button",
{
onclick: () => {
TodoStore.add(inputValue);
recast();
},
},
"Add",
],
];
}
Hook Function Plugin (functionToHookInserts.js)
module.exports = {
test: (item) => typeof item === "function",
handler: ({ item, parent, hyptiotes }) => {
contentGenerator(item, parent, hyptiotes.castWeb);
},
};
function contentGenerator(generate, parent, cast) {
let onUpdate;
let cleanupCbs = [];
let refCb = null;
const hooks = {
recast: (v) => onUpdate(v),
onRefChange: (cb) => {
if (refCb !== null) console.warn("Called onRefChange twice, only last is called");
refCb = (element) => {
cb(element);
refCb = null;
};
},
onCleanup: (fn) => cleanupCbs.push(fn),
};
let element = null;
function renderer() {
onUpdate = lockedUpdate;
cleanupCbs.forEach((fn) => fn());
cleanupCbs = [];
const model = generate(hooks);
const updatedElement = cast(model);
const next = element && element.nextSibling;
if (element) parent.removeChild(element);
parent.insertBefore(updatedElement, next);
if (refCb) refCb(updatedElement);
onUpdate = renderer;
element = updatedElement;
}
function lockedUpdate() {
throw new Error("Called update inside render (update is cyclical)");
}
renderer();
}
Full Configuration
{
"elementInitializer": (tag) => document.createElement(tag.slice(1)),
"itemHandlers": [
{
"test": (item) => typeof item === "function",
"handler": ({ item, parent, hyptiotes }) => {
contentGenerator(item, parent, hyptiotes.castWeb);
}
},
{
"test": (item) => item === null || item === undefined,
"handler": () => {}
},
{
"test": (item) => item instanceof HTMLElement,
"handler": ({item, parent}) => parent.appendChild(item)
},
{
"test": item => typeof item === "string",
"handler": ({ item, parent }) => {
const textNode = document.createTextNode(item);
parent.appendChild(textNode);
}
},
{
"test": item => Array.isArray(item) && typeof item[0] === "string" && item[0][0] === ":",
"handler": ({item, parent, hyptiotes}) => {
const nested = hyptiotes.castWeb(item);
if (nested) parent.appendChild(nested);
}
},
{
"test": item => Array.isArray(item) && (typeof item[0] !== "string" || item[0][0] !== ":"),
"handler": ({item, parent, hyptiotes}) => {
item.forEach(i => {
const child = typeof i === "string" ? document.createTextNode(item) : hyptiotes.castWeb(i);
parent.appendChild(child);
});
}
},
{
"test": (item) => typeof item === "function",
"handler": ({ item, parent, index, hyptiotes }) => {
hyptiotes.mapItem(item({ parent, hyptiotes }), parent, index);
}
}
],
"attributeHandlers": [
{
"test": key => key === "style",
"handler": ({value, parent}) => {
parent.setAttribute('style', stringifyStyleObject(value));
}
},
{
"test": (_, value) => typeof value === "function",
"handler": ({key, value, parent}) => {
parent[key] = value;
}
},
{
"test": () => true,
"handler": ({key, value, parent}) => {
parent.setAttribute(key, value);
}
}
],
"elementFinalizer": (x) => x
}