Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(720)

Side by Side Diff: chrome/third_party/jstemplate/jstemplate_compiled.js

Issue 119384: Update JSTemplate to the latest version from Google Code (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 (function(){
1 /** 2 /**
2 * @fileoverview This file contains miscellaneous basic functionality. 3 * @fileoverview Miscellaneous constants and functions referenced in
3 * 4 * the main source files.
4 */ 5 */
5 6
6 /** 7 function log(msg) {}
7 * Creates a DOM element with the given tag name in the document of the
8 * owner element.
9 *
10 * @param {String} tagName The name of the tag to create.
11 * @param {Element} owner The intended owner (i.e., parent element) of
12 * the created element.
13 * @param {Point} opt_position The top-left corner of the created element.
14 * @param {Size} opt_size The size of the created element.
15 * @param {Boolean} opt_noAppend Do not append the new element to the owner.
16 * @return {Element} The newly created element node.
17 */
18 function createElement(tagName, owner, opt_position, opt_size, opt_noAppend) {
19 var element = ownerDocument(owner).createElement(tagName);
20 if (opt_position) {
21 setPosition(element, opt_position);
22 }
23 if (opt_size) {
24 setSize(element, opt_size);
25 }
26 if (owner && !opt_noAppend) {
27 appendChild(owner, element);
28 }
29 8
30 return element; 9 /** @const */ var STRING_empty = '';
31 }
32 10
33 /** 11 /** @const */ var CSS_display = 'display';
34 * Creates a text node with the given value. 12 /** @const */ var CSS_position = 'position';
35 *
36 * @param {String} value The text to place in the new node.
37 * @param {Element} owner The owner (i.e., parent element) of the new
38 * text node.
39 * @return {Text} The newly created text node.
40 */
41 function createTextNode(value, owner) {
42 var element = ownerDocument(owner).createTextNode(value);
43 if (owner) {
44 appendChild(owner, element);
45 }
46 return element;
47 }
48 13
49 /** 14 var TYPE_boolean = 'boolean';
50 * Returns the document owner of the given element. In particular, 15 var TYPE_number = 'number';
51 * returns window.document if node is null or the browser does not 16 var TYPE_object = 'object';
52 * support ownerDocument. 17 var TYPE_string = 'string';
53 * 18 var TYPE_function = 'function';
54 * @param {Node} node The node whose ownerDocument is required. 19 var TYPE_undefined = 'undefined';
55 * @returns {Document|Null} The owner document or null if unsupported.
56 */
57 function ownerDocument(node) {
58 return (node ? node.ownerDocument : null) || document;
59 }
60
61 /**
62 * Wrapper function to create CSS units (pixels) string
63 *
64 * @param {Number} numPixels Number of pixels, may be floating point.
65 * @returns {String} Corresponding CSS units string.
66 */
67 function px(numPixels) {
68 return round(numPixels) + "px";
69 }
70
71 /**
72 * Sets the left and top of the given element to the given point.
73 *
74 * @param {Element} element The dom element to manipulate.
75 * @param {Point} point The desired position.
76 */
77 function setPosition(element, point) {
78 var style = element.style;
79 style.position = "absolute";
80 style.left = px(point.x);
81 style.top = px(point.y);
82 }
83
84 /**
85 * Sets the width and height style attributes to the given size.
86 *
87 * @param {Element} element The dom element to manipulate.
88 * @param {Size} size The desired size.
89 */
90 function setSize(element, size) {
91 var style = element.style;
92 style.width = px(size.width);
93 style.height = px(size.height);
94 }
95
96 /**
97 * Sets display to none. Doing this as a function saves a few bytes for
98 * the 'style.display' property and the 'none' literal.
99 *
100 * @param {Element} node The dom element to manipulate.
101 */
102 function displayNone(node) {
103 node.style.display = 'none';
104 }
105
106 /**
107 * Sets display to default.
108 *
109 * @param {Element} node The dom element to manipulate.
110 */
111 function displayDefault(node) {
112 node.style.display = '';
113 }
114
115 /**
116 * Appends the given child to the given parent in the DOM
117 *
118 * @param {Element} parent The parent dom element.
119 * @param {Node} child The new child dom node.
120 */
121 function appendChild(parent, child) {
122 parent.appendChild(child);
123 }
124 20
125 21
126 /** 22 /**
127 * Wrapper for the eval() builtin function to evaluate expressions and 23 * Wrapper for the eval() builtin function to evaluate expressions and
128 * obtain their value. It wraps the expression in parentheses such 24 * obtain their value. It wraps the expression in parentheses such
129 * that object literals are really evaluated to objects. Without the 25 * that object literals are really evaluated to objects. Without the
130 * wrapping, they are evaluated as block, and create syntax 26 * wrapping, they are evaluated as block, and create syntax
131 * errors. Also protects against other syntax errors in the eval()ed 27 * errors. Also protects against other syntax errors in the eval()ed
132 * code and returns null if the eval throws an exception. 28 * code and returns null if the eval throws an exception.
133 * 29 *
134 * @param {String} expr 30 * @param {string} expr
135 * @return {Object|Null} 31 * @return {Object|null}
136 */ 32 */
137 function jsEval(expr) { 33 function jsEval(expr) {
138 try { 34 try {
139 return eval('[' + expr + '][0]'); 35 return eval('[' + expr + '][0]');
140 } catch (e) { 36 } catch (e) {
37 log('EVAL FAILED ' + expr + ': ' + e);
141 return null; 38 return null;
142 } 39 }
143 } 40 }
144 41
42 function jsLength(obj) {
43 return obj.length;
44 }
45
46 function assert(obj) {}
47
48 /**
49 * Copies all properties from second object to the first. Modifies to.
50 *
51 * @param {Object} to The target object.
52 * @param {Object} from The source object.
53 */
54 function copyProperties(to, from) {
55 for (var p in from) {
56 to[p] = from[p];
57 }
58 }
59
145 60
146 /** 61 /**
147 * Wrapper for the eval() builtin function to execute statements. This 62 * @param {Object|null|undefined} value The possible value to use.
148 * guards against exceptions thrown, but doesn't return a 63 * @param {Object} defaultValue The default if the value is not set.
149 * value. Still, mostly for testability, it returns a boolean to 64 * @return {Object} The value, if it is
150 * indicate whether execution was successful. NOTE: 65 * defined and not null; otherwise the default
151 * javascript's eval semantics is murky in that it confounds
152 * expression evaluation and statement execution into a single
153 * construct. Cf. jsEval().
154 *
155 * @param {String} stmt
156 * @return {Boolean}
157 */ 66 */
158 function jsExec(stmt) { 67 function getDefaultObject(value, defaultValue) {
159 try { 68 if (typeof value != TYPE_undefined && value != null) {
160 eval(stmt); 69 return /** @type Object */(value);
161 return true; 70 } else {
162 } catch (e) { 71 return defaultValue;
163 return false;
164 } 72 }
165 } 73 }
166 74
75 /**
76 * Detect if an object looks like an Array.
77 * Note that instanceof Array is not robust; for example an Array
78 * created in another iframe fails instanceof Array.
79 * @param {Object|null} value Object to interrogate
80 * @return {boolean} Is the object an array?
81 */
82 function isArray(value) {
83 return value != null &&
84 typeof value == TYPE_object &&
85 typeof value.length == TYPE_number;
86 }
87
167 88
168 /** 89 /**
169 * Wrapper for eval with a context. NOTE: The style guide 90 * Finds a slice of an array.
170 * deprecates eval, so this is the exception that proves the
171 * rule. Notice also that since the value of the expression is
172 * returned rather than assigned to a local variable, one major
173 * objection aganist the use of the with() statement, namely that
174 * properties of the with() target override local variables of the
175 * same name, is void here.
176 * 91 *
177 * @param {String} expr 92 * @param {Array} array Array to be sliced.
178 * @param {Object} context 93 * @param {number} start The start of the slice.
179 * @return {Object|Null} 94 * @param {number} opt_end The end of the slice (optional).
95 * @return {Array} array The slice of the array from start to end.
180 */ 96 */
181 function jsEvalWith(expr, context) { 97 function arraySlice(array, start, opt_end) {
182 try { 98 return Function.prototype.call.apply(Array.prototype.slice, arguments);
183 with (context) { 99 }
184 return eval('[' + expr + '][0]'); 100
185 } 101
186 } catch (e) { 102 /**
187 return null; 103 * Jscompiler wrapper for parseInt() with base 10.
104 *
105 * @param {string} s string repersentation of a number.
106 *
107 * @return {number} The integer contained in s, converted on base 10.
108 */
109 function parseInt10(s) {
110 return parseInt(s, 10);
111 }
112
113
114 /**
115 * Clears the array by setting the length property to 0. This usually
116 * works, and if it should turn out not to work everywhere, here would
117 * be the place to implement the browser specific workaround.
118 *
119 * @param {Array} array Array to be cleared.
120 */
121 function arrayClear(array) {
122 array.length = 0;
123 }
124
125
126 /**
127 * Prebinds "this" within the given method to an object, but ignores all
128 * arguments passed to the resulting function.
129 * I.e. var_args are all the arguments that method is invoked with when
130 * invoking the bound function.
131 *
132 * @param {Object|null} object The object that the method call targets.
133 * @param {Function} method The target method.
134 * @return {Function} Method with the target object bound to it and curried by
135 * the provided arguments.
136 */
137 function bindFully(object, method, var_args) {
138 var args = arraySlice(arguments, 2);
139 return function() {
140 return method.apply(object, args);
188 } 141 }
189 } 142 }
190 143
191
192 var DOM_ELEMENT_NODE = 1; 144 var DOM_ELEMENT_NODE = 1;
193 var DOM_ATTRIBUTE_NODE = 2; 145 var DOM_ATTRIBUTE_NODE = 2;
194 var DOM_TEXT_NODE = 3; 146 var DOM_TEXT_NODE = 3;
195 var DOM_CDATA_SECTION_NODE = 4; 147 var DOM_CDATA_SECTION_NODE = 4;
196 var DOM_ENTITY_REFERENCE_NODE = 5; 148 var DOM_ENTITY_REFERENCE_NODE = 5;
197 var DOM_ENTITY_NODE = 6; 149 var DOM_ENTITY_NODE = 6;
198 var DOM_PROCESSING_INSTRUCTION_NODE = 7; 150 var DOM_PROCESSING_INSTRUCTION_NODE = 7;
199 var DOM_COMMENT_NODE = 8; 151 var DOM_COMMENT_NODE = 8;
200 var DOM_DOCUMENT_NODE = 9; 152 var DOM_DOCUMENT_NODE = 9;
201 var DOM_DOCUMENT_TYPE_NODE = 10; 153 var DOM_DOCUMENT_TYPE_NODE = 10;
202 var DOM_DOCUMENT_FRAGMENT_NODE = 11; 154 var DOM_DOCUMENT_FRAGMENT_NODE = 11;
203 var DOM_NOTATION_NODE = 12; 155 var DOM_NOTATION_NODE = 12;
204 156
157
158
159 function domGetElementById(document, id) {
160 return document.getElementById(id);
161 }
162
205 /** 163 /**
206 * Traverses the element nodes in the DOM tree underneath the given 164 * Creates a new node in the given document
207 * node and finds the first node with elemId, or null if there is no such
208 * element. Traversal is in depth-first order.
209 * 165 *
210 * NOTE: The reason this is not combined with the elem() function is 166 * @param {Document} doc Target document.
211 * that the implementations are different. 167 * @param {string} name Name of new element (i.e. the tag name)..
212 * elem() is a wrapper for the built-in document.getElementById() function, 168 * @return {Element} Newly constructed element.
213 * whereas this function performs the traversal itself. 169 */
214 * Modifying elem() to take an optional root node is a possibility, 170 function domCreateElement(doc, name) {
215 * but the in-built function would perform better than using our own traversal. 171 return doc.createElement(name);
172 }
173
174 /**
175 * Traverses the element nodes in the DOM section underneath the given
176 * node and invokes the given callback as a method on every element
177 * node encountered.
216 * 178 *
217 * @param {Element} node Root element of subtree to traverse. 179 * @param {Element} node Parent element of the subtree to traverse.
218 * @param {String} elemId The id of the element to search for. 180 * @param {Function} callback Called on each node in the traversal.
219 * @return {Element|Null} The corresponding element, or null if not found.
220 */ 181 */
221 function nodeGetElementById(node, elemId) { 182 function domTraverseElements(node, callback) {
183 var traverser = new DomTraverser(callback);
184 traverser.run(node);
185 }
186
187 /**
188 * A class to hold state for a dom traversal.
189 * @param {Function} callback Called on each node in the traversal.
190 * @constructor
191 * @class
192 */
193 function DomTraverser(callback) {
194 this.callback_ = callback;
195 }
196
197 /**
198 * Processes the dom tree in breadth-first order.
199 * @param {Element} root The root node of the traversal.
200 */
201 DomTraverser.prototype.run = function(root) {
202 var me = this;
203 me.queue_ = [ root ];
204 while (jsLength(me.queue_)) {
205 me.process_(me.queue_.shift());
206 }
207 }
208
209 /**
210 * Processes a single node.
211 * @param {Element} node The current node of the traversal.
212 */
213 DomTraverser.prototype.process_ = function(node) {
214 var me = this;
215
216 me.callback_(node);
217
222 for (var c = node.firstChild; c; c = c.nextSibling) { 218 for (var c = node.firstChild; c; c = c.nextSibling) {
223 if (c.id == elemId) {
224 return c;
225 }
226 if (c.nodeType == DOM_ELEMENT_NODE) { 219 if (c.nodeType == DOM_ELEMENT_NODE) {
227 var n = arguments.callee.call(this, c, elemId); 220 me.queue_.push(c);
228 if (n) {
229 return n;
230 }
231 } 221 }
232 } 222 }
233 return null;
234 } 223 }
235 224
236
237 /** 225 /**
238 * Get an attribute from the DOM. Simple redirect, exists to compress code. 226 * Get an attribute from the DOM. Simple redirect, exists to compress code.
239 * 227 *
240 * @param {Element} node Element to interrogate. 228 * @param {Element} node Element to interrogate.
241 * @param {String} name Name of parameter to extract. 229 * @param {string} name Name of parameter to extract.
242 * @return {String} Resulting attribute. 230 * @return {string|null} Resulting attribute.
243 */ 231 */
244 function domGetAttribute(node, name) { 232 function domGetAttribute(node, name) {
245 return node.getAttribute(name); 233 return node.getAttribute(name);
246 } 234 }
247 235
236
248 /** 237 /**
249 * Set an attribute in the DOM. Simple redirect to compress code. 238 * Set an attribute in the DOM. Simple redirect to compress code.
250 * 239 *
251 * @param {Element} node Element to interrogate. 240 * @param {Element} node Element to interrogate.
252 * @param {String} name Name of parameter to set. 241 * @param {string} name Name of parameter to set.
253 * @param {String} value Set attribute to this value. 242 * @param {string|number} value Set attribute to this value.
254 */ 243 */
255 function domSetAttribute(node, name, value) { 244 function domSetAttribute(node, name, value) {
256 node.setAttribute(name, value); 245 node.setAttribute(name, value);
257 } 246 }
258 247
259 /** 248 /**
260 * Remove an attribute from the DOM. Simple redirect to compress code. 249 * Remove an attribute from the DOM. Simple redirect to compress code.
261 * 250 *
262 * @param {Element} node Element to interrogate. 251 * @param {Element} node Element to interrogate.
263 * @param {String} name Name of parameter to remove. 252 * @param {string} name Name of parameter to remove.
264 */ 253 */
265 function domRemoveAttribute(node, name) { 254 function domRemoveAttribute(node, name) {
266 node.removeAttribute(name); 255 node.removeAttribute(name);
267 } 256 }
268 257
269 /** 258 /**
270 * Clone a node in the DOM. 259 * Clone a node in the DOM.
271 * 260 *
272 * @param {Node} node Node to clone. 261 * @param {Node} node Node to clone.
273 * @return {Node} Cloned node. 262 * @return {Node} Cloned node.
274 */ 263 */
275 function domCloneNode(node) { 264 function domCloneNode(node) {
276 return node.cloneNode(true); 265 return node.cloneNode(true);
277 } 266 }
278 267
279
280 /** 268 /**
281 * Return a safe string for the className of a node. 269 * Clone a element in the DOM.
282 * If className is not a string, returns "".
283 * 270 *
284 * @param {Element} node DOM element to query. 271 * @param {Element} element Element to clone.
285 * @return {String} 272 * @return {Element} Cloned element.
286 */ 273 */
287 function domClassName(node) { 274 function domCloneElement(element) {
288 return node.className ? "" + node.className : ""; 275 return /** @type {Element} */(domCloneNode(element));
289 } 276 }
290 277
291 /** 278 /**
292 * Adds a class name to the class attribute of the given node. 279 * Returns the document owner of the given element. In particular,
280 * returns window.document if node is null or the browser does not
281 * support ownerDocument. If the node is a document itself, returns
282 * itself.
293 * 283 *
294 * @param {Element} node DOM element to modify. 284 * @param {Node|null|undefined} node The node whose ownerDocument is required.
295 * @param {String} className Class name to add. 285 * @returns {Document} The owner document or window.document if unsupported.
296 */ 286 */
297 function domAddClass(node, className) { 287 function ownerDocument(node) {
298 var name = domClassName(node); 288 if (!node) {
299 if (name) { 289 return document;
300 var cn = name.split(/\s+/); 290 } else if (node.nodeType == DOM_DOCUMENT_NODE) {
301 var found = false; 291 return /** @type Document */(node);
302 for (var i = 0; i < jsLength(cn); ++i) {
303 if (cn[i] == className) {
304 found = true;
305 break;
306 }
307 }
308
309 if (!found) {
310 cn.push(className);
311 }
312
313 node.className = cn.join(' ');
314 } else { 292 } else {
315 node.className = className; 293 return node.ownerDocument || document;
316 } 294 }
317 } 295 }
318 296
319 /** 297 /**
320 * Removes a class name from the class attribute of the given node. 298 * Creates a new text node in the given document.
321 * 299 *
322 * @param {Element} node DOM element to modify. 300 * @param {Document} doc Target document.
323 * @param {String} className Class name to remove. 301 * @param {string} text Text composing new text node.
302 * @return {Text} Newly constructed text node.
324 */ 303 */
325 function domRemoveClass(node, className) { 304 function domCreateTextNode(doc, text) {
326 var c = domClassName(node); 305 return doc.createTextNode(text);
327 if (!c || c.indexOf(className) == -1) {
328 return;
329 }
330 var cn = c.split(/\s+/);
331 for (var i = 0; i < jsLength(cn); ++i) {
332 if (cn[i] == className) {
333 cn.splice(i--, 1);
334 }
335 }
336 node.className = cn.join(' ');
337 }
338
339 /**
340 * Checks if a node belongs to a style class.
341 *
342 * @param {Element} node DOM element to test.
343 * @param {String} className Class name to check for.
344 * @return {Boolean} Node belongs to style class.
345 */
346 function domTestClass(node, className) {
347 var cn = domClassName(node).split(/\s+/);
348 for (var i = 0; i < jsLength(cn); ++i) {
349 if (cn[i] == className) {
350 return true;
351 }
352 }
353 return false;
354 }
355
356 /**
357 * Inserts a new child before a given sibling.
358 *
359 * @param {Node} newChild Node to insert.
360 * @param {Node} oldChild Sibling node.
361 * @return {Node} Reference to new child.
362 */
363 function domInsertBefore(newChild, oldChild) {
364 return oldChild.parentNode.insertBefore(newChild, oldChild);
365 } 306 }
366 307
367 /** 308 /**
368 * Appends a new child to the specified (parent) node. 309 * Appends a new child to the specified (parent) node.
369 * 310 *
370 * @param {Element} node Parent element. 311 * @param {Element} node Parent element.
371 * @param {Node} child Child node to append. 312 * @param {Node} child Child node to append.
372 * @return {Node} Newly appended node. 313 * @return {Node} Newly appended node.
373 */ 314 */
374 function domAppendChild(node, child) { 315 function domAppendChild(node, child) {
375 return node.appendChild(child); 316 return node.appendChild(child);
376 } 317 }
377 318
378 /** 319 /**
379 * Remove a new child from the specified (parent) node. 320 * Sets display to default.
380 * 321 *
381 * @param {Element} node Parent element. 322 * @param {Element} node The dom element to manipulate.
382 * @param {Node} child Child node to remove.
383 * @return {Node} Removed node.
384 */ 323 */
385 function domRemoveChild(node, child) { 324 function displayDefault(node) {
386 return node.removeChild(child); 325 node.style[CSS_display] = '';
326 }
327
328 /**
329 * Sets display to none. Doing this as a function saves a few bytes for
330 * the 'style.display' property and the 'none' literal.
331 *
332 * @param {Element} node The dom element to manipulate.
333 */
334 function displayNone(node) {
335 node.style[CSS_display] = 'none';
336 }
337
338
339 /**
340 * Sets position style attribute to absolute.
341 *
342 * @param {Element} node The dom element to manipulate.
343 */
344 function positionAbsolute(node) {
345 node.style[CSS_position] = 'absolute';
346 }
347
348
349 /**
350 * Inserts a new child before a given sibling.
351 *
352 * @param {Node} newChild Node to insert.
353 * @param {Node} oldChild Sibling node.
354 * @return {Node} Reference to new child.
355 */
356 function domInsertBefore(newChild, oldChild) {
357 return oldChild.parentNode.insertBefore(newChild, oldChild);
387 } 358 }
388 359
389 /** 360 /**
390 * Replaces an old child node with a new child node. 361 * Replaces an old child node with a new child node.
391 * 362 *
392 * @param {Node} newChild New child to append. 363 * @param {Node} newChild New child to append.
393 * @param {Node} oldChild Old child to remove. 364 * @param {Node} oldChild Old child to remove.
394 * @return {Node} Replaced node. 365 * @return {Node} Replaced node.
395 */ 366 */
396 function domReplaceChild(newChild, oldChild) { 367 function domReplaceChild(newChild, oldChild) {
397 return oldChild.parentNode.replaceChild(newChild, oldChild); 368 return oldChild.parentNode.replaceChild(newChild, oldChild);
398 } 369 }
399 370
400 /** 371 /**
401 * Removes a node from the DOM. 372 * Removes a node from the DOM.
402 * 373 *
403 * @param {Node} node The node to remove. 374 * @param {Node} node The node to remove.
404 * @return {Node} The removed node. 375 * @return {Node} The removed node.
405 */ 376 */
406 function domRemoveNode(node) { 377 function domRemoveNode(node) {
407 return domRemoveChild(node.parentNode, node); 378 return domRemoveChild(node.parentNode, node);
408 } 379 }
409 380
410 /** 381 /**
411 * Creates a new text node in the given document. 382 * Remove a child from the specified (parent) node.
412 * 383 *
413 * @param {Document} doc Target document. 384 * @param {Element} node Parent element.
414 * @param {String} text Text composing new text node. 385 * @param {Node} child Child node to remove.
415 * @return {Text} Newly constructed text node. 386 * @return {Node} Removed node.
416 */ 387 */
417 function domCreateTextNode(doc, text) { 388 function domRemoveChild(node, child) {
418 return doc.createTextNode(text); 389 return node.removeChild(child);
419 } 390 }
420 391
421 /**
422 * Creates a new node in the given document
423 *
424 * @param {Document} doc Target document.
425 * @param {String} name Name of new element (i.e. the tag name)..
426 * @return {Element} Newly constructed element.
427 */
428 function domCreateElement(doc, name) {
429 return doc.createElement(name);
430 }
431
432 /**
433 * Creates a new attribute in the given document.
434 *
435 * @param {Document} doc Target document.
436 * @param {String} name Name of new attribute.
437 * @return {Attr} Newly constructed attribute.
438 */
439 function domCreateAttribute(doc, name) {
440 return doc.createAttribute(name);
441 }
442
443 /**
444 * Creates a new comment in the given document.
445 *
446 * @param {Document} doc Target document.
447 * @param {String} text Comment text.
448 * @return {Comment} Newly constructed comment.
449 */
450 function domCreateComment(doc, text) {
451 return doc.createComment(text);
452 }
453
454 /**
455 * Creates a document fragment.
456 *
457 * @param {Document} doc Target document.
458 * @return {DocumentFragment} Resulting document fragment node.
459 */
460 function domCreateDocumentFragment(doc) {
461 return doc.createDocumentFragment();
462 }
463
464 /**
465 * Redirect to document.getElementById
466 *
467 * @param {Document} doc Target document.
468 * @param {String} id Id of requested node.
469 * @return {Element|Null} Resulting element.
470 */
471 function domGetElementById(doc, id) {
472 return doc.getElementById(id);
473 }
474
475 /**
476 * Redirect to window.setInterval
477 *
478 * @param {Window} win Target window.
479 * @param {Function} fun Callback function.
480 * @param {Number} time Time in milliseconds.
481 * @return {Object} Contract id.
482 */
483 function windowSetInterval(win, fun, time) {
484 return win.setInterval(fun, time);
485 }
486
487 /**
488 * Redirect to window.clearInterval
489 *
490 * @param {Window} win Target window.
491 * @param {object} id Contract id.
492 * @return {any} NOTE: Return type unknown?
493 */
494 function windowClearInterval(win, id) {
495 return win.clearInterval(id);
496 }
497
498 /**
499 * Determines whether one node is recursively contained in another.
500 * @param parent The parent node.
501 * @param child The node to look for in parent.
502 * @return parent recursively contains child
503 */
504 function containsNode(parent, child) {
505 while (parent != child && child.parentNode) {
506 child = child.parentNode;
507 }
508 return parent == child;
509 };
510 /**
511 * @fileoverview This file contains javascript utility functions that
512 * do not depend on anything defined elsewhere.
513 *
514 */
515
516 /**
517 * Returns the value of the length property of the given object. Used
518 * to reduce compiled code size.
519 *
520 * @param {Array | String} a The string or array to interrogate.
521 * @return {Number} The value of the length property.
522 */
523 function jsLength(a) {
524 return a.length;
525 }
526
527 var min = Math.min;
528 var max = Math.max;
529 var ceil = Math.ceil;
530 var floor = Math.floor;
531 var round = Math.round;
532 var abs = Math.abs;
533
534 /**
535 * Copies all properties from second object to the first. Modifies to.
536 *
537 * @param {Object} to The target object.
538 * @param {Object} from The source object.
539 */
540 function copyProperties(to, from) {
541 foreachin(from, function(p) {
542 to[p] = from[p];
543 });
544 }
545
546 /**
547 * Iterates over the array, calling the given function for each
548 * element.
549 *
550 * @param {Array} array
551 * @param {Function} fn
552 */
553 function foreach(array, fn) {
554 var I = jsLength(array);
555 for (var i = 0; i < I; ++i) {
556 fn(array[i], i);
557 }
558 }
559
560 /**
561 * Safely iterates over all properties of the given object, calling
562 * the given function for each property. If opt_all isn't true, uses
563 * hasOwnProperty() to assure the property is on the object, not on
564 * its prototype.
565 *
566 * @param {Object} object
567 * @param {Function} fn
568 * @param {Boolean} opt_all If true, also iterates over inherited properties.
569 */
570 function foreachin(object, fn, opt_all) {
571 for (var i in object) {
572 if (opt_all || !object.hasOwnProperty || object.hasOwnProperty(i)) {
573 fn(i, object[i]);
574 }
575 }
576 }
577
578 /**
579 * Appends the second array to the first, copying its elements.
580 * Optionally only a slice of the second array is copied.
581 *
582 * @param {Array} a1 Target array (modified).
583 * @param {Array} a2 Source array.
584 * @param {Number} opt_begin Begin of slice of second array (optional).
585 * @param {Number} opt_end End (exclusive) of slice of second array (optional).
586 */
587 function arrayAppend(a1, a2, opt_begin, opt_end) {
588 var i0 = opt_begin || 0;
589 var i1 = opt_end || jsLength(a2);
590 for (var i = i0; i < i1; ++i) {
591 a1.push(a2[i]);
592 }
593 }
594 392
595 /** 393 /**
596 * Trim whitespace from begin and end of string. 394 * Trim whitespace from begin and end of string.
597 * 395 *
598 * @see testStringTrim(); 396 * @see testStringTrim();
599 * 397 *
600 * @param {String} str Input string. 398 * @param {string} str Input string.
601 * @return {String} Trimmed string. 399 * @return {string} Trimmed string.
602 */ 400 */
603 function stringTrim(str) { 401 function stringTrim(str) {
604 return stringTrimRight(stringTrimLeft(str)); 402 return stringTrimRight(stringTrimLeft(str));
605 } 403 }
606 404
607 /** 405 /**
608 * Trim whitespace from beginning of string. 406 * Trim whitespace from beginning of string.
609 * 407 *
610 * @see testStringTrimLeft(); 408 * @see testStringTrimLeft();
611 * 409 *
612 * @param {String} str Input string. 410 * @param {string} str Input string.
613 * @return {String} Trimmed string. 411 * @return {string} Trimmed string.
614 */ 412 */
615 function stringTrimLeft(str) { 413 function stringTrimLeft(str) {
616 return str.replace(/^\s+/, ""); 414 return str.replace(/^\s+/, "");
617 } 415 }
618 416
619 /** 417 /**
620 * Trim whitespace from end of string. 418 * Trim whitespace from end of string.
621 * 419 *
622 * @see testStringTrimRight(); 420 * @see testStringTrimRight();
623 * 421 *
624 * @param {String} str Input string. 422 * @param {string} str Input string.
625 * @return {String} Trimmed string. 423 * @return {string} Trimmed string.
626 */ 424 */
627 function stringTrimRight(str) { 425 function stringTrimRight(str) {
628 return str.replace(/\s+$/, ""); 426 return str.replace(/\s+$/, "");
629 } 427 }
630 428 /**
631 /** 429 * Author: Steffen Meschkat <mesch@google.com>
632 * Jscompiler wrapper for parseInt() with base 10. 430 *
633 * 431 * @fileoverview This class is used to evaluate expressions in a local
634 * @param {String} s String repersentation of a number. 432 * context. Used by JstProcessor.
635 * 433 */
636 * @return {Number} The integer contained in s, converted on base 10. 434
637 */ 435
638 function parseInt10(s) { 436 /**
639 return parseInt(s, 10); 437 * Names of special variables defined by the jstemplate evaluation
640 } 438 * context. These can be used in js expression in jstemplate
641 /** 439 * attributes.
440 */
441 var VAR_index = '$index';
442 var VAR_count = '$count';
443 var VAR_this = '$this';
444 var VAR_context = '$context';
445 var VAR_top = '$top';
446
447
448 /**
449 * The name of the global variable which holds the value to be returned if
450 * context evaluation results in an error.
451 * Use JsEvalContext.setGlobal(GLOB_default, value) to set this.
452 */
453 var GLOB_default = '$default';
454
455
456 /**
457 * Un-inlined literals, to avoid object creation in IE6. TODO(mesch):
458 * So far, these are only used here, but we could use them thoughout
459 * the code and thus move them to constants.js.
460 */
461 var CHAR_colon = ':';
462 var REGEXP_semicolon = /\s*;\s*/;
463
464
465 /**
466 * See constructor_()
467 * @param {Object|null} opt_data
468 * @param {Object} opt_parent
469 * @constructor
470 */
471 function JsEvalContext(opt_data, opt_parent) {
472 this.constructor_.apply(this, arguments);
473 }
474
475 /**
476 * Context for processing a jstemplate. The context contains a context
477 * object, whose properties can be referred to in jstemplate
478 * expressions, and it holds the locally defined variables.
479 *
480 * @param {Object|null} opt_data The context object. Null if no context.
481 *
482 * @param {Object} opt_parent The parent context, from which local
483 * variables are inherited. Normally the context object of the parent
484 * context is the object whose property the parent object is. Null for the
485 * context of the root object.
486 */
487 JsEvalContext.prototype.constructor_ = function(opt_data, opt_parent) {
488 var me = this;
489
490 /**
491 * The context for variable definitions in which the jstemplate
492 * expressions are evaluated. Other than for the local context,
493 * which replaces the parent context, variable definitions of the
494 * parent are inherited. The special variable $this points to data_.
495 *
496 * If this instance is recycled from the cache, then the property is
497 * already initialized.
498 *
499 * @type {Object}
500 */
501 if (!me.vars_) {
502 me.vars_ = {};
503 }
504 if (opt_parent) {
505 copyProperties(me.vars_, opt_parent.vars_);
506 } else {
507 copyProperties(me.vars_, JsEvalContext.globals_);
508 }
509
510 /**
511 * The current context object is assigned to the special variable
512 * $this so it is possible to use it in expressions.
513 * @type Object
514 */
515 me.vars_[VAR_this] = opt_data;
516
517 /**
518 * The entire context structure is exposed as a variable so it can be
519 * passed to javascript invocations through jseval.
520 */
521 me.vars_[VAR_context] = me;
522
523 /**
524 * The local context of the input data in which the jstemplate
525 * expressions are evaluated. Notice that this is usually an Object,
526 * but it can also be a scalar value (and then still the expression
527 * $this can be used to refer to it). Notice this can even be value,
528 * undefined or null. Hence, we have to protect jsexec() from using
529 * undefined or null, yet we want $this to reflect the true value of
530 * the current context. Thus we assign the original value to $this,
531 * above, but for the expression context we replace null and
532 * undefined by the empty string.
533 *
534 * @type {Object|null}
535 */
536 me.data_ = getDefaultObject(opt_data, STRING_empty);
537
538 if (!opt_parent) {
539 me.vars_[VAR_top] = me.data_;
540 }
541 };
542
543
544 /**
545 * A map of globally defined symbols. Every instance of JsExprContext
546 * inherits them in its vars_.
547 * @type Object
548 */
549 JsEvalContext.globals_ = {}
550
551
552 /**
553 * Sets a global symbol. It will be available like a variable in every
554 * JsEvalContext instance. This is intended mainly to register
555 * immutable global objects, such as functions, at load time, and not
556 * to add global data at runtime. I.e. the same objections as to
557 * global variables in general apply also here. (Hence the name
558 * "global", and not "global var".)
559 * @param {string} name
560 * @param {Object|null} value
561 */
562 JsEvalContext.setGlobal = function(name, value) {
563 JsEvalContext.globals_[name] = value;
564 };
565
566
567 /**
568 * Set the default value to be returned if context evaluation results in an
569 * error. (This can occur if a non-existent value was requested).
570 */
571 JsEvalContext.setGlobal(GLOB_default, null);
572
573
574 /**
575 * A cache to reuse JsEvalContext instances. (IE6 perf)
576 *
577 * @type Array.<JsEvalContext>
578 */
579 JsEvalContext.recycledInstances_ = [];
580
581
582 /**
583 * A factory to create a JsEvalContext instance, possibly reusing
584 * one from recycledInstances_. (IE6 perf)
585 *
586 * @param {Object} opt_data
587 * @param {JsEvalContext} opt_parent
588 * @return {JsEvalContext}
589 */
590 JsEvalContext.create = function(opt_data, opt_parent) {
591 if (jsLength(JsEvalContext.recycledInstances_) > 0) {
592 var instance = JsEvalContext.recycledInstances_.pop();
593 JsEvalContext.call(instance, opt_data, opt_parent);
594 return instance;
595 } else {
596 return new JsEvalContext(opt_data, opt_parent);
597 }
598 };
599
600
601 /**
602 * Recycle a used JsEvalContext instance, so we can avoid creating one
603 * the next time we need one. (IE6 perf)
604 *
605 * @param {JsEvalContext} instance
606 */
607 JsEvalContext.recycle = function(instance) {
608 for (var i in instance.vars_) {
609 delete instance.vars_[i];
610 }
611 instance.data_ = null;
612 JsEvalContext.recycledInstances_.push(instance);
613 };
614
615
616 /**
617 * Executes a function created using jsEvalToFunction() in the context
618 * of vars, data, and template.
619 *
620 * @param {Function} exprFunction A javascript function created from
621 * a jstemplate attribute value.
622 *
623 * @param {Element} template DOM node of the template.
624 *
625 * @return {Object|null} The value of the expression from which
626 * exprFunction was created in the current js expression context and
627 * the context of template.
628 */
629 JsEvalContext.prototype.jsexec = function(exprFunction, template) {
630 try {
631 return exprFunction.call(template, this.vars_, this.data_);
632 } catch (e) {
633 log('jsexec EXCEPTION: ' + e + ' at ' + template +
634 ' with ' + exprFunction);
635 return JsEvalContext.globals_[GLOB_default];
636 }
637 };
638
639
640 /**
641 * Clones the current context for a new context object. The cloned
642 * context has the data object as its context object and the current
643 * context as its parent context. It also sets the $index variable to
644 * the given value. This value usually is the position of the data
645 * object in a list for which a template is instantiated multiply.
646 *
647 * @param {Object} data The new context object.
648 *
649 * @param {number} index Position of the new context when multiply
650 * instantiated. (See implementation of jstSelect().)
651 *
652 * @param {number} count The total number of contexts that were multiply
653 * instantiated. (See implementation of jstSelect().)
654 *
655 * @return {JsEvalContext}
656 */
657 JsEvalContext.prototype.clone = function(data, index, count) {
658 var ret = JsEvalContext.create(data, this);
659 ret.setVariable(VAR_index, index);
660 ret.setVariable(VAR_count, count);
661 return ret;
662 };
663
664
665 /**
666 * Binds a local variable to the given value. If set from jstemplate
667 * jsvalue expressions, variable names must start with $, but in the
668 * API they only have to be valid javascript identifier.
669 *
670 * @param {string} name
671 *
672 * @param {Object?} value
673 */
674 JsEvalContext.prototype.setVariable = function(name, value) {
675 this.vars_[name] = value;
676 };
677
678
679 /**
680 * Returns the value bound to the local variable of the given name, or
681 * undefined if it wasn't set. There is no way to distinguish a
682 * variable that wasn't set from a variable that was set to
683 * undefined. Used mostly for testing.
684 *
685 * @param {string} name
686 *
687 * @return {Object?} value
688 */
689 JsEvalContext.prototype.getVariable = function(name) {
690 return this.vars_[name];
691 };
692
693
694 /**
695 * Evaluates a string expression within the scope of this context
696 * and returns the result.
697 *
698 * @param {string} expr A javascript expression
699 * @param {Element} opt_template An optional node to serve as "this"
700 *
701 * @return {Object?} value
702 */
703 JsEvalContext.prototype.evalExpression = function(expr, opt_template) {
704 var exprFunction = jsEvalToFunction(expr);
705 return this.jsexec(exprFunction, opt_template);
706 };
707
708
709 /**
710 * Uninlined string literals for jsEvalToFunction() (IE6 perf).
711 */
712 var STRING_a = 'a_';
713 var STRING_b = 'b_';
714 var STRING_with = 'with (a_) with (b_) return ';
715
716
717 /**
718 * Cache for jsEvalToFunction results.
719 * @type Object
720 */
721 JsEvalContext.evalToFunctionCache_ = {};
722
723
724 /**
725 * Evaluates the given expression as the body of a function that takes
726 * vars and data as arguments. Since the resulting function depends
727 * only on expr, we cache the result so we save some Function
728 * invocations, and some object creations in IE6.
729 *
730 * @param {string} expr A javascript expression.
731 *
732 * @return {Function} A function that returns the value of expr in the
733 * context of vars and data.
734 */
735 function jsEvalToFunction(expr) {
736 if (!JsEvalContext.evalToFunctionCache_[expr]) {
737 try {
738 JsEvalContext.evalToFunctionCache_[expr] =
739 new Function(STRING_a, STRING_b, STRING_with + expr);
740 } catch (e) {
741 log('jsEvalToFunction (' + expr + ') EXCEPTION ' + e);
742 }
743 }
744 return JsEvalContext.evalToFunctionCache_[expr];
745 }
746
747
748 /**
749 * Evaluates the given expression to itself. This is meant to pass
750 * through string attribute values.
751 *
752 * @param {string} expr
753 *
754 * @return {string}
755 */
756 function jsEvalToSelf(expr) {
757 return expr;
758 }
759
760
761 /**
762 * Parses the value of the jsvalues attribute in jstemplates: splits
763 * it up into a map of labels and expressions, and creates functions
764 * from the expressions that are suitable for execution by
765 * JsEvalContext.jsexec(). All that is returned as a flattened array
766 * of pairs of a String and a Function.
767 *
768 * @param {string} expr
769 *
770 * @return {Array}
771 */
772 function jsEvalToValues(expr) {
773 var ret = [];
774 var values = expr.split(REGEXP_semicolon);
775 for (var i = 0, I = jsLength(values); i < I; ++i) {
776 var colon = values[i].indexOf(CHAR_colon);
777 if (colon < 0) {
778 continue;
779 }
780 var label = stringTrim(values[i].substr(0, colon));
781 var value = jsEvalToFunction(values[i].substr(colon + 1));
782 ret.push(label, value);
783 }
784 return ret;
785 }
786
787
788 /**
789 * Parses the value of the jseval attribute of jstemplates: splits it
790 * up into a list of expressions, and creates functions from the
791 * expressions that are suitable for execution by
792 * JsEvalContext.jsexec(). All that is returned as an Array of
793 * Function.
794 *
795 * @param {string} expr
796 *
797 * @return {Array.<Function>}
798 */
799 function jsEvalToExpressions(expr) {
800 var ret = [];
801 var values = expr.split(REGEXP_semicolon);
802 for (var i = 0, I = jsLength(values); i < I; ++i) {
803 if (values[i]) {
804 var value = jsEvalToFunction(values[i]);
805 ret.push(value);
806 }
807 }
808 return ret;
809 }
810 /**
811 * Author: Steffen Meschkat <mesch@google.com>
812 *
642 * @fileoverview A simple formatter to project JavaScript data into 813 * @fileoverview A simple formatter to project JavaScript data into
643 * HTML templates. The template is edited in place. I.e. in order to 814 * HTML templates. The template is edited in place. I.e. in order to
644 * instantiate a template, clone it from the DOM first, and then 815 * instantiate a template, clone it from the DOM first, and then
645 * process the cloned template. This allows for updating of templates: 816 * process the cloned template. This allows for updating of templates:
646 * If the templates is processed again, changed values are merely 817 * If the templates is processed again, changed values are merely
647 * updated. 818 * updated.
648 * 819 *
649 * NOTE: IE DOM doesn't have importNode(). 820 * NOTE(mesch): IE DOM doesn't have importNode().
650 * 821 *
651 * NOTE: The property name "length" must not be used in input 822 * NOTE(mesch): The property name "length" must not be used in input
652 * data, see comment in jstSelect_(). 823 * data, see comment in jstSelect_().
653 */ 824 */
654 825
655 826
656 /** 827 /**
657 * Names of jstemplate attributes. These attributes are attached to 828 * Names of jstemplate attributes. These attributes are attached to
658 * normal HTML elements and bind expression context data to the HTML 829 * normal HTML elements and bind expression context data to the HTML
659 * fragment that is used as template. 830 * fragment that is used as template.
660 */ 831 */
661 var ATT_select = 'jsselect'; 832 var ATT_select = 'jsselect';
662 var ATT_instance = 'jsinstance'; 833 var ATT_instance = 'jsinstance';
663 var ATT_display = 'jsdisplay'; 834 var ATT_display = 'jsdisplay';
664 var ATT_values = 'jsvalues'; 835 var ATT_values = 'jsvalues';
836 var ATT_vars = 'jsvars';
665 var ATT_eval = 'jseval'; 837 var ATT_eval = 'jseval';
666 var ATT_transclude = 'transclude'; 838 var ATT_transclude = 'transclude';
667 var ATT_content = 'jscontent'; 839 var ATT_content = 'jscontent';
668 840 var ATT_skip = 'jsskip';
669 841
670 /** 842
671 * Names of special variables defined by the jstemplate evaluation 843 /**
672 * context. These can be used in js expression in jstemplate 844 * Name of the attribute that caches a reference to the parsed
673 * attributes. 845 * template processing attribute values on a template node.
674 */ 846 */
675 var VAR_index = '$index'; 847 var ATT_jstcache = 'jstcache';
676 var VAR_this = '$this'; 848
677 849
678 850 /**
679 /** 851 * Name of the property that caches the parsed template processing
680 * Context for processing a jstemplate. The context contains a context 852 * attribute values on a template node.
681 * object, whose properties can be referred to in jstemplate 853 */
682 * expressions, and it holds the locally defined variables. 854 var PROP_jstcache = '__jstcache';
683 * 855
684 * @param {Object} opt_data The context object. Null if no context. 856
685 * 857 /**
686 * @param {Object} opt_parent The parent context, from which local 858 * ID of the element that contains dynamically loaded jstemplates.
687 * variables are inherited. Normally the context object of the parent 859 */
688 * context is the object whose property the parent object is. Null for the 860 var STRING_jsts = 'jsts';
689 * context of the root object. 861
690 * 862
691 * @constructor 863 /**
692 */ 864 * Un-inlined string literals, to avoid object creation in
693 function JsExprContext(opt_data, opt_parent) { 865 * IE6.
694 var me = this; 866 */
695 867 var CHAR_asterisk = '*';
696 /** 868 var CHAR_dollar = '$';
697 * The local context of the input data in which the jstemplate 869 var CHAR_period = '.';
698 * expressions are evaluated. Notice that this is usually an Object, 870 var CHAR_ampersand = '&';
699 * but it can also be a scalar value (and then still the expression 871 var STRING_div = 'div';
700 * $this can be used to refer to it). Notice this can be a scalar 872 var STRING_id = 'id';
701 * value, including undefined. 873 var STRING_asteriskzero = '*0';
702 * 874 var STRING_zero = '0';
703 * @type {Object}
704 */
705 me.data_ = opt_data;
706
707 /**
708 * The context for variable definitions in which the jstemplate
709 * expressions are evaluated. Other than for the local context,
710 * which replaces the parent context, variable definitions of the
711 * parent are inherited. The special variable $this points to data_.
712 *
713 * @type {Object}
714 */
715 me.vars_ = {};
716 if (opt_parent) {
717 copyProperties(me.vars_, opt_parent.vars_);
718 }
719 this.vars_[VAR_this] = me.data_;
720 }
721
722
723 /**
724 * Evaluates the given expression in the context of the current
725 * context object and the current local variables.
726 *
727 * @param {String} expr A javascript expression.
728 *
729 * @param {Element} template DOM node of the template.
730 *
731 * @return The value of that expression.
732 */
733 JsExprContext.prototype.jseval = function(expr, template) {
734 with (this.vars_) {
735 with (this.data_) {
736 try {
737 return (function() {
738 return eval('[' + expr + '][0]');
739 }).call(template);
740 } catch (e) {
741 return null;
742 }
743 }
744 }
745 }
746
747
748 /**
749 * Clones the current context for a new context object. The cloned
750 * context has the data object as its context object and the current
751 * context as its parent context. It also sets the $index variable to
752 * the given value. This value usually is the position of the data
753 * object in a list for which a template is instantiated multiply.
754 *
755 * @param {Object} data The new context object.
756 *
757 * @param {Number} index Position of the new context when multiply
758 * instantiated. (See implementation of jstSelect().)
759 *
760 * @return {JsExprContext}
761 */
762 JsExprContext.prototype.clone = function(data, index) {
763 var ret = new JsExprContext(data, this);
764 ret.setVariable(VAR_index, index);
765 if (this.resolver_) {
766 ret.setSubTemplateResolver(this.resolver_);
767 }
768 return ret;
769 }
770
771
772 /**
773 * Binds a local variable to the given value. If set from jstemplate
774 * jsvalue expressions, variable names must start with $, but in the
775 * API they only have to be valid javascript identifier.
776 *
777 * @param {String} name
778 *
779 * @param {Object} value
780 */
781 JsExprContext.prototype.setVariable = function(name, value) {
782 this.vars_[name] = value;
783 }
784
785
786 /**
787 * Sets the function used to resolve the values of the transclude
788 * attribute into DOM nodes. By default, this is jstGetTemplate(). The
789 * value set here is inherited by clones of this context.
790 *
791 * @param {Function} resolver The function used to resolve transclude
792 * ids into a DOM node of a subtemplate. The DOM node returned by this
793 * function will be inserted into the template instance being
794 * processed. Thus, the resolver function must instantiate the
795 * subtemplate as necessary.
796 */
797 JsExprContext.prototype.setSubTemplateResolver = function(resolver) {
798 this.resolver_ = resolver;
799 }
800
801
802 /**
803 * Resolves a sub template from an id. Used to process the transclude
804 * attribute. If a resolver function was set using
805 * setSubTemplateResolver(), it will be used, otherwise
806 * jstGetTemplate().
807 *
808 * @param {String} id The id of the sub template.
809 *
810 * @return {Node} The root DOM node of the sub template, for direct
811 * insertion into the currently processed template instance.
812 */
813 JsExprContext.prototype.getSubTemplate = function(id) {
814 return (this.resolver_ || jstGetTemplate).call(this, id);
815 }
816 875
817 876
818 /** 877 /**
819 * HTML template processor. Data values are bound to HTML templates 878 * HTML template processor. Data values are bound to HTML templates
820 * using the attributes transclude, jsselect, jsdisplay, jscontent, 879 * using the attributes transclude, jsselect, jsdisplay, jscontent,
821 * jsvalues. The template is modifed in place. The values of those 880 * jsvalues. The template is modifed in place. The values of those
822 * attributes are JavaScript expressions that are evaluated in the 881 * attributes are JavaScript expressions that are evaluated in the
823 * context of the data object fragment. 882 * context of the data object fragment.
824 * 883 *
825 * @param {JsExprContext} context Context created from the input data 884 * @param {JsEvalContext} context Context created from the input data
826 * object. 885 * object.
827 * 886 *
828 * @param {Element} template DOM node of the template. This will be 887 * @param {Element} template DOM node of the template. This will be
829 * processed in place. After processing, it will still be a valid 888 * processed in place. After processing, it will still be a valid
830 * template that, if processed again with the same data, will remain 889 * template that, if processed again with the same data, will remain
831 * unchanged. 890 * unchanged.
832 */ 891 *
833 function jstProcess(context, template) { 892 * @param {boolean} opt_debugging Optional flag to collect debugging
834 var processor = new JstProcessor(); 893 * information while processing the template. Only takes effect
835 processor.run_([ processor, processor.jstProcess_, context, template ]); 894 * in MAPS_DEBUG.
895 */
896 function jstProcess(context, template, opt_debugging) {
897 var processor = new JstProcessor;
898 JstProcessor.prepareTemplate_(template);
899
900 /**
901 * Caches the document of the template node, so we don't have to
902 * access it through ownerDocument.
903 * @type Document
904 */
905 processor.document_ = ownerDocument(template);
906
907 processor.run_(bindFully(processor, processor.jstProcessOuter_,
908 context, template));
836 } 909 }
837 910
838 911
839 /** 912 /**
840 * Internal class used by jstemplates to maintain context. 913 * Internal class used by jstemplates to maintain context. This is
841 * NOTE: This is necessary to process deep templates in Safari 914 * necessary to process deep templates in Safari which has a
842 * which has a relatively shallow stack. 915 * relatively shallow maximum recursion depth of 100.
843 * @class 916 * @class
917 * @constructor
844 */ 918 */
845 function JstProcessor() { 919 function JstProcessor() {
846 } 920 }
847 921
848 922
849 /** 923 /**
850 * Runs the state machine, beginning with function "start". 924 * Counter to generate node ids. These ids will be stored in
851 * 925 * ATT_jstcache and be used to lookup the preprocessed js attributes
852 * @param {Array} start The first function to run, in the form 926 * from the jstcache_. The id is stored in an attribute so it
853 * [object, method, args ...] 927 * suvives cloneNode() and thus cloned template nodes can share the
854 */ 928 * same cache entry.
855 JstProcessor.prototype.run_ = function(start) { 929 * @type number
930 */
931 JstProcessor.jstid_ = 0;
932
933
934 /**
935 * Map from jstid to processed js attributes.
936 * @type Object
937 */
938 JstProcessor.jstcache_ = {};
939
940 /**
941 * The neutral cache entry. Used for all nodes that don't have any
942 * jst attributes. We still set the jsid attribute on those nodes so
943 * we can avoid to look again for all the other jst attributes that
944 * aren't there. Remember: not only the processing of the js
945 * attribute values is expensive and we thus want to cache it. The
946 * access to the attributes on the Node in the first place is
947 * expensive too.
948 */
949 JstProcessor.jstcache_[0] = {};
950
951
952 /**
953 * Map from concatenated attribute string to jstid.
954 * The key is the concatenation of all jst atributes found on a node
955 * formatted as "name1=value1&name2=value2&...", in the order defined by
956 * JST_ATTRIBUTES. The value is the id of the jstcache_ entry that can
957 * be used for this node. This allows the reuse of cache entries in cases
958 * when a cached entry already exists for a given combination of attribute
959 * values. (For example when two different nodes in a template share the same
960 * JST attributes.)
961 * @type Object
962 */
963 JstProcessor.jstcacheattributes_ = {};
964
965
966 /**
967 * Map for storing temporary attribute values in prepareNode_() so they don't
968 * have to be retrieved twice. (IE6 perf)
969 * @type Object
970 */
971 JstProcessor.attributeValues_ = {};
972
973
974 /**
975 * A list for storing non-empty attributes found on a node in prepareNode_().
976 * The array is global since it can be reused - this way there is no need to
977 * construct a new array object for each invocation. (IE6 perf)
978 * @type Array
979 */
980 JstProcessor.attributeList_ = [];
981
982
983 /**
984 * Prepares the template: preprocesses all jstemplate attributes.
985 *
986 * @param {Element} template
987 */
988 JstProcessor.prepareTemplate_ = function(template) {
989 if (!template[PROP_jstcache]) {
990 domTraverseElements(template, function(node) {
991 JstProcessor.prepareNode_(node);
992 });
993 }
994 };
995
996
997 /**
998 * A list of attributes we use to specify jst processing instructions,
999 * and the functions used to parse their values.
1000 *
1001 * @type Array.<Array>
1002 */
1003 var JST_ATTRIBUTES = [
1004 [ ATT_select, jsEvalToFunction ],
1005 [ ATT_display, jsEvalToFunction ],
1006 [ ATT_values, jsEvalToValues ],
1007 [ ATT_vars, jsEvalToValues ],
1008 [ ATT_eval, jsEvalToExpressions ],
1009 [ ATT_transclude, jsEvalToSelf ],
1010 [ ATT_content, jsEvalToFunction ],
1011 [ ATT_skip, jsEvalToFunction ]
1012 ];
1013
1014
1015 /**
1016 * Prepares a single node: preprocesses all template attributes of the
1017 * node, and if there are any, assigns a jsid attribute and stores the
1018 * preprocessed attributes under the jsid in the jstcache.
1019 *
1020 * @param {Element} node
1021 *
1022 * @return {Object} The jstcache entry. The processed jst attributes
1023 * are properties of this object. If the node has no jst attributes,
1024 * returns an object with no properties (the jscache_[0] entry).
1025 */
1026 JstProcessor.prepareNode_ = function(node) {
1027 if (node[PROP_jstcache]) {
1028 return node[PROP_jstcache];
1029 }
1030
1031
1032 var jstid = domGetAttribute(node, ATT_jstcache);
1033 if (jstid != null) {
1034 return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];
1035 }
1036
1037 var attributeValues = JstProcessor.attributeValues_;
1038 var attributeList = JstProcessor.attributeList_;
1039 attributeList.length = 0;
1040
1041 for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {
1042 var name = JST_ATTRIBUTES[i][0];
1043 var value = domGetAttribute(node, name);
1044 attributeValues[name] = value;
1045 if (value != null) {
1046 attributeList.push(name + "=" + value);
1047 }
1048 }
1049
1050 if (attributeList.length == 0) {
1051 domSetAttribute(node, ATT_jstcache, STRING_zero);
1052 return node[PROP_jstcache] = JstProcessor.jstcache_[0];
1053 }
1054
1055 var attstring = attributeList.join(CHAR_ampersand);
1056 if (jstid = JstProcessor.jstcacheattributes_[attstring]) {
1057 domSetAttribute(node, ATT_jstcache, jstid);
1058 return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];
1059 }
1060
1061 var jstcache = {};
1062 for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {
1063 var att = JST_ATTRIBUTES[i];
1064 var name = att[0];
1065 var parse = att[1];
1066 var value = attributeValues[name];
1067 if (value != null) {
1068 jstcache[name] = parse(value);
1069 }
1070 }
1071
1072 jstid = STRING_empty + ++JstProcessor.jstid_;
1073 domSetAttribute(node, ATT_jstcache, jstid);
1074 JstProcessor.jstcache_[jstid] = jstcache;
1075 JstProcessor.jstcacheattributes_[attstring] = jstid;
1076
1077 return node[PROP_jstcache] = jstcache;
1078 };
1079
1080
1081 /**
1082 * Runs the given function in our state machine.
1083 *
1084 * It's informative to view the set of all function calls as a tree:
1085 * - nodes are states
1086 * - edges are state transitions, implemented as calls to the pending
1087 * functions in the stack.
1088 * - pre-order function calls are downward edges (recursion into call).
1089 * - post-order function calls are upward edges (return from call).
1090 * - leaves are nodes which do not recurse.
1091 * We represent the call tree as an array of array of calls, indexed as
1092 * stack[depth][index]. Here [depth] indexes into the call stack, and
1093 * [index] indexes into the call queue at that depth. We require a call
1094 * queue so that a node may branch to more than one child
1095 * (which will be called serially), typically due to a loop structure.
1096 *
1097 * @param {Function} f The first function to run.
1098 */
1099 JstProcessor.prototype.run_ = function(f) {
856 var me = this; 1100 var me = this;
857 1101
858 me.queue_ = [ start ]; 1102 /**
859 while (jsLength(me.queue_)) { 1103 * A stack of queues of pre-order calls.
860 var f = me.queue_.shift(); 1104 * The inner arrays (constituent queues) are structured as
861 f[1].apply(f[0], f.slice(2)); 1105 * [ arg2, arg1, method, arg2, arg1, method, ...]
862 } 1106 * ie. a flattened array of methods with 2 arguments, in reverse order
863 } 1107 * for efficient push/pop.
864 1108 *
865 1109 * The outer array is a stack of such queues.
866 /** 1110 *
867 * Appends a function to be called later. 1111 * @type Array.<Array>
868 * Analogous to calling that function on a subsequent line, or a subsequent 1112 */
869 * iteration of a loop. 1113 var calls = me.calls_ = [];
870 * 1114
871 * @param {Array} f A function in the form [object, method, args ...] 1115 /**
872 */ 1116 * The index into the queue for each depth. NOTE: Alternative would
873 JstProcessor.prototype.enqueue_ = function(f) { 1117 * be to maintain the queues in reverse order (popping off of the
874 this.queue_.push(f); 1118 * end) but the repeated calls to .pop() consumed 90% of this
875 } 1119 * function's execution time.
876 1120 * @type Array.<number>
877 1121 */
878 /** 1122 var queueIndices = me.queueIndices_ = [];
879 * Implements internals of jstProcess. 1123
880 * 1124 /**
881 * @param {JsExprContext} context 1125 * A pool of empty arrays. Minimizes object allocation for IE6's benefit.
1126 * @type Array.<Array>
1127 */
1128 var arrayPool = me.arrayPool_ = [];
1129
1130 f();
1131 var queue, queueIndex;
1132 var method, arg1, arg2;
1133 var temp;
1134 while (calls.length) {
1135 queue = calls[calls.length - 1];
1136 queueIndex = queueIndices[queueIndices.length - 1];
1137 if (queueIndex >= queue.length) {
1138 me.recycleArray_(calls.pop());
1139 queueIndices.pop();
1140 continue;
1141 }
1142
1143 method = queue[queueIndex++];
1144 arg1 = queue[queueIndex++];
1145 arg2 = queue[queueIndex++];
1146 queueIndices[queueIndices.length - 1] = queueIndex;
1147 method.call(me, arg1, arg2);
1148 }
1149 };
1150
1151
1152 /**
1153 * Pushes one or more functions onto the stack. These will be run in sequence,
1154 * interspersed with any recursive calls that they make.
1155 *
1156 * This method takes ownership of the given array!
1157 *
1158 * @param {Array} args Array of method calls structured as
1159 * [ method, arg1, arg2, method, arg1, arg2, ... ]
1160 */
1161 JstProcessor.prototype.push_ = function(args) {
1162 this.calls_.push(args);
1163 this.queueIndices_.push(0);
1164 };
1165
1166
1167 /**
1168 * Enable/disable debugging.
1169 * @param {boolean} debugging New state
1170 */
1171 JstProcessor.prototype.setDebugging = function(debugging) {
1172 };
1173
1174
1175 JstProcessor.prototype.createArray_ = function() {
1176 if (this.arrayPool_.length) {
1177 return this.arrayPool_.pop();
1178 } else {
1179 return [];
1180 }
1181 };
1182
1183
1184 JstProcessor.prototype.recycleArray_ = function(array) {
1185 arrayClear(array);
1186 this.arrayPool_.push(array);
1187 };
1188
1189 /**
1190 * Implements internals of jstProcess. This processes the two
1191 * attributes transclude and jsselect, which replace or multiply
1192 * elements, hence the name "outer". The remainder of the attributes
1193 * is processed in jstProcessInner_(), below. That function
1194 * jsProcessInner_() only processes attributes that affect an existing
1195 * node, but doesn't create or destroy nodes, hence the name
1196 * "inner". jstProcessInner_() is called through jstSelect_() if there
1197 * is a jsselect attribute (possibly for newly created clones of the
1198 * current template node), or directly from here if there is none.
1199 *
1200 * @param {JsEvalContext} context
882 * 1201 *
883 * @param {Element} template 1202 * @param {Element} template
884 */ 1203 */
885 JstProcessor.prototype.jstProcess_ = function(context, template) { 1204 JstProcessor.prototype.jstProcessOuter_ = function(context, template) {
886 var me = this; 1205 var me = this;
887 1206
888 var transclude = domGetAttribute(template, ATT_transclude); 1207 var jstAttributes = me.jstAttributes_(template);
1208
1209 var transclude = jstAttributes[ATT_transclude];
889 if (transclude) { 1210 if (transclude) {
890 var tr = context.getSubTemplate(transclude); 1211 var tr = jstGetTemplate(transclude);
891 if (tr) { 1212 if (tr) {
892 domReplaceChild(tr, template); 1213 domReplaceChild(tr, template);
893 me.enqueue_([ me, me.jstProcess_, context, tr ]); 1214 var call = me.createArray_();
1215 call.push(me.jstProcessOuter_, context, tr);
1216 me.push_(call);
894 } else { 1217 } else {
895 domRemoveNode(template); 1218 domRemoveNode(template);
896 } 1219 }
897 return; 1220 return;
898 } 1221 }
899 1222
900 var select = domGetAttribute(template, ATT_select); 1223 var select = jstAttributes[ATT_select];
901 if (select) { 1224 if (select) {
902 me.jstSelect_(context, template, select); 1225 me.jstSelect_(context, template, select);
903 return; 1226 } else {
904 } 1227 me.jstProcessInner_(context, template);
905 1228 }
906 var display = domGetAttribute(template, ATT_display); 1229 };
1230
1231
1232 /**
1233 * Implements internals of jstProcess. This processes all attributes
1234 * except transclude and jsselect. It is called either from
1235 * jstSelect_() for nodes that have a jsselect attribute so that the
1236 * jsselect attribute will not be processed again, or else directly
1237 * from jstProcessOuter_(). See the comment on jstProcessOuter_() for
1238 * an explanation of the name.
1239 *
1240 * @param {JsEvalContext} context
1241 *
1242 * @param {Element} template
1243 */
1244 JstProcessor.prototype.jstProcessInner_ = function(context, template) {
1245 var me = this;
1246
1247 var jstAttributes = me.jstAttributes_(template);
1248
1249 var display = jstAttributes[ATT_display];
907 if (display) { 1250 if (display) {
908 if (!context.jseval(display, template)) { 1251 var shouldDisplay = context.jsexec(display, template);
1252 if (!shouldDisplay) {
909 displayNone(template); 1253 displayNone(template);
910 return; 1254 return;
911 } 1255 }
912
913 displayDefault(template); 1256 displayDefault(template);
914 } 1257 }
915 1258
916 1259 var values = jstAttributes[ATT_vars];
917 var values = domGetAttribute(template, ATT_values); 1260 if (values) {
1261 me.jstVars_(context, template, values);
1262 }
1263
1264 values = jstAttributes[ATT_values];
918 if (values) { 1265 if (values) {
919 me.jstValues_(context, template, values); 1266 me.jstValues_(context, template, values);
920 } 1267 }
921 1268
922 var expressions = domGetAttribute(template, ATT_eval); 1269 var expressions = jstAttributes[ATT_eval];
923 if (expressions) { 1270 if (expressions) {
924 foreach(expressions.split(/\s*;\s*/), function(expression) { 1271 for (var i = 0, I = jsLength(expressions); i < I; ++i) {
925 expression = stringTrim(expression); 1272 context.jsexec(expressions[i], template);
926 if (jsLength(expression)) { 1273 }
927 context.jseval(expression, template); 1274 }
928 } 1275
929 }); 1276 var skip = jstAttributes[ATT_skip];
930 } 1277 if (skip) {
931 1278 var shouldSkip = context.jsexec(skip, template);
932 var content = domGetAttribute(template, ATT_content); 1279 if (shouldSkip) return;
1280 }
1281
1282 var content = jstAttributes[ATT_content];
933 if (content) { 1283 if (content) {
934 me.jstContent_(context, template, content); 1284 me.jstContent_(context, template, content);
935 1285
936 } else { 1286 } else {
937 var childnodes = []; 1287 var queue = me.createArray_();
938 for (var i = 0; i < jsLength(template.childNodes); ++i) { 1288 for (var c = template.firstChild; c; c = c.nextSibling) {
939 if (template.childNodes[i].nodeType == DOM_ELEMENT_NODE) { 1289 if (c.nodeType == DOM_ELEMENT_NODE) {
940 me.enqueue_( 1290 queue.push(me.jstProcessOuter_, context, c);
941 [ me, me.jstProcess_, context, template.childNodes[i] ]);
942 } 1291 }
943 } 1292 }
944 } 1293 if (queue.length) me.push_(queue);
945 } 1294 }
1295 };
946 1296
947 1297
948 /** 1298 /**
949 * Implements the jsselect attribute: evalutes the value of the 1299 * Implements the jsselect attribute: evalutes the value of the
950 * jsselect attribute in the current context, with the current 1300 * jsselect attribute in the current context, with the current
951 * variable bindings (see JsExprContext.jseval()). If the value is an 1301 * variable bindings (see JsEvalContext.jseval()). If the value is an
952 * array, the current template node is multiplied once for every 1302 * array, the current template node is multiplied once for every
953 * element in the array, with the array element being the context 1303 * element in the array, with the array element being the context
954 * object. If the array is empty, or the value is undefined, then the 1304 * object. If the array is empty, or the value is undefined, then the
955 * current template node is dropped. If the value is not an array, 1305 * current template node is dropped. If the value is not an array,
956 * then it is just made the context object. 1306 * then it is just made the context object.
957 * 1307 *
958 * @param {JsExprContext} context The current evaluation context. 1308 * @param {JsEvalContext} context The current evaluation context.
959 * 1309 *
960 * @param {Element} template The currently processed node of the template. 1310 * @param {Element} template The currently processed node of the template.
961 * 1311 *
962 * @param {String} select The javascript expression to evaluate. 1312 * @param {Function} select The javascript expression to evaluate.
963 * 1313 *
964 * @param {Function} process The function to continue processing with. 1314 * @notypecheck FIXME(hmitchell): See OCL6434950. instance and value need
1315 * type checks.
965 */ 1316 */
966 JstProcessor.prototype.jstSelect_ = function(context, template, select) { 1317 JstProcessor.prototype.jstSelect_ = function(context, template, select) {
967 var me = this; 1318 var me = this;
968 1319
969 var value = context.jseval(select, template); 1320 var value = context.jsexec(select, template);
970 domRemoveAttribute(template, ATT_select);
971 1321
972 var instance = domGetAttribute(template, ATT_instance); 1322 var instance = domGetAttribute(template, ATT_instance);
973 var instance_last = false; 1323
1324 var instanceLast = false;
974 if (instance) { 1325 if (instance) {
975 if (instance.charAt(0) == '*') { 1326 if (instance.charAt(0) == CHAR_asterisk) {
976 instance = parseInt10(instance.substr(1)); 1327 instance = parseInt10(instance.substr(1));
977 instance_last = true; 1328 instanceLast = true;
978 } else { 1329 } else {
979 instance = parseInt10(instance); 1330 instance = parseInt10(/** @type string */(instance));
980 } 1331 }
981 } 1332 }
982 1333
983 var multiple = (value !== null && 1334 var multiple = isArray(value);
984 typeof value == 'object' && 1335 var count = multiple ? jsLength(value) : 1;
985 typeof value.length == 'number'); 1336 var multipleEmpty = (multiple && count == 0);
986 var multiple_empty = (multiple && value.length == 0);
987 1337
988 if (multiple) { 1338 if (multiple) {
989 if (multiple_empty) { 1339 if (multipleEmpty) {
990 if (!instance) { 1340 if (!instance) {
991 domSetAttribute(template, ATT_select, select); 1341 domSetAttribute(template, ATT_instance, STRING_asteriskzero);
992 domSetAttribute(template, ATT_instance, '*0');
993 displayNone(template); 1342 displayNone(template);
994 } else { 1343 } else {
995 domRemoveNode(template); 1344 domRemoveNode(template);
996 } 1345 }
997 1346
998 } else { 1347 } else {
999 displayDefault(template); 1348 displayDefault(template);
1000 if (instance === null || instance === "" || instance === undefined || 1349 if (instance === null || instance === STRING_empty ||
1001 (instance_last && instance < jsLength(value) - 1)) { 1350 (instanceLast && instance < count - 1)) {
1002 var templatenodes = []; 1351 var queue = me.createArray_();
1003 var instances_start = instance || 0; 1352
1004 for (var i = instances_start + 1; i < jsLength(value); ++i) { 1353 var instancesStart = instance || 0;
1354 var i, I, clone;
1355 for (i = instancesStart, I = count - 1; i < I; ++i) {
1005 var node = domCloneNode(template); 1356 var node = domCloneNode(template);
1006 templatenodes.push(node);
1007 domInsertBefore(node, template); 1357 domInsertBefore(node, template);
1358
1359 jstSetInstance(/** @type Element */(node), value, i);
1360 clone = context.clone(value[i], i, count);
1361
1362 queue.push(me.jstProcessInner_, clone, node,
1363 JsEvalContext.recycle, clone, null);
1364
1008 } 1365 }
1009 templatenodes.push(template); 1366 jstSetInstance(template, value, i);
1010 1367 clone = context.clone(value[i], i, count);
1011 for (var i = 0; i < jsLength(templatenodes); ++i) { 1368 queue.push(me.jstProcessInner_, clone, template,
1012 var ii = i + instances_start; 1369 JsEvalContext.recycle, clone, null);
1013 var v = value[ii]; 1370 me.push_(queue);
1014 var t = templatenodes[i]; 1371 } else if (instance < count) {
1015
1016 me.enqueue_([ me, me.jstProcess_, context.clone(v, ii), t ]);
1017 var instanceStr = (ii == jsLength(value) - 1 ? '*' : '') + ii;
1018 me.enqueue_(
1019 [ null, postProcessMultiple_, t, select, instanceStr ]);
1020 }
1021
1022 } else if (instance < jsLength(value)) {
1023 var v = value[instance]; 1372 var v = value[instance];
1024 1373
1025 me.enqueue_( 1374 jstSetInstance(template, value, instance);
1026 [me, me.jstProcess_, context.clone(v, instance), template]); 1375 var clone = context.clone(v, instance, count);
1027 var instanceStr = (instance == jsLength(value) - 1 ? '*' : '') 1376 var queue = me.createArray_();
1028 + instance; 1377 queue.push(me.jstProcessInner_, clone, template,
1029 me.enqueue_( 1378 JsEvalContext.recycle, clone, null);
1030 [ null, postProcessMultiple_, template, select, instanceStr ]); 1379 me.push_(queue);
1031 } else { 1380 } else {
1032 domRemoveNode(template); 1381 domRemoveNode(template);
1033 } 1382 }
1034 } 1383 }
1035 } else { 1384 } else {
1036 if (value == null) { 1385 if (value == null) {
1037 domSetAttribute(template, ATT_select, select);
1038 displayNone(template); 1386 displayNone(template);
1039 } else { 1387 } else {
1040 me.enqueue_( 1388 displayDefault(template);
1041 [ me, me.jstProcess_, context.clone(value, 0), template ]); 1389 var clone = context.clone(value, 0, 1);
1042 me.enqueue_( 1390 var queue = me.createArray_();
1043 [ null, postProcessSingle_, template, select ]); 1391 queue.push(me.jstProcessInner_, clone, template,
1392 JsEvalContext.recycle, clone, null);
1393 me.push_(queue);
1044 } 1394 }
1045 } 1395 }
1046 } 1396 };
1047 1397
1048 1398
1049 /** 1399 /**
1050 * Sets ATT_select and ATT_instance following recursion to jstProcess. 1400 * Implements the jsvars attribute: evaluates each of the values and
1401 * assigns them to variables in the current context. Similar to
1402 * jsvalues, except that all values are treated as vars, independent
1403 * of their names.
1051 * 1404 *
1052 * @param {Element} template The template 1405 * @param {JsEvalContext} context Current evaluation context.
1053 * 1406 *
1054 * @param {String} select The jsselect string 1407 * @param {Element} template Currently processed template node.
1055 * 1408 *
1056 * @param {String} instanceStr The new value for the jsinstance attribute 1409 * @param {Array} values Processed value of the jsvalues attribute: a
1410 * flattened array of pairs. The second element in the pair is a
1411 * function that can be passed to jsexec() for evaluation in the
1412 * current jscontext, and the first element is the variable name that
1413 * the value returned by jsexec is assigned to.
1057 */ 1414 */
1058 function postProcessMultiple_(template, select, instanceStr) { 1415 JstProcessor.prototype.jstVars_ = function(context, template, values) {
1059 domSetAttribute(template, ATT_select, select); 1416 for (var i = 0, I = jsLength(values); i < I; i += 2) {
1060 domSetAttribute(template, ATT_instance, instanceStr); 1417 var label = values[i];
1061 } 1418 var value = context.jsexec(values[i+1], template);
1062 1419 context.setVariable(label, value);
1063 1420 }
1064 /** 1421 };
1065 * Sets ATT_select and makes the element visible following recursion to
1066 * jstProcess.
1067 *
1068 * @param {Element} template The template
1069 *
1070 * @param {String} select The jsselect string
1071 */
1072 function postProcessSingle_(template, select) {
1073 domSetAttribute(template, ATT_select, select);
1074 displayDefault(template);
1075 }
1076 1422
1077 1423
1078 /** 1424 /**
1079 * Implements the jsvalues attribute: evaluates each of the values and 1425 * Implements the jsvalues attribute: evaluates each of the values and
1080 * assigns them to variables in the current context (if the name 1426 * assigns them to variables in the current context (if the name
1081 * starts with '$', javascript properties of the current template node 1427 * starts with '$', javascript properties of the current template node
1082 * (if the name starts with '.'), or DOM attributes of the current 1428 * (if the name starts with '.'), or DOM attributes of the current
1083 * template node (otherwise). Since DOM attribute values are always 1429 * template node (otherwise). Since DOM attribute values are always
1084 * strings, the value is coerced to string in the latter case, 1430 * strings, the value is coerced to string in the latter case,
1085 * otherwise it's the uncoerced javascript value. 1431 * otherwise it's the uncoerced javascript value.
1086 * 1432 *
1087 * @param {JsExprContext} context Current evaluation context. 1433 * @param {JsEvalContext} context Current evaluation context.
1088 * 1434 *
1089 * @param {Element} template Currently processed template node. 1435 * @param {Element} template Currently processed template node.
1090 * 1436 *
1091 * @param {String} valuesStr Value of the jsvalues attribute to be 1437 * @param {Array} values Processed value of the jsvalues attribute: a
1092 * processed. 1438 * flattened array of pairs. The second element in the pair is a
1439 * function that can be passed to jsexec() for evaluation in the
1440 * current jscontext, and the first element is the label that
1441 * determines where the value returned by jsexec is assigned to.
1093 */ 1442 */
1094 JstProcessor.prototype.jstValues_ = function(context, template, valuesStr) { 1443 JstProcessor.prototype.jstValues_ = function(context, template, values) {
1095 var values = valuesStr.split(/\s*;\s*/); 1444 for (var i = 0, I = jsLength(values); i < I; i += 2) {
1096 for (var i = 0; i < jsLength(values); ++i) { 1445 var label = values[i];
1097 var colon = values[i].indexOf(':'); 1446 var value = context.jsexec(values[i+1], template);
1098 if (colon < 0) {
1099 continue;
1100 }
1101 var label = stringTrim(values[i].substr(0, colon));
1102 var value = context.jseval(values[i].substr(colon + 1), template);
1103 1447
1104 if (label.charAt(0) == '$') { 1448 if (label.charAt(0) == CHAR_dollar) {
1105 context.setVariable(label, value); 1449 context.setVariable(label, value);
1106 1450
1107 } else if (label.charAt(0) == '.') { 1451 } else if (label.charAt(0) == CHAR_period) {
1108 var nameSpaceLabel = label.substr(1).split('.'); 1452 var nameSpaceLabel = label.substr(1).split(CHAR_period);
1109 var nameSpaceObject = template; 1453 var nameSpaceObject = template;
1110 var nameSpaceDepth = jsLength(nameSpaceLabel); 1454 var nameSpaceDepth = jsLength(nameSpaceLabel);
1111 for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) { 1455 for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) {
1112 var jLabel = nameSpaceLabel[j]; 1456 var jLabel = nameSpaceLabel[j];
1113 if (!nameSpaceObject[jLabel]) { 1457 if (!nameSpaceObject[jLabel]) {
1114 nameSpaceObject[jLabel] = {}; 1458 nameSpaceObject[jLabel] = {};
1115 } 1459 }
1116 nameSpaceObject = nameSpaceObject[jLabel]; 1460 nameSpaceObject = nameSpaceObject[jLabel];
1117 } 1461 }
1118 nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value; 1462 nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value;
1463
1119 } else if (label) { 1464 } else if (label) {
1120 if (typeof value == 'boolean') { 1465 if (typeof value == TYPE_boolean) {
1121 if (value) { 1466 if (value) {
1122 domSetAttribute(template, label, label); 1467 domSetAttribute(template, label, label);
1123 } else { 1468 } else {
1124 domRemoveAttribute(template, label); 1469 domRemoveAttribute(template, label);
1125 } 1470 }
1126 } else { 1471 } else {
1127 domSetAttribute(template, label, '' + value); 1472 domSetAttribute(template, label, STRING_empty + value);
1128 } 1473 }
1129 } 1474 }
1130 } 1475 }
1131 } 1476 };
1132 1477
1133 1478
1134 /** 1479 /**
1135 * Implements the jscontent attribute. Evalutes the expression in 1480 * Implements the jscontent attribute. Evalutes the expression in
1136 * jscontent in the current context and with the current variables, 1481 * jscontent in the current context and with the current variables,
1137 * and assigns its string value to the content of the current template 1482 * and assigns its string value to the content of the current template
1138 * node. 1483 * node.
1139 * 1484 *
1140 * @param {JsExprContext} context Current evaluation context. 1485 * @param {JsEvalContext} context Current evaluation context.
1141 * 1486 *
1142 * @param {Element} template Currently processed template node. 1487 * @param {Element} template Currently processed template node.
1143 * 1488 *
1144 * @param {String} content Value of the jscontent attribute to be 1489 * @param {Function} content Processed value of the jscontent
1145 * processed. 1490 * attribute.
1146 */ 1491 */
1147 JstProcessor.prototype.jstContent_ = function(context, template, content) { 1492 JstProcessor.prototype.jstContent_ = function(context, template, content) {
1148 var value = '' + context.jseval(content, template); 1493 var value = STRING_empty + context.jsexec(content, template);
1149 if (template.innerHTML == value) { 1494 if (template.innerHTML == value) {
1150 return; 1495 return;
1151 } 1496 }
1152 while (template.firstChild) { 1497 while (template.firstChild) {
1153 domRemoveNode(template.firstChild); 1498 domRemoveNode(template.firstChild);
1154 } 1499 }
1155 var t = domCreateTextNode(ownerDocument(template), value); 1500 var t = domCreateTextNode(this.document_, value);
1156 domAppendChild(template, t); 1501 domAppendChild(template, t);
1157 } 1502 };
1503
1504
1505 /**
1506 * Caches access to and parsing of template processing attributes. If
1507 * domGetAttribute() is called every time a template attribute value
1508 * is used, it takes more than 10% of the time.
1509 *
1510 * @param {Element} template A DOM element node of the template.
1511 *
1512 * @return {Object} A javascript object that has all js template
1513 * processing attribute values of the node as properties.
1514 */
1515 JstProcessor.prototype.jstAttributes_ = function(template) {
1516 if (template[PROP_jstcache]) {
1517 return template[PROP_jstcache];
1518 }
1519
1520 var jstid = domGetAttribute(template, ATT_jstcache);
1521 if (jstid) {
1522 return template[PROP_jstcache] = JstProcessor.jstcache_[jstid];
1523 }
1524
1525 return JstProcessor.prepareNode_(template);
1526 };
1158 1527
1159 1528
1160 /** 1529 /**
1161 * Helps to implement the transclude attribute, and is the initial 1530 * Helps to implement the transclude attribute, and is the initial
1162 * call to get hold of a template from its ID. 1531 * call to get hold of a template from its ID.
1163 * 1532 *
1164 * @param {String} name The ID of the HTML element used as template. 1533 * If the ID is not present in the DOM, and opt_loadHtmlFn is specified, this
1165 * 1534 * function will call that function and add the result to the DOM, before
1166 * @returns {Element} The DOM node of the template. (Only element 1535 * returning the template.
1167 * nodes can be found by ID, hence it's a Element.) 1536 *
1168 */ 1537 * @param {string} name The ID of the HTML element used as template.
1169 function jstGetTemplate(name) { 1538 * @param {Function} opt_loadHtmlFn A function which, when called, will return
1170 var section = domGetElementById(document, name); 1539 * HTML that contains an element whose ID is 'name'.
1540 *
1541 * @return {Element|null} The DOM node of the template. (Only element nodes
1542 * can be found by ID, hence it's a Element.)
1543 */
1544 function jstGetTemplate(name, opt_loadHtmlFn) {
1545 var doc = document;
1546 var section;
1547 if (opt_loadHtmlFn) {
1548 section = jstLoadTemplateIfNotPresent(doc, name, opt_loadHtmlFn);
1549 } else {
1550 section = domGetElementById(doc, name);
1551 }
1171 if (section) { 1552 if (section) {
1172 var ret = domCloneNode(section); 1553 JstProcessor.prepareTemplate_(section);
1173 domRemoveAttribute(ret, 'id'); 1554 var ret = domCloneElement(section);
1555 domRemoveAttribute(ret, STRING_id);
1174 return ret; 1556 return ret;
1175 } else { 1557 } else {
1176 return null; 1558 return null;
1177 } 1559 }
1178 } 1560 }
1179 1561
1562 /**
1563 * This function is the same as 'jstGetTemplate' but, if the template
1564 * does not exist, throw an exception.
1565 *
1566 * @param {string} name The ID of the HTML element used as template.
1567 * @param {Function} opt_loadHtmlFn A function which, when called, will return
1568 * HTML that contains an element whose ID is 'name'.
1569 *
1570 * @return {Element} The DOM node of the template. (Only element nodes
1571 * can be found by ID, hence it's a Element.)
1572 */
1573 function jstGetTemplateOrDie(name, opt_loadHtmlFn) {
1574 var x = jstGetTemplate(name, opt_loadHtmlFn);
1575 check(x !== null);
1576 return /** @type Element */(x);
1577 }
1578
1579
1580 /**
1581 * If an element with id 'name' is not present in the document, call loadHtmlFn
1582 * and insert the result into the DOM.
1583 *
1584 * @param {Document} doc
1585 * @param {string} name
1586 * @param {Function} loadHtmlFn A function that returns HTML to be inserted
1587 * into the DOM.
1588 * @param {string} opt_target The id of a DOM object under which to attach the
1589 * HTML once it's inserted. An object with this id is created if it does not
1590 * exist.
1591 * @return {Element} The node whose id is 'name'
1592 */
1593 function jstLoadTemplateIfNotPresent(doc, name, loadHtmlFn, opt_target) {
1594 var section = domGetElementById(doc, name);
1595 if (section) {
1596 return section;
1597 }
1598 jstLoadTemplate_(doc, loadHtmlFn(), opt_target || STRING_jsts);
1599 var section = domGetElementById(doc, name);
1600 if (!section) {
1601 log("Error: jstGetTemplate was provided with opt_loadHtmlFn, " +
1602 "but that function did not provide the id '" + name + "'.");
1603 }
1604 return /** @type Element */(section);
1605 }
1606
1607
1608 /**
1609 * Loads the given HTML text into the given document, so that
1610 * jstGetTemplate can find it.
1611 *
1612 * We append it to the element identified by targetId, which is hidden.
1613 * If it doesn't exist, it is created.
1614 *
1615 * @param {Document} doc The document to create the template in.
1616 *
1617 * @param {string} html HTML text to be inserted into the document.
1618 *
1619 * @param {string} targetId The id of a DOM object under which to attach the
1620 * HTML once it's inserted. An object with this id is created if it does not
1621 * exist.
1622 */
1623 function jstLoadTemplate_(doc, html, targetId) {
1624 var existing_target = domGetElementById(doc, targetId);
1625 var target;
1626 if (!existing_target) {
1627 target = domCreateElement(doc, STRING_div);
1628 target.id = targetId;
1629 displayNone(target);
1630 positionAbsolute(target);
1631 domAppendChild(doc.body, target);
1632 } else {
1633 target = existing_target;
1634 }
1635 var div = domCreateElement(doc, STRING_div);
1636 target.appendChild(div);
1637 div.innerHTML = html;
1638 }
1639
1640
1641 /**
1642 * Sets the jsinstance attribute on a node according to its context.
1643 *
1644 * @param {Element} template The template DOM node to set the instance
1645 * attribute on.
1646 *
1647 * @param {Array} values The current input context, the array of
1648 * values of which the template node will render one instance.
1649 *
1650 * @param {number} index The index of this template node in values.
1651 */
1652 function jstSetInstance(template, values, index) {
1653 if (index == jsLength(values) - 1) {
1654 domSetAttribute(template, ATT_instance, CHAR_asterisk + index);
1655 } else {
1656 domSetAttribute(template, ATT_instance, STRING_empty + index);
1657 }
1658 }
1659
1660
1661 /**
1662 * Log the current state.
1663 * @param {string} caller An identifier for the caller of .log_.
1664 * @param {Element} template The template node being processed.
1665 * @param {Object} jstAttributeValues The jst attributes of the template node.
1666 */
1667 JstProcessor.prototype.logState_ = function(
1668 caller, template, jstAttributeValues) {
1669 };
1670
1671
1672 /**
1673 * Retrieve the processing logs.
1674 * @return {Array.<string>} The processing logs.
1675 */
1676 JstProcessor.prototype.getLogs = function() {
1677 return this.logs_;
1678 };
1180 window['jstGetTemplate'] = jstGetTemplate; 1679 window['jstGetTemplate'] = jstGetTemplate;
1680 window['JsEvalContext'] = JsEvalContext;
1181 window['jstProcess'] = jstProcess; 1681 window['jstProcess'] = jstProcess;
1182 window['JsExprContext'] = JsExprContext; 1682 })()
OLDNEW
« no previous file with comments | « chrome/third_party/jstemplate/jstemplate.js ('k') | chrome/third_party/jstemplate/jstemplate_example.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698