Skip to content

Instantly share code, notes, and snippets.

@thomaswilburn
Last active December 15, 2023 01:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomaswilburn/d395bfafe673cc992117681671eb4e30 to your computer and use it in GitHub Desktop.
Save thomaswilburn/d395bfafe673cc992117681671eb4e30 to your computer and use it in GitHub Desktop.
Fill In The Blanks
<!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