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

Side by Side Diff: chrome/third_party/jstemplate/jstemplate.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 // Copyright 2006 Google Inc. All rights reserved. 1 // Copyright 2006 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 // implied. See the License for the specific language governing
13 // permissions and limitations under the License.
2 /** 14 /**
15 * Author: Steffen Meschkat <mesch@google.com>
16 *
3 * @fileoverview A simple formatter to project JavaScript data into 17 * @fileoverview A simple formatter to project JavaScript data into
4 * HTML templates. The template is edited in place. I.e. in order to 18 * HTML templates. The template is edited in place. I.e. in order to
5 * instantiate a template, clone it from the DOM first, and then 19 * instantiate a template, clone it from the DOM first, and then
6 * process the cloned template. This allows for updating of templates: 20 * process the cloned template. This allows for updating of templates:
7 * If the templates is processed again, changed values are merely 21 * If the templates is processed again, changed values are merely
8 * updated. 22 * updated.
9 * 23 *
10 * NOTE: IE DOM doesn't have importNode(). 24 * NOTE(mesch): IE DOM doesn't have importNode().
11 * 25 *
12 * NOTE: The property name "length" must not be used in input 26 * NOTE(mesch): The property name "length" must not be used in input
13 * data, see comment in jstSelect_(). 27 * data, see comment in jstSelect_().
14 */ 28 */
15 29
16 30
17 /** 31 /**
18 * Names of jstemplate attributes. These attributes are attached to 32 * Names of jstemplate attributes. These attributes are attached to
19 * normal HTML elements and bind expression context data to the HTML 33 * normal HTML elements and bind expression context data to the HTML
20 * fragment that is used as template. 34 * fragment that is used as template.
21 */ 35 */
22 var ATT_select = 'jsselect'; 36 var ATT_select = 'jsselect';
23 var ATT_instance = 'jsinstance'; 37 var ATT_instance = 'jsinstance';
24 var ATT_display = 'jsdisplay'; 38 var ATT_display = 'jsdisplay';
25 var ATT_values = 'jsvalues'; 39 var ATT_values = 'jsvalues';
40 var ATT_vars = 'jsvars';
26 var ATT_eval = 'jseval'; 41 var ATT_eval = 'jseval';
27 var ATT_transclude = 'transclude'; 42 var ATT_transclude = 'transclude';
28 var ATT_content = 'jscontent'; 43 var ATT_content = 'jscontent';
29 44 var ATT_skip = 'jsskip';
30 45
31 /** 46
32 * Names of special variables defined by the jstemplate evaluation 47 /**
33 * context. These can be used in js expression in jstemplate 48 * Name of the attribute that caches a reference to the parsed
34 * attributes. 49 * template processing attribute values on a template node.
35 */ 50 */
36 var VAR_index = '$index'; 51 var ATT_jstcache = 'jstcache';
37 var VAR_this = '$this'; 52
38 53
39 54 /**
40 /** 55 * Name of the property that caches the parsed template processing
41 * Context for processing a jstemplate. The context contains a context 56 * attribute values on a template node.
42 * object, whose properties can be referred to in jstemplate 57 */
43 * expressions, and it holds the locally defined variables. 58 var PROP_jstcache = '__jstcache';
44 * 59
45 * @param {Object} opt_data The context object. Null if no context. 60
46 * 61 /**
47 * @param {Object} opt_parent The parent context, from which local 62 * ID of the element that contains dynamically loaded jstemplates.
48 * variables are inherited. Normally the context object of the parent 63 */
49 * context is the object whose property the parent object is. Null for the 64 var STRING_jsts = 'jsts';
50 * context of the root object. 65
51 * 66
52 * @constructor 67 /**
53 */ 68 * Un-inlined string literals, to avoid object creation in
54 function JsExprContext(opt_data, opt_parent) { 69 * IE6.
55 var me = this; 70 */
56 71 var CHAR_asterisk = '*';
57 /** 72 var CHAR_dollar = '$';
58 * The local context of the input data in which the jstemplate 73 var CHAR_period = '.';
59 * expressions are evaluated. Notice that this is usually an Object, 74 var CHAR_ampersand = '&';
60 * but it can also be a scalar value (and then still the expression 75 var STRING_div = 'div';
61 * $this can be used to refer to it). Notice this can be a scalar 76 var STRING_id = 'id';
62 * value, including undefined. 77 var STRING_asteriskzero = '*0';
63 * 78 var STRING_zero = '0';
64 * @type {Object}
65 */
66 me.data_ = opt_data;
67
68 /**
69 * The context for variable definitions in which the jstemplate
70 * expressions are evaluated. Other than for the local context,
71 * which replaces the parent context, variable definitions of the
72 * parent are inherited. The special variable $this points to data_.
73 *
74 * @type {Object}
75 */
76 me.vars_ = {};
77 if (opt_parent) {
78 // If there is a parent node, inherit local variables from the
79 // parent.
80 copyProperties(me.vars_, opt_parent.vars_);
81 }
82 // Add the current context object as a special variable $this
83 // so it is possible to use it in expressions.
84 this.vars_[VAR_this] = me.data_;
85 }
86
87
88 /**
89 * Evaluates the given expression in the context of the current
90 * context object and the current local variables.
91 *
92 * @param {String} expr A javascript expression.
93 *
94 * @param {Element} template DOM node of the template.
95 *
96 * @return The value of that expression.
97 */
98 JsExprContext.prototype.jseval = function(expr, template) {
99 with (this.vars_) {
100 with (this.data_) {
101 try {
102 // NOTE: Since eval is a built-in function, calling
103 // eval.call(template, ...) does not set 'this' to template.
104 return (function() {
105 return eval('[' + expr + '][0]');
106 }).call(template);
107 } catch (e) {
108 return null;
109 }
110 }
111 }
112 }
113
114
115 /**
116 * Clones the current context for a new context object. The cloned
117 * context has the data object as its context object and the current
118 * context as its parent context. It also sets the $index variable to
119 * the given value. This value usually is the position of the data
120 * object in a list for which a template is instantiated multiply.
121 *
122 * @param {Object} data The new context object.
123 *
124 * @param {Number} index Position of the new context when multiply
125 * instantiated. (See implementation of jstSelect().)
126 *
127 * @return {JsExprContext}
128 */
129 JsExprContext.prototype.clone = function(data, index) {
130 var ret = new JsExprContext(data, this);
131 ret.setVariable(VAR_index, index);
132 if (this.resolver_) {
133 ret.setSubTemplateResolver(this.resolver_);
134 }
135 return ret;
136 }
137
138
139 /**
140 * Binds a local variable to the given value. If set from jstemplate
141 * jsvalue expressions, variable names must start with $, but in the
142 * API they only have to be valid javascript identifier.
143 *
144 * @param {String} name
145 *
146 * @param {Object} value
147 */
148 JsExprContext.prototype.setVariable = function(name, value) {
149 this.vars_[name] = value;
150 }
151
152
153 /**
154 * Sets the function used to resolve the values of the transclude
155 * attribute into DOM nodes. By default, this is jstGetTemplate(). The
156 * value set here is inherited by clones of this context.
157 *
158 * @param {Function} resolver The function used to resolve transclude
159 * ids into a DOM node of a subtemplate. The DOM node returned by this
160 * function will be inserted into the template instance being
161 * processed. Thus, the resolver function must instantiate the
162 * subtemplate as necessary.
163 */
164 JsExprContext.prototype.setSubTemplateResolver = function(resolver) {
165 this.resolver_ = resolver;
166 }
167
168
169 /**
170 * Resolves a sub template from an id. Used to process the transclude
171 * attribute. If a resolver function was set using
172 * setSubTemplateResolver(), it will be used, otherwise
173 * jstGetTemplate().
174 *
175 * @param {String} id The id of the sub template.
176 *
177 * @return {Node} The root DOM node of the sub template, for direct
178 * insertion into the currently processed template instance.
179 */
180 JsExprContext.prototype.getSubTemplate = function(id) {
181 return (this.resolver_ || jstGetTemplate).call(this, id);
182 }
183 79
184 80
185 /** 81 /**
186 * HTML template processor. Data values are bound to HTML templates 82 * HTML template processor. Data values are bound to HTML templates
187 * using the attributes transclude, jsselect, jsdisplay, jscontent, 83 * using the attributes transclude, jsselect, jsdisplay, jscontent,
188 * jsvalues. The template is modifed in place. The values of those 84 * jsvalues. The template is modifed in place. The values of those
189 * attributes are JavaScript expressions that are evaluated in the 85 * attributes are JavaScript expressions that are evaluated in the
190 * context of the data object fragment. 86 * context of the data object fragment.
191 * 87 *
192 * @param {JsExprContext} context Context created from the input data 88 * @param {JsEvalContext} context Context created from the input data
193 * object. 89 * object.
194 * 90 *
195 * @param {Element} template DOM node of the template. This will be 91 * @param {Element} template DOM node of the template. This will be
196 * processed in place. After processing, it will still be a valid 92 * processed in place. After processing, it will still be a valid
197 * template that, if processed again with the same data, will remain 93 * template that, if processed again with the same data, will remain
198 * unchanged. 94 * unchanged.
199 */ 95 *
200 function jstProcess(context, template) { 96 * @param {boolean} opt_debugging Optional flag to collect debugging
201 var processor = new JstProcessor(); 97 * information while processing the template. Only takes effect
202 processor.run_([ processor, processor.jstProcess_, context, template ]); 98 * in MAPS_DEBUG.
99 */
100 function jstProcess(context, template, opt_debugging) {
101 var processor = new JstProcessor;
102 JstProcessor.prepareTemplate_(template);
103
104 /**
105 * Caches the document of the template node, so we don't have to
106 * access it through ownerDocument.
107 * @type Document
108 */
109 processor.document_ = ownerDocument(template);
110
111 processor.run_(bindFully(processor, processor.jstProcessOuter_,
112 context, template));
203 } 113 }
204 114
205 115
206 /** 116 /**
207 * Internal class used by jstemplates to maintain context. 117 * Internal class used by jstemplates to maintain context. This is
208 * NOTE: This is necessary to process deep templates in Safari 118 * necessary to process deep templates in Safari which has a
209 * which has a relatively shallow stack. 119 * relatively shallow maximum recursion depth of 100.
210 * @class 120 * @class
121 * @constructor
211 */ 122 */
212 function JstProcessor() { 123 function JstProcessor() {
213 } 124 }
214 125
215 126
216 /** 127 /**
217 * Runs the state machine, beginning with function "start". 128 * Counter to generate node ids. These ids will be stored in
218 * 129 * ATT_jstcache and be used to lookup the preprocessed js attributes
219 * @param {Array} start The first function to run, in the form 130 * from the jstcache_. The id is stored in an attribute so it
220 * [object, method, args ...] 131 * suvives cloneNode() and thus cloned template nodes can share the
221 */ 132 * same cache entry.
222 JstProcessor.prototype.run_ = function(start) { 133 * @type number
134 */
135 JstProcessor.jstid_ = 0;
136
137
138 /**
139 * Map from jstid to processed js attributes.
140 * @type Object
141 */
142 JstProcessor.jstcache_ = {};
143
144 /**
145 * The neutral cache entry. Used for all nodes that don't have any
146 * jst attributes. We still set the jsid attribute on those nodes so
147 * we can avoid to look again for all the other jst attributes that
148 * aren't there. Remember: not only the processing of the js
149 * attribute values is expensive and we thus want to cache it. The
150 * access to the attributes on the Node in the first place is
151 * expensive too.
152 */
153 JstProcessor.jstcache_[0] = {};
154
155
156 /**
157 * Map from concatenated attribute string to jstid.
158 * The key is the concatenation of all jst atributes found on a node
159 * formatted as "name1=value1&name2=value2&...", in the order defined by
160 * JST_ATTRIBUTES. The value is the id of the jstcache_ entry that can
161 * be used for this node. This allows the reuse of cache entries in cases
162 * when a cached entry already exists for a given combination of attribute
163 * values. (For example when two different nodes in a template share the same
164 * JST attributes.)
165 * @type Object
166 */
167 JstProcessor.jstcacheattributes_ = {};
168
169
170 /**
171 * Map for storing temporary attribute values in prepareNode_() so they don't
172 * have to be retrieved twice. (IE6 perf)
173 * @type Object
174 */
175 JstProcessor.attributeValues_ = {};
176
177
178 /**
179 * A list for storing non-empty attributes found on a node in prepareNode_().
180 * The array is global since it can be reused - this way there is no need to
181 * construct a new array object for each invocation. (IE6 perf)
182 * @type Array
183 */
184 JstProcessor.attributeList_ = [];
185
186
187 /**
188 * Prepares the template: preprocesses all jstemplate attributes.
189 *
190 * @param {Element} template
191 */
192 JstProcessor.prepareTemplate_ = function(template) {
193 if (!template[PROP_jstcache]) {
194 domTraverseElements(template, function(node) {
195 JstProcessor.prepareNode_(node);
196 });
197 }
198 };
199
200
201 /**
202 * A list of attributes we use to specify jst processing instructions,
203 * and the functions used to parse their values.
204 *
205 * @type Array.<Array>
206 */
207 var JST_ATTRIBUTES = [
208 [ ATT_select, jsEvalToFunction ],
209 [ ATT_display, jsEvalToFunction ],
210 [ ATT_values, jsEvalToValues ],
211 [ ATT_vars, jsEvalToValues ],
212 [ ATT_eval, jsEvalToExpressions ],
213 [ ATT_transclude, jsEvalToSelf ],
214 [ ATT_content, jsEvalToFunction ],
215 [ ATT_skip, jsEvalToFunction ]
216 ];
217
218
219 /**
220 * Prepares a single node: preprocesses all template attributes of the
221 * node, and if there are any, assigns a jsid attribute and stores the
222 * preprocessed attributes under the jsid in the jstcache.
223 *
224 * @param {Element} node
225 *
226 * @return {Object} The jstcache entry. The processed jst attributes
227 * are properties of this object. If the node has no jst attributes,
228 * returns an object with no properties (the jscache_[0] entry).
229 */
230 JstProcessor.prepareNode_ = function(node) {
231 // If the node already has a cache property, return it.
232 if (node[PROP_jstcache]) {
233 return node[PROP_jstcache];
234 }
235
236 // If it is not found, we always set the PROP_jstcache property on the node.
237 // Accessing the property is faster than executing getAttribute(). If we
238 // don't find the property on a node that was cloned in jstSelect_(), we
239 // will fall back to check for the attribute and set the property
240 // from cache.
241
242 // If the node has an attribute indexing a cache object, set it as a property
243 // and return it.
244 var jstid = domGetAttribute(node, ATT_jstcache);
245 if (jstid != null) {
246 return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];
247 }
248
249 var attributeValues = JstProcessor.attributeValues_;
250 var attributeList = JstProcessor.attributeList_;
251 attributeList.length = 0;
252
253 // Look for interesting attributes.
254 for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {
255 var name = JST_ATTRIBUTES[i][0];
256 var value = domGetAttribute(node, name);
257 attributeValues[name] = value;
258 if (value != null) {
259 attributeList.push(name + "=" + value);
260 }
261 }
262
263 // If none found, mark this node to prevent further inspection, and return
264 // an empty cache object.
265 if (attributeList.length == 0) {
266 domSetAttribute(node, ATT_jstcache, STRING_zero);
267 return node[PROP_jstcache] = JstProcessor.jstcache_[0];
268 }
269
270 // If we already have a cache object corresponding to these attributes,
271 // annotate the node with it, and return it.
272 var attstring = attributeList.join(CHAR_ampersand);
273 if (jstid = JstProcessor.jstcacheattributes_[attstring]) {
274 domSetAttribute(node, ATT_jstcache, jstid);
275 return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];
276 }
277
278 // Otherwise, build a new cache object.
279 var jstcache = {};
280 for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {
281 var att = JST_ATTRIBUTES[i];
282 var name = att[0];
283 var parse = att[1];
284 var value = attributeValues[name];
285 if (value != null) {
286 jstcache[name] = parse(value);
287 }
288 }
289
290 jstid = STRING_empty + ++JstProcessor.jstid_;
291 domSetAttribute(node, ATT_jstcache, jstid);
292 JstProcessor.jstcache_[jstid] = jstcache;
293 JstProcessor.jstcacheattributes_[attstring] = jstid;
294
295 return node[PROP_jstcache] = jstcache;
296 };
297
298
299 /**
300 * Runs the given function in our state machine.
301 *
302 * It's informative to view the set of all function calls as a tree:
303 * - nodes are states
304 * - edges are state transitions, implemented as calls to the pending
305 * functions in the stack.
306 * - pre-order function calls are downward edges (recursion into call).
307 * - post-order function calls are upward edges (return from call).
308 * - leaves are nodes which do not recurse.
309 * We represent the call tree as an array of array of calls, indexed as
310 * stack[depth][index]. Here [depth] indexes into the call stack, and
311 * [index] indexes into the call queue at that depth. We require a call
312 * queue so that a node may branch to more than one child
313 * (which will be called serially), typically due to a loop structure.
314 *
315 * @param {Function} f The first function to run.
316 */
317 JstProcessor.prototype.run_ = function(f) {
223 var me = this; 318 var me = this;
224 319
225 me.queue_ = [ start ]; 320 /**
226 while (jsLength(me.queue_)) { 321 * A stack of queues of pre-order calls.
227 var f = me.queue_.shift(); 322 * The inner arrays (constituent queues) are structured as
228 f[1].apply(f[0], f.slice(2)); 323 * [ arg2, arg1, method, arg2, arg1, method, ...]
229 } 324 * ie. a flattened array of methods with 2 arguments, in reverse order
230 } 325 * for efficient push/pop.
231 326 *
232 327 * The outer array is a stack of such queues.
233 /** 328 *
234 * Appends a function to be called later. 329 * @type Array.<Array>
235 * Analogous to calling that function on a subsequent line, or a subsequent 330 */
236 * iteration of a loop. 331 var calls = me.calls_ = [];
237 * 332
238 * @param {Array} f A function in the form [object, method, args ...] 333 /**
239 */ 334 * The index into the queue for each depth. NOTE: Alternative would
240 JstProcessor.prototype.enqueue_ = function(f) { 335 * be to maintain the queues in reverse order (popping off of the
241 this.queue_.push(f); 336 * end) but the repeated calls to .pop() consumed 90% of this
242 } 337 * function's execution time.
243 338 * @type Array.<number>
244 339 */
245 /** 340 var queueIndices = me.queueIndices_ = [];
246 * Implements internals of jstProcess. 341
247 * 342 /**
248 * @param {JsExprContext} context 343 * A pool of empty arrays. Minimizes object allocation for IE6's benefit.
344 * @type Array.<Array>
345 */
346 var arrayPool = me.arrayPool_ = [];
347
348 f();
349 var queue, queueIndex;
350 var method, arg1, arg2;
351 var temp;
352 while (calls.length) {
353 queue = calls[calls.length - 1];
354 queueIndex = queueIndices[queueIndices.length - 1];
355 if (queueIndex >= queue.length) {
356 me.recycleArray_(calls.pop());
357 queueIndices.pop();
358 continue;
359 }
360
361 // Run the first function in the queue.
362 method = queue[queueIndex++];
363 arg1 = queue[queueIndex++];
364 arg2 = queue[queueIndex++];
365 queueIndices[queueIndices.length - 1] = queueIndex;
366 method.call(me, arg1, arg2);
367 }
368 };
369
370
371 /**
372 * Pushes one or more functions onto the stack. These will be run in sequence,
373 * interspersed with any recursive calls that they make.
374 *
375 * This method takes ownership of the given array!
376 *
377 * @param {Array} args Array of method calls structured as
378 * [ method, arg1, arg2, method, arg1, arg2, ... ]
379 */
380 JstProcessor.prototype.push_ = function(args) {
381 this.calls_.push(args);
382 this.queueIndices_.push(0);
383 };
384
385
386 /**
387 * Enable/disable debugging.
388 * @param {boolean} debugging New state
389 */
390 JstProcessor.prototype.setDebugging = function(debugging) {
391 };
392
393
394 JstProcessor.prototype.createArray_ = function() {
395 if (this.arrayPool_.length) {
396 return this.arrayPool_.pop();
397 } else {
398 return [];
399 }
400 };
401
402
403 JstProcessor.prototype.recycleArray_ = function(array) {
404 arrayClear(array);
405 this.arrayPool_.push(array);
406 };
407
408 /**
409 * Implements internals of jstProcess. This processes the two
410 * attributes transclude and jsselect, which replace or multiply
411 * elements, hence the name "outer". The remainder of the attributes
412 * is processed in jstProcessInner_(), below. That function
413 * jsProcessInner_() only processes attributes that affect an existing
414 * node, but doesn't create or destroy nodes, hence the name
415 * "inner". jstProcessInner_() is called through jstSelect_() if there
416 * is a jsselect attribute (possibly for newly created clones of the
417 * current template node), or directly from here if there is none.
418 *
419 * @param {JsEvalContext} context
249 * 420 *
250 * @param {Element} template 421 * @param {Element} template
251 */ 422 */
252 JstProcessor.prototype.jstProcess_ = function(context, template) { 423 JstProcessor.prototype.jstProcessOuter_ = function(context, template) {
253 var me = this; 424 var me = this;
254 425
255 var transclude = domGetAttribute(template, ATT_transclude); 426 var jstAttributes = me.jstAttributes_(template);
427
428 var transclude = jstAttributes[ATT_transclude];
256 if (transclude) { 429 if (transclude) {
257 var tr = context.getSubTemplate(transclude); 430 var tr = jstGetTemplate(transclude);
258 if (tr) { 431 if (tr) {
259 domReplaceChild(tr, template); 432 domReplaceChild(tr, template);
260 me.enqueue_([ me, me.jstProcess_, context, tr ]); 433 var call = me.createArray_();
434 call.push(me.jstProcessOuter_, context, tr);
435 me.push_(call);
261 } else { 436 } else {
262 domRemoveNode(template); 437 domRemoveNode(template);
263 } 438 }
264 return; 439 return;
265 } 440 }
266 441
267 var select = domGetAttribute(template, ATT_select); 442 var select = jstAttributes[ATT_select];
268 if (select) { 443 if (select) {
269 me.jstSelect_(context, template, select); 444 me.jstSelect_(context, template, select);
270 return; 445 } else {
271 } 446 me.jstProcessInner_(context, template);
272 447 }
273 var display = domGetAttribute(template, ATT_display); 448 };
449
450
451 /**
452 * Implements internals of jstProcess. This processes all attributes
453 * except transclude and jsselect. It is called either from
454 * jstSelect_() for nodes that have a jsselect attribute so that the
455 * jsselect attribute will not be processed again, or else directly
456 * from jstProcessOuter_(). See the comment on jstProcessOuter_() for
457 * an explanation of the name.
458 *
459 * @param {JsEvalContext} context
460 *
461 * @param {Element} template
462 */
463 JstProcessor.prototype.jstProcessInner_ = function(context, template) {
464 var me = this;
465
466 var jstAttributes = me.jstAttributes_(template);
467
468 // NOTE(mesch): See NOTE on ATT_content why this is a separate
469 // attribute, and not a special value in ATT_values.
470 var display = jstAttributes[ATT_display];
274 if (display) { 471 if (display) {
275 if (!context.jseval(display, template)) { 472 var shouldDisplay = context.jsexec(display, template);
473 if (!shouldDisplay) {
276 displayNone(template); 474 displayNone(template);
277 return; 475 return;
278 } 476 }
279
280 displayDefault(template); 477 displayDefault(template);
281 // domRemoveAttribute(template, ATT_display); 478 }
282 } 479
283 480 // NOTE(mesch): jsvars is evaluated before jsvalues, because it's
284 // NOTE: content is a separate attribute, instead of just a 481 // more useful to be able to use var values in attribute value
482 // expressions than vice versa.
483 var values = jstAttributes[ATT_vars];
484 if (values) {
485 me.jstVars_(context, template, values);
486 }
487
488 values = jstAttributes[ATT_values];
489 if (values) {
490 me.jstValues_(context, template, values);
491 }
492
493 // Evaluate expressions immediately. Useful for hooking callbacks
494 // into jstemplates.
495 //
496 // NOTE(mesch): Evaluation order is sometimes significant, e.g. when
497 // the expression evaluated in jseval relies on the values set in
498 // jsvalues, so it needs to be evaluated *after*
499 // jsvalues. TODO(mesch): This is quite arbitrary, it would be
500 // better if this would have more necessity to it.
501 var expressions = jstAttributes[ATT_eval];
502 if (expressions) {
503 for (var i = 0, I = jsLength(expressions); i < I; ++i) {
504 context.jsexec(expressions[i], template);
505 }
506 }
507
508 var skip = jstAttributes[ATT_skip];
509 if (skip) {
510 var shouldSkip = context.jsexec(skip, template);
511 if (shouldSkip) return;
512 }
513
514 // NOTE(mesch): content is a separate attribute, instead of just a
285 // special value mentioned in values, for two reasons: (1) it is 515 // special value mentioned in values, for two reasons: (1) it is
286 // fairly common to have only mapped content, and writing 516 // fairly common to have only mapped content, and writing
287 // content="expr" is shorter than writing values="content:expr", and 517 // content="expr" is shorter than writing values="content:expr", and
288 // (2) the presence of content actually terminates traversal, and we 518 // (2) the presence of content actually terminates traversal, and we
289 // need to check for that. Display is a separate attribute for a 519 // need to check for that. Display is a separate attribute for a
290 // reason similar to the second, in that its presence *may* 520 // reason similar to the second, in that its presence *may*
291 // terminate traversal. 521 // terminate traversal.
292 522 var content = jstAttributes[ATT_content];
293 var values = domGetAttribute(template, ATT_values); 523 if (content) {
294 if (values) { 524 me.jstContent_(context, template, content);
295 // domRemoveAttribute(template, ATT_values); 525
296 me.jstValues_(context, template, values); 526 } else {
297 } 527 // Newly generated children should be ignored, so we explicitly
298 528 // store the children to be processed.
299 // Evaluate expressions immediately. Useful for hooking callbacks 529 var queue = me.createArray_();
300 // into jstemplates. 530 for (var c = template.firstChild; c; c = c.nextSibling) {
301 // 531 if (c.nodeType == DOM_ELEMENT_NODE) {
302 // NOTE: Evaluation order is sometimes significant, e.g. in 532 queue.push(me.jstProcessOuter_, context, c);
303 // map_html_addressbook_template.tpl, the expression evaluated in
304 // jseval relies on the values set in jsvalues, so it needs to be
305 // evaluated *after* jsvalues. TODO: This is quite arbitrary,
306 // it would be better if this would have more necessity to it.
307 var expressions = domGetAttribute(template, ATT_eval);
308 if (expressions) {
309 // See NOTE at top of jstValues.
310 foreach(expressions.split(/\s*;\s*/), function(expression) {
311 expression = stringTrim(expression);
312 if (jsLength(expression)) {
313 context.jseval(expression, template);
314 } 533 }
315 }); 534 }
316 } 535 if (queue.length) me.push_(queue);
317 536 }
318 var content = domGetAttribute(template, ATT_content); 537 };
319 if (content) {
320 // domRemoveAttribute(template, ATT_content);
321 me.jstContent_(context, template, content);
322
323 } else {
324 var childnodes = [];
325 // NOTE: We enqueue the processing of each child via
326 // enqueue_(), before processing any one of them. This means
327 // that any newly generated children will be ignored (as desired).
328 for (var i = 0; i < jsLength(template.childNodes); ++i) {
329 if (template.childNodes[i].nodeType == DOM_ELEMENT_NODE) {
330 me.enqueue_(
331 [ me, me.jstProcess_, context, template.childNodes[i] ]);
332 }
333 }
334 }
335 }
336 538
337 539
338 /** 540 /**
339 * Implements the jsselect attribute: evalutes the value of the 541 * Implements the jsselect attribute: evalutes the value of the
340 * jsselect attribute in the current context, with the current 542 * jsselect attribute in the current context, with the current
341 * variable bindings (see JsExprContext.jseval()). If the value is an 543 * variable bindings (see JsEvalContext.jseval()). If the value is an
342 * array, the current template node is multiplied once for every 544 * array, the current template node is multiplied once for every
343 * element in the array, with the array element being the context 545 * element in the array, with the array element being the context
344 * object. If the array is empty, or the value is undefined, then the 546 * object. If the array is empty, or the value is undefined, then the
345 * current template node is dropped. If the value is not an array, 547 * current template node is dropped. If the value is not an array,
346 * then it is just made the context object. 548 * then it is just made the context object.
347 * 549 *
348 * @param {JsExprContext} context The current evaluation context. 550 * @param {JsEvalContext} context The current evaluation context.
349 * 551 *
350 * @param {Element} template The currently processed node of the template. 552 * @param {Element} template The currently processed node of the template.
351 * 553 *
352 * @param {String} select The javascript expression to evaluate. 554 * @param {Function} select The javascript expression to evaluate.
353 * 555 *
354 * @param {Function} process The function to continue processing with. 556 * @notypecheck FIXME(hmitchell): See OCL6434950. instance and value need
557 * type checks.
355 */ 558 */
356 JstProcessor.prototype.jstSelect_ = function(context, template, select) { 559 JstProcessor.prototype.jstSelect_ = function(context, template, select) {
357 var me = this; 560 var me = this;
358 561
359 var value = context.jseval(select, template); 562 var value = context.jsexec(select, template);
360 domRemoveAttribute(template, ATT_select);
361 563
362 // Enable reprocessing: if this template is reprocessed, then only 564 // Enable reprocessing: if this template is reprocessed, then only
363 // fill the section instance here. Otherwise do the cardinal 565 // fill the section instance here. Otherwise do the cardinal
364 // processing of a new template. 566 // processing of a new template.
365 var instance = domGetAttribute(template, ATT_instance); 567 var instance = domGetAttribute(template, ATT_instance);
366 var instance_last = false; 568
569 var instanceLast = false;
367 if (instance) { 570 if (instance) {
368 if (instance.charAt(0) == '*') { 571 if (instance.charAt(0) == CHAR_asterisk) {
369 instance = parseInt10(instance.substr(1)); 572 instance = parseInt10(instance.substr(1));
370 instance_last = true; 573 instanceLast = true;
371 } else { 574 } else {
372 instance = parseInt10(instance); 575 instance = parseInt10(/** @type string */(instance));
373 } 576 }
374 } 577 }
375 578
376 // NOTE: value instanceof Array is occasionally false for 579 // The expression value instanceof Array is occasionally false for
377 // arrays, seen in Firefox. Thus we recognize an array as an object 580 // arrays, seen in Firefox. Thus we recognize an array as an object
378 // which is not null that has a length property. Notice that this 581 // which is not null that has a length property. Notice that this
379 // also matches input data with a length property, so this property 582 // also matches input data with a length property, so this property
380 // name should be avoided in input data. 583 // name should be avoided in input data.
381 var multiple = (value !== null && 584 var multiple = isArray(value);
382 typeof value == 'object' && 585 var count = multiple ? jsLength(value) : 1;
383 typeof value.length == 'number'); 586 var multipleEmpty = (multiple && count == 0);
384 var multiple_empty = (multiple && value.length == 0);
385 587
386 if (multiple) { 588 if (multiple) {
387 if (multiple_empty) { 589 if (multipleEmpty) {
388 // For an empty array, keep the first template instance and mark 590 // For an empty array, keep the first template instance and mark
389 // it last. Remove all other template instances. 591 // it last. Remove all other template instances.
390 if (!instance) { 592 if (!instance) {
391 domSetAttribute(template, ATT_select, select); 593 domSetAttribute(template, ATT_instance, STRING_asteriskzero);
392 domSetAttribute(template, ATT_instance, '*0');
393 displayNone(template); 594 displayNone(template);
394 } else { 595 } else {
395 domRemoveNode(template); 596 domRemoveNode(template);
396 } 597 }
397 598
398 } else { 599 } else {
399 displayDefault(template); 600 displayDefault(template);
400 // For a non empty array, create as many template instances as 601 // For a non empty array, create as many template instances as
401 // are needed. If the template is first processed, as many 602 // are needed. If the template is first processed, as many
402 // template instances are needed as there are values in the 603 // template instances are needed as there are values in the
403 // array. If the template is reprocessed, new template instances 604 // array. If the template is reprocessed, new template instances
404 // are only needed if there are more array values than template 605 // are only needed if there are more array values than template
405 // instances. Those additional instances are created by 606 // instances. Those additional instances are created by
406 // replicating the last template instance. NOTE: If the 607 // replicating the last template instance.
407 // template is first processed, there is no jsinstance 608 //
609 // When the template is first processed, there is no jsinstance
408 // attribute. This is indicated by instance === null, except in 610 // attribute. This is indicated by instance === null, except in
409 // opera it is instance === "". Notice also that the === is 611 // opera it is instance === "". Notice also that the === is
410 // essential, because 0 == "", presumably via type coercion to 612 // essential, because 0 == "", presumably via type coercion to
411 // boolean. For completeness, in case any browser returns 613 // boolean.
412 // undefined and not null for getAttribute of nonexistent 614 if (instance === null || instance === STRING_empty ||
413 // attributes, we also test for === undefined. 615 (instanceLast && instance < count - 1)) {
414 if (instance === null || instance === "" || instance === undefined || 616 // A queue of calls to push.
415 (instance_last && instance < jsLength(value) - 1)) { 617 var queue = me.createArray_();
416 var templatenodes = []; 618
417 var instances_start = instance || 0; 619 var instancesStart = instance || 0;
418 for (var i = instances_start + 1; i < jsLength(value); ++i) { 620 var i, I, clone;
621 for (i = instancesStart, I = count - 1; i < I; ++i) {
419 var node = domCloneNode(template); 622 var node = domCloneNode(template);
420 templatenodes.push(node);
421 domInsertBefore(node, template); 623 domInsertBefore(node, template);
624
625 jstSetInstance(/** @type Element */(node), value, i);
626 clone = context.clone(value[i], i, count);
627
628 queue.push(me.jstProcessInner_, clone, node,
629 JsEvalContext.recycle, clone, null);
630
422 } 631 }
423 // Push the originally present template instance last to keep 632 // Push the originally present template instance last to keep
424 // the order aligned with the DOM order, because the newly 633 // the order aligned with the DOM order, because the newly
425 // created template instances are inserted *before* the 634 // created template instances are inserted *before* the
426 // original instance. 635 // original instance.
427 templatenodes.push(template); 636 jstSetInstance(template, value, i);
428 637 clone = context.clone(value[i], i, count);
429 for (var i = 0; i < jsLength(templatenodes); ++i) { 638 queue.push(me.jstProcessInner_, clone, template,
430 var ii = i + instances_start; 639 JsEvalContext.recycle, clone, null);
431 var v = value[ii]; 640 me.push_(queue);
432 var t = templatenodes[i]; 641 } else if (instance < count) {
433
434 me.enqueue_([ me, me.jstProcess_, context.clone(v, ii), t ]);
435 var instanceStr = (ii == jsLength(value) - 1 ? '*' : '') + ii;
436 me.enqueue_(
437 [ null, postProcessMultiple_, t, select, instanceStr ]);
438 }
439
440 } else if (instance < jsLength(value)) {
441 var v = value[instance]; 642 var v = value[instance];
442 643
443 me.enqueue_( 644 jstSetInstance(template, value, instance);
444 [me, me.jstProcess_, context.clone(v, instance), template]); 645 var clone = context.clone(v, instance, count);
445 var instanceStr = (instance == jsLength(value) - 1 ? '*' : '') 646 var queue = me.createArray_();
446 + instance; 647 queue.push(me.jstProcessInner_, clone, template,
447 me.enqueue_( 648 JsEvalContext.recycle, clone, null);
448 [ null, postProcessMultiple_, template, select, instanceStr ]); 649 me.push_(queue);
449 } else { 650 } else {
450 domRemoveNode(template); 651 domRemoveNode(template);
451 } 652 }
452 } 653 }
453 } else { 654 } else {
454 if (value == null) { 655 if (value == null) {
455 domSetAttribute(template, ATT_select, select);
456 displayNone(template); 656 displayNone(template);
457 } else { 657 } else {
458 me.enqueue_( 658 displayDefault(template);
459 [ me, me.jstProcess_, context.clone(value, 0), template ]); 659 var clone = context.clone(value, 0, 1);
460 me.enqueue_( 660 var queue = me.createArray_();
461 [ null, postProcessSingle_, template, select ]); 661 queue.push(me.jstProcessInner_, clone, template,
662 JsEvalContext.recycle, clone, null);
663 me.push_(queue);
462 } 664 }
463 } 665 }
464 } 666 };
465 667
466 668
467 /** 669 /**
468 * Sets ATT_select and ATT_instance following recursion to jstProcess. 670 * Implements the jsvars attribute: evaluates each of the values and
671 * assigns them to variables in the current context. Similar to
672 * jsvalues, except that all values are treated as vars, independent
673 * of their names.
469 * 674 *
470 * @param {Element} template The template 675 * @param {JsEvalContext} context Current evaluation context.
471 * 676 *
472 * @param {String} select The jsselect string 677 * @param {Element} template Currently processed template node.
473 * 678 *
474 * @param {String} instanceStr The new value for the jsinstance attribute 679 * @param {Array} values Processed value of the jsvalues attribute: a
680 * flattened array of pairs. The second element in the pair is a
681 * function that can be passed to jsexec() for evaluation in the
682 * current jscontext, and the first element is the variable name that
683 * the value returned by jsexec is assigned to.
475 */ 684 */
476 function postProcessMultiple_(template, select, instanceStr) { 685 JstProcessor.prototype.jstVars_ = function(context, template, values) {
477 domSetAttribute(template, ATT_select, select); 686 for (var i = 0, I = jsLength(values); i < I; i += 2) {
478 domSetAttribute(template, ATT_instance, instanceStr); 687 var label = values[i];
479 } 688 var value = context.jsexec(values[i+1], template);
480 689 context.setVariable(label, value);
481 690 }
482 /** 691 };
483 * Sets ATT_select and makes the element visible following recursion to
484 * jstProcess.
485 *
486 * @param {Element} template The template
487 *
488 * @param {String} select The jsselect string
489 */
490 function postProcessSingle_(template, select) {
491 domSetAttribute(template, ATT_select, select);
492 displayDefault(template);
493 }
494 692
495 693
496 /** 694 /**
497 * Implements the jsvalues attribute: evaluates each of the values and 695 * Implements the jsvalues attribute: evaluates each of the values and
498 * assigns them to variables in the current context (if the name 696 * assigns them to variables in the current context (if the name
499 * starts with '$', javascript properties of the current template node 697 * starts with '$', javascript properties of the current template node
500 * (if the name starts with '.'), or DOM attributes of the current 698 * (if the name starts with '.'), or DOM attributes of the current
501 * template node (otherwise). Since DOM attribute values are always 699 * template node (otherwise). Since DOM attribute values are always
502 * strings, the value is coerced to string in the latter case, 700 * strings, the value is coerced to string in the latter case,
503 * otherwise it's the uncoerced javascript value. 701 * otherwise it's the uncoerced javascript value.
504 * 702 *
505 * @param {JsExprContext} context Current evaluation context. 703 * @param {JsEvalContext} context Current evaluation context.
506 * 704 *
507 * @param {Element} template Currently processed template node. 705 * @param {Element} template Currently processed template node.
508 * 706 *
509 * @param {String} valuesStr Value of the jsvalues attribute to be 707 * @param {Array} values Processed value of the jsvalues attribute: a
510 * processed. 708 * flattened array of pairs. The second element in the pair is a
709 * function that can be passed to jsexec() for evaluation in the
710 * current jscontext, and the first element is the label that
711 * determines where the value returned by jsexec is assigned to.
511 */ 712 */
512 JstProcessor.prototype.jstValues_ = function(context, template, valuesStr) { 713 JstProcessor.prototype.jstValues_ = function(context, template, values) {
513 // TODO: It is insufficient to split the values by simply 714 for (var i = 0, I = jsLength(values); i < I; i += 2) {
514 // finding semi-colons, as the semi-colon may be part of a string constant 715 var label = values[i];
515 // or escaped. This needs to be fixed. 716 var value = context.jsexec(values[i+1], template);
516 var values = valuesStr.split(/\s*;\s*/);
517 for (var i = 0; i < jsLength(values); ++i) {
518 var colon = values[i].indexOf(':');
519 if (colon < 0) {
520 continue;
521 }
522 var label = stringTrim(values[i].substr(0, colon));
523 var value = context.jseval(values[i].substr(colon + 1), template);
524 717
525 if (label.charAt(0) == '$') { 718 if (label.charAt(0) == CHAR_dollar) {
526 // A jsvalues entry whose name starts with $ sets a local 719 // A jsvalues entry whose name starts with $ sets a local
527 // variable. 720 // variable.
528 context.setVariable(label, value); 721 context.setVariable(label, value);
529 722
530 } else if (label.charAt(0) == '.') { 723 } else if (label.charAt(0) == CHAR_period) {
531 // A jsvalues entry whose name starts with . sets a property of 724 // A jsvalues entry whose name starts with . sets a property of
532 // the current template node. The name may have further dot 725 // the current template node. The name may have further dot
533 // separated components, which are translated into namespace 726 // separated components, which are translated into namespace
534 // objects. This specifically allows to set properties on .style 727 // objects. This specifically allows to set properties on .style
535 // using jsvalues. NOTE(mesch): Setting the style attribute has 728 // using jsvalues. NOTE(mesch): Setting the style attribute has
536 // no effect in IE and hence should not be done anyway. 729 // no effect in IE and hence should not be done anyway.
537 var nameSpaceLabel = label.substr(1).split('.'); 730 var nameSpaceLabel = label.substr(1).split(CHAR_period);
538 var nameSpaceObject = template; 731 var nameSpaceObject = template;
539 var nameSpaceDepth = jsLength(nameSpaceLabel); 732 var nameSpaceDepth = jsLength(nameSpaceLabel);
540 for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) { 733 for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) {
541 var jLabel = nameSpaceLabel[j]; 734 var jLabel = nameSpaceLabel[j];
542 if (!nameSpaceObject[jLabel]) { 735 if (!nameSpaceObject[jLabel]) {
543 nameSpaceObject[jLabel] = {}; 736 nameSpaceObject[jLabel] = {};
544 } 737 }
545 nameSpaceObject = nameSpaceObject[jLabel]; 738 nameSpaceObject = nameSpaceObject[jLabel];
546 } 739 }
547 nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value; 740 nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value;
741
548 } else if (label) { 742 } else if (label) {
549 // Any other jsvalues entry sets an attribute of the current 743 // Any other jsvalues entry sets an attribute of the current
550 // template node. 744 // template node.
551 if (typeof value == 'boolean') { 745 if (typeof value == TYPE_boolean) {
552 // Handle boolean values that are set as attributes specially, 746 // Handle boolean values that are set as attributes specially,
553 // according to the XML/HTML convention. 747 // according to the XML/HTML convention.
554 if (value) { 748 if (value) {
555 domSetAttribute(template, label, label); 749 domSetAttribute(template, label, label);
556 } else { 750 } else {
557 domRemoveAttribute(template, label); 751 domRemoveAttribute(template, label);
558 } 752 }
559 } else { 753 } else {
560 domSetAttribute(template, label, '' + value); 754 domSetAttribute(template, label, STRING_empty + value);
561 } 755 }
562 } 756 }
563 } 757 }
564 } 758 };
565 759
566 760
567 /** 761 /**
568 * Implements the jscontent attribute. Evalutes the expression in 762 * Implements the jscontent attribute. Evalutes the expression in
569 * jscontent in the current context and with the current variables, 763 * jscontent in the current context and with the current variables,
570 * and assigns its string value to the content of the current template 764 * and assigns its string value to the content of the current template
571 * node. 765 * node.
572 * 766 *
573 * @param {JsExprContext} context Current evaluation context. 767 * @param {JsEvalContext} context Current evaluation context.
574 * 768 *
575 * @param {Element} template Currently processed template node. 769 * @param {Element} template Currently processed template node.
576 * 770 *
577 * @param {String} content Value of the jscontent attribute to be 771 * @param {Function} content Processed value of the jscontent
578 * processed. 772 * attribute.
579 */ 773 */
580 JstProcessor.prototype.jstContent_ = function(context, template, content) { 774 JstProcessor.prototype.jstContent_ = function(context, template, content) {
581 var value = '' + context.jseval(content, template); 775 // NOTE(mesch): Profiling shows that this method costs significant
776 // time. In jstemplate_perf.html, it's about 50%. I tried to replace
777 // by HTML escaping and assignment to innerHTML, but that was even
778 // slower.
779 var value = STRING_empty + context.jsexec(content, template);
582 // Prevent flicker when refreshing a template and the value doesn't 780 // Prevent flicker when refreshing a template and the value doesn't
583 // change. 781 // change.
584 if (template.innerHTML == value) { 782 if (template.innerHTML == value) {
585 return; 783 return;
586 } 784 }
587 while (template.firstChild) { 785 while (template.firstChild) {
588 domRemoveNode(template.firstChild); 786 domRemoveNode(template.firstChild);
589 } 787 }
590 var t = domCreateTextNode(ownerDocument(template), value); 788 var t = domCreateTextNode(this.document_, value);
591 domAppendChild(template, t); 789 domAppendChild(template, t);
592 } 790 };
791
792
793 /**
794 * Caches access to and parsing of template processing attributes. If
795 * domGetAttribute() is called every time a template attribute value
796 * is used, it takes more than 10% of the time.
797 *
798 * @param {Element} template A DOM element node of the template.
799 *
800 * @return {Object} A javascript object that has all js template
801 * processing attribute values of the node as properties.
802 */
803 JstProcessor.prototype.jstAttributes_ = function(template) {
804 if (template[PROP_jstcache]) {
805 return template[PROP_jstcache];
806 }
807
808 var jstid = domGetAttribute(template, ATT_jstcache);
809 if (jstid) {
810 return template[PROP_jstcache] = JstProcessor.jstcache_[jstid];
811 }
812
813 return JstProcessor.prepareNode_(template);
814 };
593 815
594 816
595 /** 817 /**
596 * Helps to implement the transclude attribute, and is the initial 818 * Helps to implement the transclude attribute, and is the initial
597 * call to get hold of a template from its ID. 819 * call to get hold of a template from its ID.
598 * 820 *
599 * @param {String} name The ID of the HTML element used as template. 821 * If the ID is not present in the DOM, and opt_loadHtmlFn is specified, this
822 * function will call that function and add the result to the DOM, before
823 * returning the template.
600 * 824 *
601 * @returns {Element} The DOM node of the template. (Only element 825 * @param {string} name The ID of the HTML element used as template.
602 * nodes can be found by ID, hence it's a Element.) 826 * @param {Function} opt_loadHtmlFn A function which, when called, will return
827 * HTML that contains an element whose ID is 'name'.
828 *
829 * @return {Element|null} The DOM node of the template. (Only element nodes
830 * can be found by ID, hence it's a Element.)
603 */ 831 */
604 function jstGetTemplate(name) { 832 function jstGetTemplate(name, opt_loadHtmlFn) {
605 var section = domGetElementById(document, name); 833 var doc = document;
834 var section;
835 if (opt_loadHtmlFn) {
836 section = jstLoadTemplateIfNotPresent(doc, name, opt_loadHtmlFn);
837 } else {
838 section = domGetElementById(doc, name);
839 }
606 if (section) { 840 if (section) {
607 var ret = domCloneNode(section); 841 JstProcessor.prepareTemplate_(section);
608 domRemoveAttribute(ret, 'id'); 842 var ret = domCloneElement(section);
843 domRemoveAttribute(ret, STRING_id);
609 return ret; 844 return ret;
610 } else { 845 } else {
611 return null; 846 return null;
612 } 847 }
613 } 848 }
614 849
615 window['jstGetTemplate'] = jstGetTemplate; 850 /**
616 window['jstProcess'] = jstProcess; 851 * This function is the same as 'jstGetTemplate' but, if the template
617 window['JsExprContext'] = JsExprContext; 852 * does not exist, throw an exception.
853 *
854 * @param {string} name The ID of the HTML element used as template.
855 * @param {Function} opt_loadHtmlFn A function which, when called, will return
856 * HTML that contains an element whose ID is 'name'.
857 *
858 * @return {Element} The DOM node of the template. (Only element nodes
859 * can be found by ID, hence it's a Element.)
860 */
861 function jstGetTemplateOrDie(name, opt_loadHtmlFn) {
862 var x = jstGetTemplate(name, opt_loadHtmlFn);
863 check(x !== null);
864 return /** @type Element */(x);
865 }
866
867
868 /**
869 * If an element with id 'name' is not present in the document, call loadHtmlFn
870 * and insert the result into the DOM.
871 *
872 * @param {Document} doc
873 * @param {string} name
874 * @param {Function} loadHtmlFn A function that returns HTML to be inserted
875 * into the DOM.
876 * @param {string} opt_target The id of a DOM object under which to attach the
877 * HTML once it's inserted. An object with this id is created if it does not
878 * exist.
879 * @return {Element} The node whose id is 'name'
880 */
881 function jstLoadTemplateIfNotPresent(doc, name, loadHtmlFn, opt_target) {
882 var section = domGetElementById(doc, name);
883 if (section) {
884 return section;
885 }
886 // Load any necessary HTML and try again.
887 jstLoadTemplate_(doc, loadHtmlFn(), opt_target || STRING_jsts);
888 var section = domGetElementById(doc, name);
889 if (!section) {
890 log("Error: jstGetTemplate was provided with opt_loadHtmlFn, " +
891 » "but that function did not provide the id '" + name + "'.");
892 }
893 return /** @type Element */(section);
894 }
895
896
897 /**
898 * Loads the given HTML text into the given document, so that
899 * jstGetTemplate can find it.
900 *
901 * We append it to the element identified by targetId, which is hidden.
902 * If it doesn't exist, it is created.
903 *
904 * @param {Document} doc The document to create the template in.
905 *
906 * @param {string} html HTML text to be inserted into the document.
907 *
908 * @param {string} targetId The id of a DOM object under which to attach the
909 * HTML once it's inserted. An object with this id is created if it does not
910 * exist.
911 */
912 function jstLoadTemplate_(doc, html, targetId) {
913 var existing_target = domGetElementById(doc, targetId);
914 var target;
915 if (!existing_target) {
916 target = domCreateElement(doc, STRING_div);
917 target.id = targetId;
918 displayNone(target);
919 positionAbsolute(target);
920 domAppendChild(doc.body, target);
921 } else {
922 target = existing_target;
923 }
924 var div = domCreateElement(doc, STRING_div);
925 target.appendChild(div);
926 div.innerHTML = html;
927 }
928
929
930 /**
931 * Sets the jsinstance attribute on a node according to its context.
932 *
933 * @param {Element} template The template DOM node to set the instance
934 * attribute on.
935 *
936 * @param {Array} values The current input context, the array of
937 * values of which the template node will render one instance.
938 *
939 * @param {number} index The index of this template node in values.
940 */
941 function jstSetInstance(template, values, index) {
942 if (index == jsLength(values) - 1) {
943 domSetAttribute(template, ATT_instance, CHAR_asterisk + index);
944 } else {
945 domSetAttribute(template, ATT_instance, STRING_empty + index);
946 }
947 }
948
949
950 /**
951 * Log the current state.
952 * @param {string} caller An identifier for the caller of .log_.
953 * @param {Element} template The template node being processed.
954 * @param {Object} jstAttributeValues The jst attributes of the template node.
955 */
956 JstProcessor.prototype.logState_ = function(
957 caller, template, jstAttributeValues) {
958 };
959
960
961 /**
962 * Retrieve the processing logs.
963 * @return {Array.<string>} The processing logs.
964 */
965 JstProcessor.prototype.getLogs = function() {
966 return this.logs_;
967 };
OLDNEW
« no previous file with comments | « chrome/third_party/jstemplate/jsevalcontext.js ('k') | chrome/third_party/jstemplate/jstemplate_compiled.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698