112 lines
3.6 KiB
Agda
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
|
|
}
|
|
`
|