Files
newt/src/Web/Spruce.newt

112 lines
3.6 KiB
Agda

module Web.Spruce
-- Spruce is not Elm
-- TODO better story for import..
-- also, we'll probably want to expose something with subscriptions / dispatch / ...
import Prelude
ptype Element
pfunc getElementById : String Element := `(id) => document.getElementById(id)`
-- Make this align with spruce.ts
data Attr msg = SAttr String String | MAttr String msg | VAttr String (String msg)
data VNode msg = TNode String | ENode String (List $ Attr msg) (List $ VNode msg)
text : msg. String VNode msg
text = TNode
tag : msg. String List (Attr msg) List (VNode msg) VNode msg
tag = ENode
tag_ : msg. String List (VNode msg) VNode msg
tag_ tag es = ENode tag Nil es
pfunc runApp : msg model. Element model (update : msg model model) (view : model VNode msg) Unit := `
(_msg, _model, node, init, update, view) => {
function replace(parent, node, child) {
if (node) {
parent.insertBefore(child, node);
parent.removeChild(node);
} else {
parent.appendChild(child);
}
return child;
}
// patch node, possibly returning a new node
function patch(parentNode, node, vnode) {
if (vnode.tag == 0) {
if (node && node.nodeType == 3) {
node.nodeValue = vnode.h1;
return node;
}
return replace(parentNode, node, document.createTextNode(vnode.h1));
}
let el;
if (node instanceof Element && node.tagName.toLowerCase() == vnode.h1) {
el = node;
} else {
el = document.createElement(vnode.h1);
}
// update node here
let has = {};
for (let attrs = vnode.h2; attrs.tag == 1; attrs = attrs.h2) {
let attr = attrs.h1;
if (attr.tag == 0) {
let key = attr.h1;
has[key] = true;
if (key in el)
el[key] = attr.h2;
else
el.setAttribute(key, attr.h2);
} else {
let key = attr.h1.slice(2).toLowerCase();
has[key] = true;
let events = el.events || (el.events = {});
if (!events[key])
el.addEventListener(key, listener);
events[key] = attr;
}
}
for (let i = 0; i < el.attributes.length;) {
let attr = el.attributes[i];
if (!has[attr.name]) {
el.removeAttribute(attr.name);
} else {
i++;
}
}
if (el.events) {
for (let key of Object.keys(el.events)) {
if (!has[key]) delete el.events[key];
}
}
let i = 0;
for (let kids = vnode.h3; kids.tag == 1; kids = kids.h2) {
patch(el, el.childNodes[i++], kids.h1);
}
while (el.childNodes[i]) el.removeChild(el.childNodes[i]);
return node == el ? node : replace(parentNode, node, el);
}
let model = init;
let vdom = view(model);
let listener = (ev) => {
console.log('listener', ev, ev.target);
let target = ev.target;
for (;;) {
if (!target) return;
if (target.events?.[ev.type]) break;
target = target.parentElement;
}
const attr = target.events[ev.type];
let action = attr.tag == 2 ? attr.h2(ev.target.value) : attr.h2;
model = update(action)(model);
vdom = view(model);
node = patch(node.parentNode, node, vdom);
};
node = patch(node.parentNode, node, vdom);
return 0
}
`