| /* | |
| * 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); | |
| } |