Skip to content

Instantly share code, notes, and snippets.

@jkriss
Last active July 3, 2020 06:13
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 jkriss/0bc3bdcd41908366be3fdbf79bc599b3 to your computer and use it in GitHub Desktop.
Save jkriss/0bc3bdcd41908366be3fdbf79bc599b3 to your computer and use it in GitHub Desktop.
Template stamping
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>template stamping</title>
</head>
<body>
<div id=one></div>
<div id=two></div>
<template id=test>
<h1 onclick={clickHandler}>section: {name}</h1>
<p if={author}>by { author.name }</p>
<div if={content} html={content}>this will be replaced</div>
<div if={content}>but it's escaped in regular text children: {content}</div>
<ul each={list}>
<li>item {#}: {this}</li>
</ul>
<p>
<a href={href}>{linkText}</a>
<a href=http://google.com>normal link</a>
</p>
</template>
<script type=module>
import stamp from './index.js'
const template = document.querySelector('#test')
const one = document.querySelector('#one')
const two = document.querySelector('#two')
function render(template, data, el) {
el.innerHTML = "";
el.appendChild(stamp(template, data));
}
render(template, {
clickHandler: (evt) => {
alert(`heeeey ${evt.target.innerText}`)
},
name: 'thing one',
content: '<b>bold</b> is allowed in html.',
linkText: 'hello!',
href: 'https://jklabs.net',
author: {
name: 'Jesse'
},
list: ['one', 'two', 'three']
}, one)
render(template, {
name: 'thing two',
linkText: 'hello again!',
href: 'https://timestreams.org',
author: false
}, two)
</script>
</body>
</html>
const slice = Array.prototype.slice;
function walk(nodes, cb) {
if (!("length" in nodes)) {
nodes = [nodes];
}
nodes = slice.call(nodes);
while (nodes.length) {
var node = nodes.shift(),
ret = cb(node);
if (ret) {
return ret;
}
if (node.childNodes && node.childNodes.length) {
nodes = slice.call(node.childNodes).concat(nodes);
}
}
}
const braceRegex = /{\s*(.*?)\s*}/g;
const braceRegexOne = /{\s*(.*?)\s*}/;
function getDot(obj, key) {
let result = obj
for (const property of key.split(".")) {
result = result ? result[property] : "";
}
return result
}
function getKey(templateStr) {
const m = templateStr.match(braceRegexOne)
return m && m[1]
}
function t(template, data) {
return template.replace(braceRegex, (_, key) => {
return String(getDot(data, key));
});
}
export default function stamp(template, data) {
const clone = template.content.cloneNode(true);
walk(clone, (node) => {
if (node.attributes) {
const toRemove = []
for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
const key = getKey(attr.value)
const val = key && getDot(data, key)
if (attr.name === 'if') {
if (!val) {
node.remove();
} else {
toRemove.push('if')
}
} else if (attr.name === 'each') {
toRemove.push('each')
if (val) {
// make a new template from the children,
// render for each item
const t = document.createElement('template')
for (let j=0; j<node.children.length; j++) {
t.content.appendChild(node.children[j])
}
node.innerHTML = ''
val.forEach((subVal, i) => {
const itemData = { '#': i, this: subVal }
node.appendChild(stamp(t, itemData))
})
} else {
node.remove()
}
} else if (attr.name === 'html') {
node.innerHTML = val
toRemove.push('html')
} else {
// special handling for event listeners
const m = attr.name.match(/^on(.*)/)
if (m) {
if (val) node.addEventListener(m[1], val)
toRemove.push(attr.name)
} else {
attr.value = val;
}
}
}
toRemove.map(node.removeAttribute.bind(node))
}
if (node instanceof Text) {
node.textContent = t(node.textContent, data);
}
});
return clone;
}
const e=Array.prototype.slice;function t(t,n){if(!("length"in t)){t=[t]}t=e.call(t);while(t.length){var i=t.shift(),o=n(i);if(o){return o}if(i.childNodes&&i.childNodes.length){t=e.call(i.childNodes).concat(t)}}}const n=/{\s*(.*?)\s*}/g;const i=/{\s*(.*?)\s*}/;function o(e,t){let n=e;for(const e of t.split(".")){n=n?n[e]:""}return n}function c(e){const t=e.match(i);return t&&t[1]}function s(e,t){return e.replace(n,(e,n)=>String(o(t,n)))}export default function l(e,n){const i=e.content.cloneNode(true);t(i,e=>{if(e.attributes){const t=[];for(let i=0;i<e.attributes.length;i++){const s=e.attributes[i];const r=c(s.value);const a=r&&o(n,r);if(s.name==="if"){if(!a){e.remove()}else{t.push("if")}}else if(s.name==="each"){t.push("each");if(a){const t=document.createElement("template");for(let n=0;n<e.children.length;n++){t.content.appendChild(e.children[n])}e.innerHTML="";a.forEach((n,i)=>{const o={"#":i,this:n};e.appendChild(l(t,o))})}else{e.remove()}}else if(s.name==="html"){e.innerHTML=a;t.push("html")}else{const n=s.name.match(/^on(.*)/);if(n){if(a)e.addEventListener(n[1],a);t.push(s.name)}else{s.value=a}}}t.map(e.removeAttribute.bind(e))}if(e instanceof Text){e.textContent=s(e.textContent,n)}});return i}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment