Source code
Revision control
Copy as Markdown
Other Tools
/*
* Template code
*
* A template is just a javascript structure. An element is represented as:
*
* [tag_name, {attr_name:attr_value}, child1, child2]
*
* the children can either be strings (which act like text nodes), other templates or
* functions (see below)
*
* A text node is represented as
*
* ["{text}", value]
*
* String values have a simple substitution syntax; ${foo} represents a variable foo.
*
* It is possible to embed logic in templates by using a function in a place where a
* node would usually go. The function must either return part of a template or null.
*
* In cases where a set of nodes are required as output rather than a single node
* with children it is possible to just use a list
* [node1, node2, node3]
*
* Usage:
*
* render(template, substitutions) - take a template and an object mapping
* variable names to parameters and return either a DOM node or a list of DOM nodes
*
* substitute(template, substitutions) - take a template and variable mapping object,
* make the variable substitutions and return the substituted template
*
*/
function is_single_node(template)
{
return typeof template[0] === "string";
}
function substitute(template, substitutions)
{
if (typeof template === "function") {
var replacement = template(substitutions);
if (replacement)
{
var rv = substitute(replacement, substitutions);
return rv;
}
else
{
return null;
}
}
else if (is_single_node(template))
{
return substitute_single(template, substitutions);
}
else
{
return filter(map(template, function(x) {
return substitute(x, substitutions);
}), function(x) {return x !== null;});
}
}
expose(substitute, "template.substitute");
function substitute_single(template, substitutions)
{
var substitution_re = /\${([^ }]*)}/g;
function do_substitution(input) {
var components = input.split(substitution_re);
var rv = [];
for (var i=0; i<components.length; i+=2)
{
rv.push(components[i]);
if (components[i+1])
{
rv.push(substitutions[components[i+1]]);
}
}
return rv;
}
var rv = [];
rv.push(do_substitution(String(template[0])).join(""));
if (template[0] === "{text}") {
substitute_children(template.slice(1), rv);
} else {
substitute_attrs(template[1], rv);
substitute_children(template.slice(2), rv);
}
function substitute_attrs(attrs, rv)
{
rv[1] = {};
for (name in template[1])
{
if (attrs.hasOwnProperty(name))
{
var new_name = do_substitution(name).join("");
var new_value = do_substitution(attrs[name]).join("");
rv[1][new_name] = new_value;
};
}
}
function substitute_children(children, rv)
{
for (var i=0; i<children.length; i++)
{
if (children[i] instanceof Object) {
var replacement = substitute(children[i], substitutions);
if (replacement !== null)
{
if (is_single_node(replacement))
{
rv.push(replacement);
}
else
{
extend(rv, replacement);
}
}
}
else
{
extend(rv, do_substitution(String(children[i])));
}
}
return rv;
}
return rv;
}
function make_dom_single(template)
{
if (template[0] === "{text}")
{
var element = document.createTextNode("");
for (var i=1; i<template.length; i++)
{
element.data += template[i];
}
}
else
{
var element = document.createElement(template[0]);
for (name in template[1]) {
if (template[1].hasOwnProperty(name))
{
element.setAttribute(name, template[1][name]);
}
}
for (var i=2; i<template.length; i++)
{
if (template[i] instanceof Object)
{
var sub_element = make_dom(template[i]);
element.appendChild(sub_element);
}
else
{
var text_node = document.createTextNode(template[i]);
element.appendChild(text_node);
}
}
}
return element;
}
function make_dom(template, substitutions)
{
if (is_single_node(template))
{
return make_dom_single(template);
}
else
{
return map(template, function(x) {
return make_dom_single(x);
});
}
}
function render(template, substitutions)
{
return make_dom(substitute(template, substitutions));
}
expose(render, "template.render");
function expose(object, name)
{
var components = name.split(".");
var target = window;
for (var i=0; i<components.length - 1; i++)
{
if (!(components[i] in target))
{
target[components[i]] = {};
}
target = target[components[i]];
}
target[components[components.length - 1]] = object;
}
function extend(array, items)
{
Array.prototype.push.apply(array, items);
}