Last active
July 3, 2020 06:13
-
-
Save jkriss/0bc3bdcd41908366be3fdbf79bc599b3 to your computer and use it in GitHub Desktop.
Template stamping
This file contains hidden or 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> | |
<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> |
This file contains hidden or 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
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; | |
} |
This file contains hidden or 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
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