Created
February 22, 2017 13:52
-
-
Save fragsalat/a62da9ba9ce5996ca05164672846ecfb to your computer and use it in GitHub Desktop.
Small template renderer which acts like aurelia's template engine
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 INTERPOLATION_START = '\\${'; | |
const INTERPOLATION_END = '}'; | |
const INTERPOLATION_EXPRESSION = new RegExp(`(${INTERPOLATION_START}(.*?)${INTERPOLATION_END})`, 'g'); | |
class TemplateEngine { | |
constructor(template) { | |
this.baseTemplate = template; | |
} | |
render(data) { | |
if (this.baseTemplate) { | |
let clone = document.importNode(this.baseTemplate, true); | |
this._processNode(clone, data || {}); | |
return clone; | |
} | |
return null; | |
} | |
_processNode(node, context) { | |
// Attributes and children can change during the loop therefore we generate a new array using map fn | |
let attributeNames = Array.from(node.attributes).map(attr => attr.name); | |
let textNodes = Array.from(node.childNodes).filter(node => node.nodeType === Node.TEXT_NODE); | |
let children = Array.from(node.childNodes).filter(node => node.nodeType === Node.ELEMENT_NODE); | |
for (let name of attributeNames) { | |
// Abort of node processing can happen for an repeat placeholder or element has show/hide.if attribute | |
if (!this._processAttribute(node, name, context)) { | |
return; | |
} | |
} | |
// Process interpolations in text nodes | |
for (let text of textNodes) { | |
if (INTERPOLATION_EXPRESSION.test(text.textContent)) { | |
text.textContent = this._interpolate(text.textContent, context); | |
} | |
} | |
// Process child nodes | |
for (let child of children) { | |
this._processNode(child, context); | |
} | |
} | |
_processAttribute(node, attribute, context) { | |
let expression = node.getAttribute(attribute); | |
node.removeAttribute(attribute); | |
// let [attrName, command] = ... throws exception when stepping through it with debugger | |
let split = attribute.split('.'); | |
let attrName = split[0]; | |
let command = split[1]; | |
switch (command) { | |
case 'trigger': | |
node.addEventListener(attrName, event => { | |
let eventContext = Object.assign({}, context, {event}); | |
this._evaluate(expression, eventContext); | |
}, true); | |
break; | |
case 'if': | |
let value = this._evaluate(expression, context); | |
// Return false to abort further processing of current node | |
if (attrName === 'hide' ? value === 'true' : value === 'false') { | |
node.parentNode.removeChild(node); | |
return false; | |
} | |
break; | |
case 'bind': | |
node.setAttribute(attrName, this._evaluate(expression, context)); | |
break; | |
case 'for': | |
this._processRepeatAttribute(node, context[expression], context); | |
// Abort further node processing because the placeholder node got removed removed | |
return false; | |
} | |
return true; | |
} | |
_processRepeatAttribute(node, items, context) { | |
let i = 0; | |
for (let key in items) { | |
if (!items.hasOwnProperty(key)) { | |
continue; | |
} | |
// Create a clone with children (also with text nodes which can hold interpolations) | |
let clone = document.importNode(node, true); | |
let itemContext = {index: i++, key, item: items[key], parent: context}; | |
// Process attributes and children | |
this._processNode(clone, itemContext); | |
// Insert processed copy of placeholder | |
node.parentNode.insertBefore(clone, node); | |
} | |
// Remove placeholder from dom tree | |
node.parentNode.removeChild(node); | |
} | |
_interpolate(string, context) { | |
return string.replace(INTERPOLATION_EXPRESSION, (match, interpolation, expression) => { | |
return this._evaluate(expression, context); | |
}); | |
} | |
_evaluate(expression, context) { | |
let values = Object.keys(context).map(key => context[key]); | |
return (new Function(Object.keys(context), 'return `${' + expression + '}`'))(...values); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment