template.js (6214B)
1 /* 2 * Template code 3 * 4 * A template is just a javascript structure. An element is represented as: 5 * 6 * [tag_name, {attr_name:attr_value}, child1, child2] 7 * 8 * the children can either be strings (which act like text nodes), other templates or 9 * functions (see below) 10 * 11 * A text node is represented as 12 * 13 * ["{text}", value] 14 * 15 * String values have a simple substitution syntax; ${foo} represents a variable foo. 16 * 17 * It is possible to embed logic in templates by using a function in a place where a 18 * node would usually go. The function must either return part of a template or null. 19 * 20 * In cases where a set of nodes are required as output rather than a single node 21 * with children it is possible to just use a list 22 * [node1, node2, node3] 23 * 24 * Usage: 25 * 26 * render(template, substitutions) - take a template and an object mapping 27 * variable names to parameters and return either a DOM node or a list of DOM nodes 28 * 29 * substitute(template, substitutions) - take a template and variable mapping object, 30 * make the variable substitutions and return the substituted template 31 * 32 */ 33 34 function is_single_node(template) 35 { 36 return typeof template[0] === "string"; 37 } 38 39 function substitute(template, substitutions) 40 { 41 if (typeof template === "function") { 42 var replacement = template(substitutions); 43 if (replacement) 44 { 45 var rv = substitute(replacement, substitutions); 46 return rv; 47 } 48 else 49 { 50 return null; 51 } 52 } 53 else if (is_single_node(template)) 54 { 55 return substitute_single(template, substitutions); 56 } 57 else 58 { 59 return filter(map(template, function(x) { 60 return substitute(x, substitutions); 61 }), function(x) {return x !== null;}); 62 } 63 } 64 expose(substitute, "template.substitute"); 65 66 function substitute_single(template, substitutions) 67 { 68 var substitution_re = /\${([^ }]*)}/g; 69 70 function do_substitution(input) { 71 var components = input.split(substitution_re); 72 var rv = []; 73 for (var i=0; i<components.length; i+=2) 74 { 75 rv.push(components[i]); 76 if (components[i+1]) 77 { 78 rv.push(substitutions[components[i+1]]); 79 } 80 } 81 return rv; 82 } 83 84 var rv = []; 85 rv.push(do_substitution(String(template[0])).join("")); 86 87 if (template[0] === "{text}") { 88 substitute_children(template.slice(1), rv); 89 } else { 90 substitute_attrs(template[1], rv); 91 substitute_children(template.slice(2), rv); 92 } 93 94 function substitute_attrs(attrs, rv) 95 { 96 rv[1] = {}; 97 for (name in template[1]) 98 { 99 if (attrs.hasOwnProperty(name)) 100 { 101 var new_name = do_substitution(name).join(""); 102 var new_value = do_substitution(attrs[name]).join(""); 103 rv[1][new_name] = new_value; 104 }; 105 } 106 } 107 108 function substitute_children(children, rv) 109 { 110 for (var i=0; i<children.length; i++) 111 { 112 if (children[i] instanceof Object) { 113 var replacement = substitute(children[i], substitutions); 114 if (replacement !== null) 115 { 116 if (is_single_node(replacement)) 117 { 118 rv.push(replacement); 119 } 120 else 121 { 122 extend(rv, replacement); 123 } 124 } 125 } 126 else 127 { 128 extend(rv, do_substitution(String(children[i]))); 129 } 130 } 131 return rv; 132 } 133 134 return rv; 135 } 136 137 function make_dom_single(template) 138 { 139 if (template[0] === "{text}") 140 { 141 var element = document.createTextNode(""); 142 for (var i=1; i<template.length; i++) 143 { 144 element.data += template[i]; 145 } 146 } 147 else 148 { 149 var element = document.createElement(template[0]); 150 for (name in template[1]) { 151 if (template[1].hasOwnProperty(name)) 152 { 153 element.setAttribute(name, template[1][name]); 154 } 155 } 156 for (var i=2; i<template.length; i++) 157 { 158 if (template[i] instanceof Object) 159 { 160 var sub_element = make_dom(template[i]); 161 element.appendChild(sub_element); 162 } 163 else 164 { 165 var text_node = document.createTextNode(template[i]); 166 element.appendChild(text_node); 167 } 168 } 169 } 170 171 return element; 172 } 173 174 175 176 function make_dom(template, substitutions) 177 { 178 if (is_single_node(template)) 179 { 180 return make_dom_single(template); 181 } 182 else 183 { 184 return map(template, function(x) { 185 return make_dom_single(x); 186 }); 187 } 188 } 189 190 function render(template, substitutions) 191 { 192 return make_dom(substitute(template, substitutions)); 193 } 194 expose(render, "template.render"); 195 196 function expose(object, name) 197 { 198 var components = name.split("."); 199 var target = window; 200 for (var i=0; i<components.length - 1; i++) 201 { 202 if (!(components[i] in target)) 203 { 204 target[components[i]] = {}; 205 } 206 target = target[components[i]]; 207 } 208 target[components[components.length - 1]] = object; 209 } 210 211 function extend(array, items) 212 { 213 Array.prototype.push.apply(array, items); 214 }