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