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 return eval('[' + expr + '][0]'); 59 } catch (e) { 60 log('EVAL FAILED ' + expr + ': ' + e); 61 return null; 62 } 63 } 64 65 function jsLength(obj) { 66 return obj.length; 67 } 68 69 function 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 */ 77 function 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 */ 90 function 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 */ 105 function 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 */ 120 function 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 */ 139 function 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 */ 151 function 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 */ 167 function 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>. 176 var DOM_ELEMENT_NODE = 1; 177 var DOM_ATTRIBUTE_NODE = 2; 178 var DOM_TEXT_NODE = 3; 179 var DOM_CDATA_SECTION_NODE = 4; 180 var DOM_ENTITY_REFERENCE_NODE = 5; 181 var DOM_ENTITY_NODE = 6; 182 var DOM_PROCESSING_INSTRUCTION_NODE = 7; 183 var DOM_COMMENT_NODE = 8; 184 var DOM_DOCUMENT_NODE = 9; 185 var DOM_DOCUMENT_TYPE_NODE = 10; 186 var DOM_DOCUMENT_FRAGMENT_NODE = 11; 187 var DOM_NOTATION_NODE = 12; 188 189 190 191 function 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 */ 202 function 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 */ 214 function 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 */ 225 function 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 */ 233 DomTraverser.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 */ 245 DomTraverser.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 */ 264 function 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 */ 281 function 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 */ 291 function 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 */ 301 function 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 */ 313 function 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 */ 326 function 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 */ 343 function 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 */ 354 function 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 */ 363 function 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 */ 373 function 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 */ 383 function 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 */ 395 function 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 */ 406 function 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 */ 416 function 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 */ 427 function 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 */ 440 function 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 */ 452 function 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 */ 464 function stringTrimRight(str) { 465 return str.replace(/\s+$/, ""); 466 } 467