Home | History | Annotate | Download | only in jstemplate
      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.
     14 /**
     15  * @fileoverview Miscellaneous constants and functions referenced in
     16  * the main source files.
     17  */
     18 
     19 function log(msg) {}
     20 
     21 // String literals defined globally and not to be inlined. (IE6 perf)
     22 /** @const */ var STRING_empty = '';
     23 
     24 /** @const */ var CSS_display = 'display';
     25 /** @const */ var CSS_position = 'position';
     26 
     27 // Constants for possible values of the typeof operator.
     28 var TYPE_boolean = 'boolean';
     29 var TYPE_number = 'number';
     30 var TYPE_object = 'object';
     31 var TYPE_string = 'string';
     32 var TYPE_function = 'function';
     33 var TYPE_undefined = 'undefined';
     34 
     35 
     36 /**
     37  * Wrapper for the eval() builtin function to evaluate expressions and
     38  * obtain their value. It wraps the expression in parentheses such
     39  * that object literals are really evaluated to objects. Without the
     40  * wrapping, they are evaluated as block, and create syntax
     41  * errors. Also protects against other syntax errors in the eval()ed
     42  * code and returns null if the eval throws an exception.
     43  *
     44  * @param {string} expr
     45  * @return {Object|null}
     46  */
     47 function jsEval(expr) {
     48   try {
     49     // NOTE(mesch): An alternative idiom would be:
     50     //
     51     //   eval('(' + expr + ')');
     52     //
     53     // Note that using the square brackets as below, "" evals to undefined.
     54     // The alternative of using parentheses does not work when evaluating
     55     // function literals in IE.
     56     // e.g. eval("(function() {})") returns undefined, and not a function
     57     // object, in IE.
     58     var result = eval('[' + expr + '][0]');
     59     if (typeof result != 'object') {
     60       throw new Error('expression of type Object expected, ' +
     61                       typeof result + ' found');
     62     }
     63     return /** @type {Object} */(result);
     64   } catch (e) {
     65     log('EVAL FAILED ' + expr + ': ' + e);
     66     return null;
     67   }
     68 }
     69 
     70 function jsLength(obj) {
     71   return obj.length;
     72 }
     73 
     74 /**
     75  * Copies all properties from second object to the first.  Modifies to.
     76  *
     77  * @param {Object} to  The target object.
     78  * @param {Object} from  The source object.
     79  */
     80 function copyProperties(to, from) {
     81   for (var p in from) {
     82     to[p] = from[p];
     83   }
     84 }
     85 
     86 
     87 /**
     88  * @param {*} value The possible value to use.
     89  * @param {*} defaultValue The default if the value is not set.
     90  * @return {*} The value, if it is defined and not null; otherwise the default.
     91  */
     92 function getDefaultObject(value, defaultValue) {
     93   if (typeof value != TYPE_undefined && value != null) {
     94     return value;
     95   } else {
     96     return defaultValue;
     97   }
     98 }
     99 
    100 /**
    101  * Detect if an object looks like an Array.
    102  * Note that instanceof Array is not robust; for example an Array
    103  * created in another iframe fails instanceof Array.
    104  * @param {Object|null} value Object to interrogate
    105  * @return {boolean} Is the object an array?
    106  */
    107 function isArray(value) {
    108   return value != null &&
    109       typeof value == TYPE_object &&
    110       typeof value.length == TYPE_number;
    111 }
    112 
    113 
    114 /**
    115  * Finds a slice of an array.
    116  *
    117  * @param {Array|Arguments} array  Array to be sliced.
    118  * @param {number} start  The start of the slice.
    119  * @param {number=} opt_end  The end of the slice (optional).
    120  * @return {Array} array  The slice of the array from start to end.
    121  */
    122 function arraySlice(array, start, opt_end) {
    123   // Use
    124   //   return Function.prototype.call.apply(Array.prototype.slice, arguments);
    125   // instead of the simpler
    126   //   return Array.prototype.slice.call(array, start, opt_end);
    127   // here because of a bug in the FF and IE implementations of
    128   // Array.prototype.slice which causes this function to return an empty list
    129   // if opt_end is not provided.
    130   return /** @type {Array} */(
    131       Function.prototype.call.apply(Array.prototype.slice, arguments));
    132 }
    133 
    134 
    135 /**
    136  * Jscompiler wrapper for parseInt() with base 10.
    137  *
    138  * @param {string} s string repersentation of a number.
    139  *
    140  * @return {number} The integer contained in s, converted on base 10.
    141  */
    142 function parseInt10(s) {
    143   return parseInt(s, 10);
    144 }
    145 
    146 
    147 /**
    148  * Clears the array by setting the length property to 0. This usually
    149  * works, and if it should turn out not to work everywhere, here would
    150  * be the place to implement the browser specific workaround.
    151  *
    152  * @param {Array} array  Array to be cleared.
    153  */
    154 function arrayClear(array) {
    155   array.length = 0;
    156 }
    157 
    158 
    159 /**
    160  * Prebinds "this" within the given method to an object, but ignores all
    161  * arguments passed to the resulting function.
    162  * I.e. var_args are all the arguments that method is invoked with when
    163  * invoking the bound function.
    164  *
    165  * @param {Object|null} object  The object that the method call targets.
    166  * @param {Function} method  The target method.
    167  * @param {...*} var_args
    168  * @return {Function}  Method with the target object bound to it and curried by
    169  *                     the provided arguments.
    170  */
    171 function bindFully(object, method, var_args) {
    172   var args = arraySlice(arguments, 2);
    173   return function() {
    174     return method.apply(object, args);
    175   }
    176 }
    177 
    178 // Based on <http://www.w3.org/TR/2000/ REC-DOM-Level-2-Core-20001113/
    179 // core.html#ID-1950641247>.
    180 var DOM_ELEMENT_NODE = 1;
    181 var DOM_ATTRIBUTE_NODE = 2;
    182 var DOM_TEXT_NODE = 3;
    183 var DOM_CDATA_SECTION_NODE = 4;
    184 var DOM_ENTITY_REFERENCE_NODE = 5;
    185 var DOM_ENTITY_NODE = 6;
    186 var DOM_PROCESSING_INSTRUCTION_NODE = 7;
    187 var DOM_COMMENT_NODE = 8;
    188 var DOM_DOCUMENT_NODE = 9;
    189 var DOM_DOCUMENT_TYPE_NODE = 10;
    190 var DOM_DOCUMENT_FRAGMENT_NODE = 11;
    191 var DOM_NOTATION_NODE = 12;
    192 
    193 
    194 
    195 function domGetElementById(document, id) {
    196   return document.getElementById(id);
    197 }
    198 
    199 /**
    200  * Creates a new node in the given document
    201  *
    202  * @param {Document} doc  Target document.
    203  * @param {string} name  Name of new element (i.e. the tag name)..
    204  * @return {Element}  Newly constructed element.
    205  */
    206 function domCreateElement(doc, name) {
    207   return doc.createElement(name);
    208 }
    209 
    210 /**
    211  * Traverses the element nodes in the DOM section underneath the given
    212  * node and invokes the given callback as a method on every element
    213  * node encountered.
    214  *
    215  * @param {Element} node  Parent element of the subtree to traverse.
    216  * @param {Function} callback  Called on each node in the traversal.
    217  */
    218 function domTraverseElements(node, callback) {
    219   var traverser = new DomTraverser(callback);
    220   traverser.run(node);
    221 }
    222 
    223 /**
    224  * A class to hold state for a dom traversal.
    225  * @param {Function} callback  Called on each node in the traversal.
    226  * @constructor
    227  * @class
    228  */
    229 function DomTraverser(callback) {
    230   this.callback_ = callback;
    231 }
    232 
    233 /**
    234  * Processes the dom tree in breadth-first order.
    235  * @param {Element} root  The root node of the traversal.
    236  */
    237 DomTraverser.prototype.run = function(root) {
    238   var me = this;
    239   me.queue_ = [ root ];
    240   while (jsLength(me.queue_)) {
    241     me.process_(me.queue_.shift());
    242   }
    243 }
    244 
    245 /**
    246  * Processes a single node.
    247  * @param {Element} node  The current node of the traversal.
    248  */
    249 DomTraverser.prototype.process_ = function(node) {
    250   var me = this;
    251 
    252   me.callback_(node);
    253 
    254   for (var c = node.firstChild; c; c = c.nextSibling) {
    255     if (c.nodeType == DOM_ELEMENT_NODE) {
    256       me.queue_.push(c);
    257     }
    258   }
    259 }
    260 
    261 /**
    262  * Get an attribute from the DOM.  Simple redirect, exists to compress code.
    263  *
    264  * @param {Element} node  Element to interrogate.
    265  * @param {string} name  Name of parameter to extract.
    266  * @return {string|null}  Resulting attribute.
    267  */
    268 function domGetAttribute(node, name) {
    269   return node.getAttribute(name);
    270   // NOTE(mesch): Neither in IE nor in Firefox, HTML DOM attributes
    271   // implement namespaces. All items in the attribute collection have
    272   // null localName and namespaceURI attribute values. In IE, we even
    273   // encounter DIV elements that don't implement the method
    274   // getAttributeNS().
    275 }
    276 
    277 
    278 /**
    279  * Set an attribute in the DOM.  Simple redirect to compress code.
    280  *
    281  * @param {Element} node  Element to interrogate.
    282  * @param {string} name  Name of parameter to set.
    283  * @param {string|number} value  Set attribute to this value.
    284  */
    285 function domSetAttribute(node, name, value) {
    286   node.setAttribute(name, value);
    287 }
    288 
    289 /**
    290  * Remove an attribute from the DOM.  Simple redirect to compress code.
    291  *
    292  * @param {Element} node  Element to interrogate.
    293  * @param {string} name  Name of parameter to remove.
    294  */
    295 function domRemoveAttribute(node, name) {
    296   node.removeAttribute(name);
    297 }
    298 
    299 /**
    300  * Clone a node in the DOM.
    301  *
    302  * @param {Node} node  Node to clone.
    303  * @return {Node}  Cloned node.
    304  */
    305 function domCloneNode(node) {
    306   return node.cloneNode(true);
    307   // NOTE(mesch): we never so far wanted to use cloneNode(false),
    308   // hence the default.
    309 }
    310 
    311 /**
    312  * Clone a element in the DOM.
    313  *
    314  * @param {Element} element  Element to clone.
    315  * @return {Element}  Cloned element.
    316  */
    317 function domCloneElement(element) {
    318   return /** @type {Element} */(domCloneNode(element));
    319 }
    320 
    321 /**
    322  * Returns the document owner of the given element. In particular,
    323  * returns window.document if node is null or the browser does not
    324  * support ownerDocument.  If the node is a document itself, returns
    325  * itself.
    326  *
    327  * @param {Node|null|undefined} node  The node whose ownerDocument is required.
    328  * @returns {Document}  The owner document or window.document if unsupported.
    329  */
    330 function ownerDocument(node) {
    331   if (!node) {
    332     return document;
    333   } else if (node.nodeType == DOM_DOCUMENT_NODE) {
    334     return /** @type Document */(node);
    335   } else {
    336     return node.ownerDocument || document;
    337   }
    338 }
    339 
    340 /**
    341  * Creates a new text node in the given document.
    342  *
    343  * @param {Document} doc  Target document.
    344  * @param {string} text  Text composing new text node.
    345  * @return {Text}  Newly constructed text node.
    346  */
    347 function domCreateTextNode(doc, text) {
    348   return doc.createTextNode(text);
    349 }
    350 
    351 /**
    352  * Appends a new child to the specified (parent) node.
    353  *
    354  * @param {Element} node  Parent element.
    355  * @param {Node} child  Child node to append.
    356  * @return {Node}  Newly appended node.
    357  */
    358 function domAppendChild(node, child) {
    359   return node.appendChild(child);
    360 }
    361 
    362 /**
    363  * Sets display to default.
    364  *
    365  * @param {Element} node  The dom element to manipulate.
    366  */
    367 function displayDefault(node) {
    368   node.style[CSS_display] = '';
    369 }
    370 
    371 /**
    372  * Sets display to none. Doing this as a function saves a few bytes for
    373  * the 'style.display' property and the 'none' literal.
    374  *
    375  * @param {Element} node  The dom element to manipulate.
    376  */
    377 function displayNone(node) {
    378   node.style[CSS_display] = 'none';
    379 }
    380 
    381 
    382 /**
    383  * Sets position style attribute to absolute.
    384  *
    385  * @param {Element} node  The dom element to manipulate.
    386  */
    387 function positionAbsolute(node) {
    388   node.style[CSS_position] = 'absolute';
    389 }
    390 
    391 
    392 /**
    393  * Inserts a new child before a given sibling.
    394  *
    395  * @param {Node} newChild  Node to insert.
    396  * @param {Node} oldChild  Sibling node.
    397  * @return {Node}  Reference to new child.
    398  */
    399 function domInsertBefore(newChild, oldChild) {
    400   return oldChild.parentNode.insertBefore(newChild, oldChild);
    401 }
    402 
    403 /**
    404  * Replaces an old child node with a new child node.
    405  *
    406  * @param {Node} newChild  New child to append.
    407  * @param {Node} oldChild  Old child to remove.
    408  * @return {Node}  Replaced node.
    409  */
    410 function domReplaceChild(newChild, oldChild) {
    411   return oldChild.parentNode.replaceChild(newChild, oldChild);
    412 }
    413 
    414 /**
    415  * Removes a node from the DOM.
    416  *
    417  * @param {Node} node  The node to remove.
    418  * @return {Node}  The removed node.
    419  */
    420 function domRemoveNode(node) {
    421   return domRemoveChild(node.parentNode, node);
    422 }
    423 
    424 /**
    425  * Remove a child from the specified (parent) node.
    426  *
    427  * @param {Node} node  Parent element.
    428  * @param {Node} child  Child node to remove.
    429  * @return {Node}  Removed node.
    430  */
    431 function domRemoveChild(node, child) {
    432   return node.removeChild(child);
    433 }
    434 
    435 
    436 /**
    437  * Trim whitespace from begin and end of string.
    438  *
    439  * @see testStringTrim();
    440  *
    441  * @param {string} str  Input string.
    442  * @return {string}  Trimmed string.
    443  */
    444 function stringTrim(str) {
    445   return stringTrimRight(stringTrimLeft(str));
    446 }
    447 
    448 /**
    449  * Trim whitespace from beginning of string.
    450  *
    451  * @see testStringTrimLeft();
    452  *
    453  * @param {string} str  Input string.
    454  * @return {string}  Trimmed string.
    455  */
    456 function stringTrimLeft(str) {
    457   return str.replace(/^\s+/, "");
    458 }
    459 
    460 /**
    461  * Trim whitespace from end of string.
    462  *
    463  * @see testStringTrimRight();
    464  *
    465  * @param {string} str  Input string.
    466  * @return {string}  Trimmed string.
    467   */
    468 function stringTrimRight(str) {
    469   return str.replace(/\s+$/, "");
    470 }
    471