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