Last active
December 15, 2023 01:07
-
-
Save thomaswilburn/d395bfafe673cc992117681671eb4e30 to your computer and use it in GitHub Desktop.
Fill In The Blanks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<style> | |
.card { | |
display: grid; | |
grid-template-columns: 1fr 2fr; | |
& img { | |
max-height: 200px; | |
} | |
} | |
</style> | |
<template id="source"> | |
<div class="card"> | |
<div class="mug"> | |
<img :src="mugshot" :ref="portrait"> | |
</div> | |
<div class="bio"> | |
<ul> | |
<li> Name: <!-- :name --> | |
<li> Title: <!-- :title --> | |
</ul> | |
<button :ref="remove" @click="remove">Remove card</button> | |
</div> | |
</div> | |
</template> | |
<script type="module"> | |
var pathCache = new WeakMap(); | |
class TextPin { | |
#value = null; | |
constructor(node) { | |
this.node = new Text(); | |
} | |
set value(v) { | |
if (v == this.#value) return; | |
this.node.nodeValue = this.#value = v; | |
} | |
} | |
class AttributePin { | |
#value = null; | |
constructor(element, name) { | |
this.element = element; | |
this.name = name; | |
} | |
set value(v) { | |
if (v == this.#value) return; | |
this.#value = v; | |
if (typeof v == "number" || typeof v == "string") { | |
this.element.setAttribute(this.name, v); | |
} else { | |
this.element.toggleAttribute(this.name, v); | |
} | |
} | |
} | |
function getPath(object, path) { | |
if (typeof path == "string") { | |
path = path.split("."); | |
} | |
} | |
function reinstantiate(template) { | |
var fragment = template.content.cloneNode(true); | |
var elements = {}; | |
var pins = {}; | |
var paths = pathCache.get(template); | |
for (var path of paths) { | |
var node = fragment; | |
for (var index of path.indices) { | |
node = node.childNodes[index]; | |
} | |
switch (path.type) { | |
case "text": | |
var pin = new TextPin(); | |
node.parentNode.replaceChild(pin.node, node); | |
pins[path.key] = pin; | |
break; | |
case "attribute": | |
var pin = new AttributePin(node, path.name); | |
pins[path.key] = pin; | |
break; | |
case "element": | |
elements[path.key] = node; | |
break; | |
} | |
} | |
var blanks = new Proxy(pins, { | |
set(target, key, value) { | |
pins[key].value = value; | |
return true; | |
} | |
}); | |
return { fragment, elements, blanks }; | |
} | |
function instantiate(template) { | |
if (pathCache.has(template)) { | |
return reinstantiate(template); | |
} | |
var fragment = document.createDocumentFragment(); | |
var elements = {}; | |
var pins = {}; | |
var paths = []; | |
var walk = function(node, parent, indices = []) { | |
var copy = node.cloneNode(); | |
switch (node.nodeType) { | |
case Node.COMMENT_NODE: | |
var comment = node.nodeValue.trim(); | |
if (comment[0] != ":") break; | |
var key = comment.slice(1); | |
var pin = new TextPin(); | |
copy = pin.node; | |
pins[key] = pin; | |
paths.push({ | |
indices, | |
key, | |
type: "text", | |
}); | |
break; | |
case Node.ELEMENT_NODE: | |
for (var attribute of node.attributes) { | |
if (attribute.name[0] != ":") continue; | |
var name = attribute.name.slice(1); | |
var key = attribute.value; | |
if (name == "ref") { | |
elements[key] = copy; | |
paths.push({ | |
indices, | |
key, | |
type: "element" | |
}); | |
} else { | |
var pin = new AttributePin(copy, name); | |
pins[key] = pin; | |
paths.push({ | |
indices, | |
key, | |
name, | |
type: "attribute" | |
}) | |
} | |
} | |
} | |
parent.append(copy); | |
var child = node.firstChild; | |
var index = 0; | |
while (child) { | |
walk(child, copy, [...indices, index]); | |
child = child.nextSibling; | |
index++; | |
} | |
} | |
for (var i = 0; i < template.content.childNodes.length; i++) { | |
walk(template.content.childNodes[i], fragment, [i]); | |
} | |
var blanks = new Proxy(pins, { | |
set(target, key, value) { | |
pins[key].value = value; | |
return true; | |
} | |
}); | |
pathCache.set(template, paths); | |
return { fragment, elements, blanks }; | |
} | |
var renderCache = new WeakMap(); | |
function render(template, target, data) { | |
var view = renderCache.get(target); | |
if (!view) { | |
var { fragment, blanks, elements} = instantiate(template); | |
target.append(fragment); | |
view = { template, blanks, elements }; | |
renderCache.set(target, view); | |
} | |
Object.assign(view.blanks, data); | |
} | |
var people = [ | |
{ name: "Thomas Wilburn", title: "Senior Data Editor, Chalkbeat", mugshot: "http://thomaswilburn.net/portrait.jpg" }, | |
{ name: "Wallace Wilburn", title: "Dog" } | |
]; | |
document.body.append(...people.map(bio => { | |
var div = document.createElement("div"); | |
div.className = "rendered"; | |
render(window.source, div, bio); | |
return div; | |
})); | |
var last = document.querySelector(".rendered:last-child"); | |
render(window.source, last, { | |
name: "Also Thomas Wilburn", | |
title: "Clone", | |
mugshot: people[0].mugshot | |
}); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment