• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview This file contains miscellaneous basic functionality.
3 *
4 */
5
6/**
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 */
18function 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
30  return element;
31}
32
33/**
34 * Creates a text node with the given value.
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 */
41function createTextNode(value, owner) {
42  var element = ownerDocument(owner).createTextNode(value);
43  if (owner) {
44    appendChild(owner, element);
45  }
46  return element;
47}
48
49/**
50 * Returns the document owner of the given element. In particular,
51 * returns window.document if node is null or the browser does not
52 * support ownerDocument.
53 *
54 * @param {Node} node  The node whose ownerDocument is required.
55 * @returns {Document|Null}  The owner document or null if unsupported.
56 */
57function 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 */
67function 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 */
77function 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 */
90function 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 */
102function 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 */
111function 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 */
121function appendChild(parent, child) {
122  parent.appendChild(child);
123}
124
125
126/**
127 * Wrapper for the eval() builtin function to evaluate expressions and
128 * obtain their value. It wraps the expression in parentheses such
129 * that object literals are really evaluated to objects. Without the
130 * wrapping, they are evaluated as block, and create syntax
131 * errors. Also protects against other syntax errors in the eval()ed
132 * code and returns null if the eval throws an exception.
133 *
134 * @param {String} expr
135 * @return {Object|Null}
136 */
137function jsEval(expr) {
138  try {
139    return eval('[' + expr + '][0]');
140  } catch (e) {
141    return null;
142  }
143}
144
145
146/**
147 * Wrapper for the eval() builtin function to execute statements. This
148 * guards against exceptions thrown, but doesn't return a
149 * value. Still, mostly for testability, it returns a boolean to
150 * indicate whether execution was successful. NOTE:
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 */
158function jsExec(stmt) {
159  try {
160    eval(stmt);
161    return true;
162  } catch (e) {
163    return false;
164  }
165}
166
167
168/**
169 * Wrapper for eval with a context. NOTE: The style guide
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 *
177 * @param {String} expr
178 * @param {Object} context
179 * @return {Object|Null}
180 */
181function jsEvalWith(expr, context) {
182  try {
183    with (context) {
184      return eval('[' + expr + '][0]');
185    }
186  } catch (e) {
187    return null;
188  }
189}
190
191
192var DOM_ELEMENT_NODE = 1;
193var DOM_ATTRIBUTE_NODE = 2;
194var DOM_TEXT_NODE = 3;
195var DOM_CDATA_SECTION_NODE = 4;
196var DOM_ENTITY_REFERENCE_NODE = 5;
197var DOM_ENTITY_NODE = 6;
198var DOM_PROCESSING_INSTRUCTION_NODE = 7;
199var DOM_COMMENT_NODE = 8;
200var DOM_DOCUMENT_NODE = 9;
201var DOM_DOCUMENT_TYPE_NODE = 10;
202var DOM_DOCUMENT_FRAGMENT_NODE = 11;
203var DOM_NOTATION_NODE = 12;
204
205/**
206 * Traverses the element nodes in the DOM tree underneath the given
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 *
210 * NOTE: The reason this is not combined with the elem() function is
211 * that the implementations are different.
212 * elem() is a wrapper for the built-in document.getElementById() function,
213 * whereas this function performs the traversal itself.
214 * Modifying elem() to take an optional root node is a possibility,
215 * but the in-built function would perform better than using our own traversal.
216 *
217 * @param {Element} node Root element of subtree to traverse.
218 * @param {String} elemId The id of the element to search for.
219 * @return {Element|Null} The corresponding element, or null if not found.
220 */
221function nodeGetElementById(node, elemId) {
222  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) {
227      var n = arguments.callee.call(this, c, elemId);
228      if (n) {
229        return n;
230      }
231    }
232  }
233  return null;
234}
235
236
237/**
238 * Get an attribute from the DOM.  Simple redirect, exists to compress code.
239 *
240 * @param {Element} node  Element to interrogate.
241 * @param {String} name  Name of parameter to extract.
242 * @return {String}  Resulting attribute.
243 */
244function domGetAttribute(node, name) {
245  return node.getAttribute(name);
246}
247
248/**
249 * Set an attribute in the DOM.  Simple redirect to compress code.
250 *
251 * @param {Element} node  Element to interrogate.
252 * @param {String} name  Name of parameter to set.
253 * @param {String} value  Set attribute to this value.
254 */
255function domSetAttribute(node, name, value) {
256  node.setAttribute(name, value);
257}
258
259/**
260 * Remove an attribute from the DOM.  Simple redirect to compress code.
261 *
262 * @param {Element} node  Element to interrogate.
263 * @param {String} name  Name of parameter to remove.
264 */
265function domRemoveAttribute(node, name) {
266  node.removeAttribute(name);
267}
268
269/**
270 * Clone a node in the DOM.
271 *
272 * @param {Node} node  Node to clone.
273 * @return {Node}  Cloned node.
274 */
275function domCloneNode(node) {
276  return node.cloneNode(true);
277}
278
279
280/**
281 * Return a safe string for the className of a node.
282 * If className is not a string, returns "".
283 *
284 * @param {Element} node  DOM element to query.
285 * @return {String}
286 */
287function domClassName(node) {
288  return node.className ? "" + node.className : "";
289}
290
291/**
292 * Adds a class name to the class attribute of the given node.
293 *
294 * @param {Element} node  DOM element to modify.
295 * @param {String} className  Class name to add.
296 */
297function domAddClass(node, className) {
298  var name = domClassName(node);
299  if (name) {
300    var cn = name.split(/\s+/);
301    var found = false;
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 {
315    node.className = className;
316  }
317}
318
319/**
320 * Removes a class name from the class attribute of the given node.
321 *
322 * @param {Element} node  DOM element to modify.
323 * @param {String} className  Class name to remove.
324 */
325function domRemoveClass(node, className) {
326  var c = domClassName(node);
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 */
346function 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 */
363function domInsertBefore(newChild, oldChild) {
364  return oldChild.parentNode.insertBefore(newChild, oldChild);
365}
366
367/**
368 * Appends a new child to the specified (parent) node.
369 *
370 * @param {Element} node  Parent element.
371 * @param {Node} child  Child node to append.
372 * @return {Node}  Newly appended node.
373 */
374function domAppendChild(node, child) {
375  return node.appendChild(child);
376}
377
378/**
379 * Remove a new child from the specified (parent) node.
380 *
381 * @param {Element} node  Parent element.
382 * @param {Node} child  Child node to remove.
383 * @return {Node}  Removed node.
384 */
385function domRemoveChild(node, child) {
386  return node.removeChild(child);
387}
388
389/**
390 * Replaces an old child node with a new child node.
391 *
392 * @param {Node} newChild  New child to append.
393 * @param {Node} oldChild  Old child to remove.
394 * @return {Node}  Replaced node.
395 */
396function domReplaceChild(newChild, oldChild) {
397  return oldChild.parentNode.replaceChild(newChild, oldChild);
398}
399
400/**
401 * Removes a node from the DOM.
402 *
403 * @param {Node} node  The node to remove.
404 * @return {Node}  The removed node.
405 */
406function domRemoveNode(node) {
407  return domRemoveChild(node.parentNode, node);
408}
409
410/**
411 * Creates a new text node in the given document.
412 *
413 * @param {Document} doc  Target document.
414 * @param {String} text  Text composing new text node.
415 * @return {Text}  Newly constructed text node.
416 */
417function domCreateTextNode(doc, text) {
418  return doc.createTextNode(text);
419}
420
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 */
428function 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 */
439function 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 */
450function 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 */
460function 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 */
471function 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 */
483function 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 */
494function 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 */
504function 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 */
523function jsLength(a) {
524  return a.length;
525}
526
527var min = Math.min;
528var max = Math.max;
529var ceil = Math.ceil;
530var floor = Math.floor;
531var round = Math.round;
532var 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 */
540function 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 */
553function 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 */
570function 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 */
587function 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
595/**
596 * Trim whitespace from begin and end of string.
597 *
598 * @see testStringTrim();
599 *
600 * @param {String} str  Input string.
601 * @return {String}  Trimmed string.
602 */
603function stringTrim(str) {
604  return stringTrimRight(stringTrimLeft(str));
605}
606
607/**
608 * Trim whitespace from beginning of string.
609 *
610 * @see testStringTrimLeft();
611 *
612 * @param {String} str  Input string.
613 * @return {String}  Trimmed string.
614 */
615function stringTrimLeft(str) {
616  return str.replace(/^\s+/, "");
617}
618
619/**
620 * Trim whitespace from end of string.
621 *
622 * @see testStringTrimRight();
623 *
624 * @param {String} str  Input string.
625 * @return {String}  Trimmed string.
626 */
627function stringTrimRight(str) {
628  return str.replace(/\s+$/, "");
629}
630
631/**
632 * Jscompiler wrapper for parseInt() with base 10.
633 *
634 * @param {String} s String repersentation of a number.
635 *
636 * @return {Number} The integer contained in s, converted on base 10.
637 */
638function parseInt10(s) {
639  return parseInt(s, 10);
640}
641/**
642 * @fileoverview A simple formatter to project JavaScript data into
643 * 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
645 * process the cloned template. This allows for updating of templates:
646 * If the templates is processed again, changed values are merely
647 * updated.
648 *
649 * NOTE: IE DOM doesn't have importNode().
650 *
651 * NOTE: The property name "length" must not be used in input
652 * data, see comment in jstSelect_().
653 */
654
655
656/**
657 * Names of jstemplate attributes. These attributes are attached to
658 * normal HTML elements and bind expression context data to the HTML
659 * fragment that is used as template.
660 */
661var ATT_select = 'jsselect';
662var ATT_instance = 'jsinstance';
663var ATT_display = 'jsdisplay';
664var ATT_values = 'jsvalues';
665var ATT_eval = 'jseval';
666var ATT_transclude = 'transclude';
667var ATT_content = 'jscontent';
668
669
670/**
671 * Names of special variables defined by the jstemplate evaluation
672 * context. These can be used in js expression in jstemplate
673 * attributes.
674 */
675var VAR_index = '$index';
676var VAR_this = '$this';
677
678
679/**
680 * Context for processing a jstemplate. The context contains a context
681 * object, whose properties can be referred to in jstemplate
682 * expressions, and it holds the locally defined variables.
683 *
684 * @param {Object} opt_data The context object. Null if no context.
685 *
686 * @param {Object} opt_parent The parent context, from which local
687 * variables are inherited. Normally the context object of the parent
688 * context is the object whose property the parent object is. Null for the
689 * context of the root object.
690 *
691 * @constructor
692 */
693function JsExprContext(opt_data, opt_parent) {
694  var me = this;
695
696  /**
697   * The local context of the input data in which the jstemplate
698   * expressions are evaluated. Notice that this is usually an Object,
699   * but it can also be a scalar value (and then still the expression
700   * $this can be used to refer to it). Notice this can be a scalar
701   * value, including undefined.
702   *
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 */
733JsExprContext.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 */
762JsExprContext.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 */
781JsExprContext.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 */
797JsExprContext.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 */
813JsExprContext.prototype.getSubTemplate = function(id) {
814  return (this.resolver_ || jstGetTemplate).call(this, id);
815}
816
817
818/**
819 * HTML template processor. Data values are bound to HTML templates
820 * using the attributes transclude, jsselect, jsdisplay, jscontent,
821 * jsvalues. The template is modifed in place. The values of those
822 * attributes are JavaScript expressions that are evaluated in the
823 * context of the data object fragment.
824 *
825 * @param {JsExprContext} context Context created from the input data
826 * object.
827 *
828 * @param {Element} template DOM node of the template. This will be
829 * processed in place. After processing, it will still be a valid
830 * template that, if processed again with the same data, will remain
831 * unchanged.
832 */
833function jstProcess(context, template) {
834  var processor = new JstProcessor();
835  processor.run_([ processor, processor.jstProcess_, context, template ]);
836}
837
838
839/**
840 * Internal class used by jstemplates to maintain context.
841 * NOTE: This is necessary to process deep templates in Safari
842 * which has a relatively shallow stack.
843 * @class
844 */
845function JstProcessor() {
846}
847
848
849/**
850 * Runs the state machine, beginning with function "start".
851 *
852 * @param {Array} start The first function to run, in the form
853 * [object, method, args ...]
854 */
855JstProcessor.prototype.run_ = function(start) {
856  var me = this;
857
858  me.queue_ = [ start ];
859  while (jsLength(me.queue_)) {
860    var f = me.queue_.shift();
861    f[1].apply(f[0], f.slice(2));
862  }
863}
864
865
866/**
867 * Appends a function to be called later.
868 * Analogous to calling that function on a subsequent line, or a subsequent
869 * iteration of a loop.
870 *
871 * @param {Array} f  A function in the form [object, method, args ...]
872 */
873JstProcessor.prototype.enqueue_ = function(f) {
874  this.queue_.push(f);
875}
876
877
878/**
879 * Implements internals of jstProcess.
880 *
881 * @param {JsExprContext} context
882 *
883 * @param {Element} template
884 */
885JstProcessor.prototype.jstProcess_ = function(context, template) {
886  var me = this;
887
888  var transclude = domGetAttribute(template, ATT_transclude);
889  if (transclude) {
890    var tr = context.getSubTemplate(transclude);
891    if (tr) {
892      domReplaceChild(tr, template);
893      me.enqueue_([ me, me.jstProcess_, context, tr ]);
894    } else {
895      domRemoveNode(template);
896    }
897    return;
898  }
899
900  var select = domGetAttribute(template, ATT_select);
901  if (select) {
902    me.jstSelect_(context, template, select);
903    return;
904  }
905
906  var display = domGetAttribute(template, ATT_display);
907  if (display) {
908    if (!context.jseval(display, template)) {
909      displayNone(template);
910      return;
911    }
912
913    displayDefault(template);
914  }
915
916
917  var values = domGetAttribute(template, ATT_values);
918  if (values) {
919    me.jstValues_(context, template, values);
920  }
921
922  var expressions = domGetAttribute(template, ATT_eval);
923  if (expressions) {
924    foreach(expressions.split(/\s*;\s*/), function(expression) {
925      expression = stringTrim(expression);
926      if (jsLength(expression)) {
927        context.jseval(expression, template);
928      }
929    });
930  }
931
932  var content = domGetAttribute(template, ATT_content);
933  if (content) {
934    me.jstContent_(context, template, content);
935
936  } else {
937    var childnodes = [];
938    for (var i = 0; i < jsLength(template.childNodes); ++i) {
939      if (template.childNodes[i].nodeType == DOM_ELEMENT_NODE) {
940      me.enqueue_(
941          [ me, me.jstProcess_, context, template.childNodes[i] ]);
942      }
943    }
944  }
945}
946
947
948/**
949 * Implements the jsselect attribute: evalutes the value of the
950 * jsselect attribute in the current context, with the current
951 * variable bindings (see JsExprContext.jseval()). If the value is an
952 * array, the current template node is multiplied once for every
953 * 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
955 * current template node is dropped. If the value is not an array,
956 * then it is just made the context object.
957 *
958 * @param {JsExprContext} context The current evaluation context.
959 *
960 * @param {Element} template The currently processed node of the template.
961 *
962 * @param {String} select The javascript expression to evaluate.
963 *
964 * @param {Function} process The function to continue processing with.
965 */
966JstProcessor.prototype.jstSelect_ = function(context, template, select) {
967  var me = this;
968
969  var value = context.jseval(select, template);
970  domRemoveAttribute(template, ATT_select);
971
972  var instance = domGetAttribute(template, ATT_instance);
973  var instance_last = false;
974  if (instance) {
975    if (instance.charAt(0) == '*') {
976      instance = parseInt10(instance.substr(1));
977      instance_last = true;
978    } else {
979      instance = parseInt10(instance);
980    }
981  }
982
983  var multiple = (value !== null &&
984                  typeof value == 'object' &&
985                  typeof value.length == 'number');
986  var multiple_empty = (multiple && value.length == 0);
987
988  if (multiple) {
989    if (multiple_empty) {
990      if (!instance) {
991        domSetAttribute(template, ATT_select, select);
992        domSetAttribute(template, ATT_instance, '*0');
993        displayNone(template);
994      } else {
995        domRemoveNode(template);
996      }
997
998    } else {
999      displayDefault(template);
1000      if (instance === null || instance === "" || instance === undefined ||
1001          (instance_last && instance < jsLength(value) - 1)) {
1002        var templatenodes = [];
1003        var instances_start = instance || 0;
1004        for (var i = instances_start + 1; i < jsLength(value); ++i) {
1005          var node = domCloneNode(template);
1006          templatenodes.push(node);
1007          domInsertBefore(node, template);
1008        }
1009        templatenodes.push(template);
1010
1011        for (var i = 0; i < jsLength(templatenodes); ++i) {
1012          var ii = i + instances_start;
1013          var v = value[ii];
1014          var t = templatenodes[i];
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];
1024
1025        me.enqueue_(
1026            [me, me.jstProcess_, context.clone(v, instance), template]);
1027        var instanceStr = (instance == jsLength(value) - 1 ? '*' : '')
1028                          + instance;
1029        me.enqueue_(
1030            [ null, postProcessMultiple_, template, select, instanceStr ]);
1031      } else {
1032        domRemoveNode(template);
1033      }
1034    }
1035  } else {
1036    if (value == null) {
1037      domSetAttribute(template, ATT_select, select);
1038      displayNone(template);
1039    } else {
1040      me.enqueue_(
1041          [ me, me.jstProcess_, context.clone(value, 0), template ]);
1042      me.enqueue_(
1043          [ null, postProcessSingle_, template, select ]);
1044    }
1045  }
1046}
1047
1048
1049/**
1050 * Sets ATT_select and ATT_instance following recursion to jstProcess.
1051 *
1052 * @param {Element} template  The template
1053 *
1054 * @param {String} select  The jsselect string
1055 *
1056 * @param {String} instanceStr  The new value for the jsinstance attribute
1057 */
1058function postProcessMultiple_(template, select, instanceStr) {
1059  domSetAttribute(template, ATT_select, select);
1060  domSetAttribute(template, ATT_instance, instanceStr);
1061}
1062
1063
1064/**
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 */
1072function postProcessSingle_(template, select) {
1073  domSetAttribute(template, ATT_select, select);
1074  displayDefault(template);
1075}
1076
1077
1078/**
1079 * Implements the jsvalues attribute: evaluates each of the values and
1080 * assigns them to variables in the current context (if the name
1081 * starts with '$', javascript properties of the current template node
1082 * (if the name starts with '.'), or DOM attributes of the current
1083 * template node (otherwise). Since DOM attribute values are always
1084 * strings, the value is coerced to string in the latter case,
1085 * otherwise it's the uncoerced javascript value.
1086 *
1087 * @param {JsExprContext} context Current evaluation context.
1088 *
1089 * @param {Element} template Currently processed template node.
1090 *
1091 * @param {String} valuesStr Value of the jsvalues attribute to be
1092 * processed.
1093 */
1094JstProcessor.prototype.jstValues_ = function(context, template, valuesStr) {
1095  var values = valuesStr.split(/\s*;\s*/);
1096  for (var i = 0; i < jsLength(values); ++i) {
1097    var colon = values[i].indexOf(':');
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
1104    if (label.charAt(0) == '$') {
1105      context.setVariable(label, value);
1106
1107    } else if (label.charAt(0) == '.') {
1108      var nameSpaceLabel = label.substr(1).split('.');
1109      var nameSpaceObject = template;
1110      var nameSpaceDepth = jsLength(nameSpaceLabel);
1111      for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) {
1112        var jLabel = nameSpaceLabel[j];
1113        if (!nameSpaceObject[jLabel]) {
1114          nameSpaceObject[jLabel] = {};
1115        }
1116        nameSpaceObject = nameSpaceObject[jLabel];
1117      }
1118      nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value;
1119    } else if (label) {
1120      if (typeof value == 'boolean') {
1121        if (value) {
1122          domSetAttribute(template, label, label);
1123        } else {
1124          domRemoveAttribute(template, label);
1125        }
1126      } else {
1127        domSetAttribute(template, label, '' + value);
1128      }
1129    }
1130  }
1131}
1132
1133
1134/**
1135 * Implements the jscontent attribute. Evalutes the expression in
1136 * jscontent in the current context and with the current variables,
1137 * and assigns its string value to the content of the current template
1138 * node.
1139 *
1140 * @param {JsExprContext} context Current evaluation context.
1141 *
1142 * @param {Element} template Currently processed template node.
1143 *
1144 * @param {String} content Value of the jscontent attribute to be
1145 * processed.
1146 */
1147JstProcessor.prototype.jstContent_ = function(context, template, content) {
1148  var value = '' + context.jseval(content, template);
1149  if (template.innerHTML == value) {
1150    return;
1151  }
1152  while (template.firstChild) {
1153    domRemoveNode(template.firstChild);
1154  }
1155  var t = domCreateTextNode(ownerDocument(template), value);
1156  domAppendChild(template, t);
1157}
1158
1159
1160/**
1161 * Helps to implement the transclude attribute, and is the initial
1162 * call to get hold of a template from its ID.
1163 *
1164 * @param {String} name The ID of the HTML element used as template.
1165 *
1166 * @returns {Element} The DOM node of the template. (Only element
1167 * nodes can be found by ID, hence it's a Element.)
1168 */
1169function jstGetTemplate(name) {
1170  var section = domGetElementById(document, name);
1171  if (section) {
1172    var ret = domCloneNode(section);
1173    domRemoveAttribute(ret, 'id');
1174    return ret;
1175  } else {
1176    return null;
1177  }
1178}
1179
1180window['jstGetTemplate'] = jstGetTemplate;
1181window['jstProcess'] = jstProcess;
1182window['JsExprContext'] = JsExprContext;
1183