1 /** 2 * Utility class with static methods for manipulating the DOM 3 * 4 * @class DOMUtil 5 */ 6 DOMUtil = { 7 /** Boots old browsers to the upgrade page */ 8 checkDOM: function() { 9 if(!Browser.DOM || !Browser.Ajax) { 10 window.location = "/requirements/error/browser/"; 11 } 12 }, 13 14 /** 15 * Removes all children of the passed node 16 * 17 * @param {Node} The node whose children to remove 18 * @return {Node} 19 */ 20 emptyNode: function(node) { 21 if(node) { 22 if(node.appendTo) { 23 node.empty(); 24 } else { 25 while(node.hasChildNodes()) { 26 var childNode = node.childNodes[node.childNodes.length-1]; 27 28 if(childNode.owner) { 29 var foo = childNode.owner(); 30 31 if(foo.notifyListeners) { 32 foo.notifyListeners("onBeforeRemoveFromDOM"); 33 } 34 } 35 36 node.removeChild(node.childNodes[node.childNodes.length-1]); 37 38 if(foo && foo.notifyListeners) { 39 foo.notifyListeners("onAfterRemoveFromDOM"); 40 } 41 } 42 } 43 44 return node; 45 } 46 }, 47 48 /** 49 * Removes all children of the passed nodes 50 * 51 * @example 52 * <pre><code class="language-javascript"> 53 * DOMUtil.emptyNodes(node1, node2…); 54 * </code></pre> 55 */ 56 emptyNodes: function() { 57 $A(arguments).each(function(element, index) { 58 if(element instanceof Array) { 59 this.emptyNodes(element); 60 } else { 61 this.emptyNode(element); 62 } 63 }.bind(this)); 64 }, 65 66 /** 67 * Creates a node with a textNode as a child 68 * 69 * @param {string} tagName 70 * @param {string} text 71 * @param {Object} attributes 72 * @return {Node} 73 * @deprecated Use DOMUtil#createElement instead 74 */ 75 createTextElement: function(tagName, text, attributes) { 76 var element = this.createElement(tagName, attributes); 77 78 this.append(text ? text : "", element); 79 80 return element; 81 }, 82 83 /** 84 * Shortcut for creating a DOM node 85 * 86 * 87 * @example 88 * <pre><code class="language-javascript"> 89 * // <p>Hello world!</p> 90 * DOMUtil.createElement("p", "Hello world!"); 91 * 92 * // <p class="foo" style="color: red" >Hello world!</p> 93 * DOMUtil.createElement("p", "Hello world!", {className: "foo", style: {color: "red"}}); 94 * 95 * // <p>Contents as array</p> 96 * DOMUtil.createElement("p", ["Contents", " ", "as", " ", "array"]); 97 * </code></pre> 98 * @param {string} Tag name 99 * @param {Object} The contents of the output node - a string, DOM Node or bbq.gui.GUIWidget 100 * @param {Object} Attributes that will be applied to the output of this function 101 * @return {Node} 102 */ 103 createElement: function(arg1, arg2, arg3) { 104 var tagName = arg1; 105 var attributes; 106 var content; 107 108 if(arg2 && (Object.isString(arg2) || arg2.appendChild || Object.isArray(arg2))) { 109 content = arg2; 110 attributes = arg3; 111 112 return DOMUtil.createTextElement(tagName, content, attributes); 113 } else { 114 attributes = arg2; 115 } 116 117 var element = $(document.createElement(tagName)); 118 this.applyAttributesToElement(element, attributes); 119 120 return element; 121 }, 122 123 /** 124 * Creates a table row 125 * 126 * @param {Number} numCells 127 * @param {boolean} header 128 * @return {Node} 129 */ 130 createTableRow: function(numCells, header) { 131 var row = document.createElement("tr"); 132 133 for(var i = 0; i < numCells; i++) { 134 row.appendChild(document.createElement((header ? "th" : "td"))); 135 } 136 137 return row; 138 }, 139 140 /** 141 * Creates a row of empty table header cells 142 * 143 * @param {Number} numCells 144 * @return {Node} 145 */ 146 createTableHeaderRow: function(numCells) { 147 return this.createTableRow(numCells, true); 148 }, 149 150 /** 151 * Creates a single line HTML text input 152 * 153 * @param {String} nodeValue 154 * @param {Object} options 155 * @return {Node} 156 */ 157 createTextInputNode: function(nodeValue, options) { 158 var element = this.createElement("input", options); 159 element.type = "text"; 160 element.value = (nodeValue == null ? "" : nodeValue); 161 162 return element; 163 }, 164 165 /** 166 * Creates a HTML password input 167 * 168 * @param {String} nodeValue 169 * @param {Object} options 170 * @return {Node} 171 */ 172 createPasswordInputNode: function(nodeValue, options) { 173 var element = this.createTextInputNode(nodeValue, options); 174 element.type = "password"; 175 176 return element; 177 }, 178 179 /** 180 * Creates a HTML submit button 181 * 182 * @param {String} nodeValue 183 * @param {Object} options 184 * @return {Node} 185 */ 186 createSubmitInputNode: function(nodeValue, options) { 187 var element = this.createTextInputNode(nodeValue, options); 188 element.type = "submit"; 189 element.value = (nodeValue == null ? "" : nodeValue ); 190 191 return element; 192 }, 193 194 /** 195 * Creates a HTML password input 196 * 197 * @param {Node} element Node to apply attributes to 198 * @param {Object} attributes Associative array of attributes to apply to the passed node 199 * @return {void} 200 */ 201 applyAttributesToElement: function(element, attributes) { 202 if(attributes) { 203 for(var key in attributes) { 204 if(key == "style") { 205 for(s_key in attributes[key]) { 206 element.style[s_key] = attributes[key][s_key]; 207 } 208 } else { 209 element[key] = attributes[key]; 210 } 211 } 212 } 213 }, 214 215 createNullForm: function(attributes) { 216 var element = document.createElement("form"); 217 218 this.applyAttributesToElement(element, attributes); 219 220 element.action = "."; 221 element.method = "get"; 222 element.onsubmit = function() {return false}; 223 224 return element; 225 }, 226 227 /** 228 * Adds a CSS class to the passed node 229 * 230 * @param {Node} node 231 * @param {string} newClass 232 * @return {void} 233 */ 234 addClass: function(node, newClass) { 235 if(node && newClass) { 236 if(newClass instanceof Array) { 237 for(var i = 0, iCount=newClass.length; i < iCount; i++) { 238 this.addClass(node, newClass[i]); 239 } 240 } else if(node.addClass) { 241 node.addClass(newClass); 242 } else if(!DOMUtil.hasClass(node, newClass)) { 243 Element.addClassName(node, newClass); 244 } 245 } 246 }, 247 248 /** 249 * Removes a CSS class to the passed node, optionally recursing through it's child nodes 250 * 251 * @param {Node} node 252 * @param {String} oldClass 253 * @param {boolean} recursive 254 * @return {void} 255 */ 256 removeClass: function(node, oldClass, recursive) { 257 if(!node) { 258 return; 259 } 260 261 if(oldClass instanceof Array) { 262 for(var i = 0; i < oldClass.length; i++) { 263 this.removeClass(node, oldClass[i], recursive); 264 } 265 } else if(recursive) { 266 this.recursivelyRemoveClasses(node, oldClass); 267 } else { 268 Element.removeClassName(node, oldClass); 269 } 270 }, 271 272 removeClasses: function(nodes, oldClass) { 273 for(var i = 0; i < nodes.length; i++) { 274 this.removeClass(nodes[i], oldClass); 275 } 276 }, 277 278 recursivelyRemoveClasses: function(node, oldClass) { 279 if(node && node.hasChildNodes()) { 280 this.removeClass(node, oldClass); 281 282 for(var i = 0; i < node.childNodes.length; i++) { 283 if(node.childNodes[i].hasChildNodes()) { 284 this.recursivelyRemoveClasses(node.childNodes[i], oldClass); 285 } 286 } 287 } 288 }, 289 290 /** 291 * Returns the iFrameDocument object from the passed iFrame in a browser neutral way 292 * 293 * @param {Node} iframe 294 * @return {Node} 295 */ 296 getIFrameDocument: function(iframe) { 297 var oDoc = false; 298 299 if(iframe.contentWindow) { 300 Log.info("found iframe.contentWindow"); 301 oDoc = iframe.contentWindow; 302 } else if(iframe.contentDocument) { 303 Log.info("found iframe.contentDocument"); 304 oDoc = iframe.contentDocument; 305 } 306 307 if(oDoc.document) { 308 Log.info("found oDoc.document"); 309 oDoc = oDoc.document; 310 } 311 312 return oDoc; 313 }, 314 315 /** 316 * Sets a CSS style on the passed node to the passed value 317 * @param {Node} element 318 * @param {Object} styleToSet 319 * @param {Object} value 320 */ 321 setStyle: function(element, styleToSet, value) { 322 try { 323 if(element) { 324 if(element.appendChild) { 325 element.style[styleToSet] = value; 326 } else if(element.appendTo) { 327 element.setStyle(styleToSet, value); 328 } 329 } 330 } catch(e) { 331 Log.error("Error setting style " + styleToSet + " to value " + value + " on element " + element, e); 332 } 333 }, 334 335 /** 336 * @param {Object} element 337 * @param {Object} styleToGet 338 */ 339 getStyle: function(element, styleToGet) { 340 try { 341 if(element.owner) { 342 // get root node of GUIWidget 343 element = element.owner().getRootNode(); 344 } 345 346 if(element.currentStyle) { 347 return element.currentStyle[styleToGet]; 348 } else if (window.getComputedStyle) { 349 return document.defaultView.getComputedStyle(element, null).getPropertyValue(styleToGet); 350 } 351 352 if(element && element.style) { 353 return element.style[styleToGet]; 354 } 355 } catch(e) { 356 Log.error("Error getting style " + styleToGet + " from element " + element, e); 357 } 358 }, 359 360 /** 361 * @param {Node} 362 * @param {string} className 363 * @return {boolean} 364 */ 365 hasClass: function(node, className) { 366 var hasClass = false; 367 368 if(node && node.className) { 369 // broken 370 //Element.hasClassName(node, className); 371 372 var elementClassName = node.className; 373 elementClassName = elementClassName.replace(/\s+/g, " "); 374 375 elementClassName.split(" ").each(function(nodeClassName) { 376 if(className == nodeClassName) { 377 hasClass = true; 378 } 379 }); 380 } 381 382 return hasClass; 383 }, 384 385 /** 386 * @param {Object} headerArray 387 * @param {Object} attributes 388 * @return {Node} 389 */ 390 createTable: function(headerArray, attributes) { 391 if(typeof(attributes) == "undefined") { 392 attributes = {}; 393 } 394 395 var table = DOMUtil.createElement("table", attributes); 396 397 if(headerArray instanceof Array && headerArray.length > 0) { 398 var thead = DOMUtil.createElement("thead"); 399 thead.appendChild(DOMUtil._processRow(headerArray, "th")); 400 table.appendChild(thead); 401 } 402 403 return table; 404 }, 405 406 /** 407 * @param {Node} table 408 * @param {Object} rowData 409 */ 410 addRowToTable: function(table, rowData) { 411 var tbody = table.getElementsByTagName("tbody")[0]; 412 413 if(!tbody) { 414 tbody = document.createElement("tbody"); 415 table.appendChild(tbody); 416 } 417 418 tbody.appendChild(DOMUtil._processRow(rowData, "td")); 419 }, 420 421 _processRow: function(rowData, cellType) { 422 var row = document.createElement("tr"); 423 424 for(var i = 0; i < rowData.length; i++) { 425 var headerCell = document.createElement(cellType); 426 427 if(rowData[i]) { 428 if(rowData[i].appendTo) { 429 rowData[i].appendTo(headerCell); 430 } else if(rowData[i].appendChild) { 431 headerCell.appendChild(rowData[i]); 432 } else { 433 headerCell.appendChild(document.createTextNode(rowData[i])); 434 } 435 } else { 436 headerCell.appendChild(document.createTextNode(" ")); 437 } 438 439 row.appendChild(headerCell); 440 } 441 442 return row; 443 }, 444 445 /** 446 * @param {Array} values 447 * @param {Array} attributes 448 * @return {Node} 449 */ 450 createDefinitionList: function(values, attributes) { 451 var definitions = []; 452 453 for(var key in values) { 454 definitions.push(DOMUtil.createTextElement("dt", key, attributes[key])); 455 definitions.push(DOMUtil.createTextElement("dd", values[key], attributes[key])) 456 } 457 458 return DOMUtil.createTextElement("dl", definitions); 459 }, 460 461 _findSibling: function(node, type) { 462 var sibling = null; 463 464 while(true) { 465 sibling = node[type + "Sibling"]; 466 467 if(!sibling) { 468 return sibling; 469 } 470 471 if(sibling.nodeType != 3) { 472 return $(sibling); 473 } 474 475 node = sibling; 476 } 477 }, 478 479 getOccupiedDimensions: function(node) { 480 if(Object.isFunction(node.getRootNode)) { 481 node = node.getRootNode(); 482 } 483 484 node = $(node); 485 486 var dims = { 487 height: 0, 488 width: 0 489 }; 490 491 if(!node.getHeight || !node.getWidth) { 492 return dims; 493 } 494 495 dims.height = node.getHeight(); 496 dims.width = node.getWidth(); 497 498 var nextSibling = DOMUtil._findSibling(node, "next"); 499 500 // find node siblings - if margins have been set we need to adjust the dimensions 501 if(nextSibling) { 502 503 // occupied size is affected by margins 504 if(node.getStyle("marginBottom")) { 505 var nodeMarginBotton = parseInt(node.getStyle("marginBottom")); 506 var nextSiblingMarginTop = parseInt(nextSibling.getStyle("marginTop")); 507 508 // only add if the next node's marginTop does not trump our marginBottom 509 if(nodeMarginBotton > nextSiblingMarginTop) { 510 dims.height += nodeMarginBotton; 511 } 512 } 513 514 if(node.getStyle("marginRight")) { 515 var nodeMarginRight = parseInt(node.getStyle("marginRight")); 516 var nextSiblingMarginLeft = parseInt(nextSibling.getStyle("marginLeft")); 517 518 // only add if the next node's marginTop does not trump our marginBottom 519 if(nodeMarginRight > nextSiblingMarginLeft) { 520 dims.width += nodeMarginRight; 521 } 522 } 523 } 524 525 var previousSibling = DOMUtil._findSibling(node, "previous"); 526 527 if(previousSibling) { 528 529 // occupied size is affected by margins 530 if(node.getStyle("marginTop")) { 531 var nodeMarginTop = parseInt(node.getStyle("marginTop")); 532 var previousSiblingMarginBottom = parseInt(previousSibling.getStyle("marginBottom")); 533 534 // only add if the next node's marginTop does not trump our marginBottom 535 if(nodeMarginTop > previousSiblingMarginBottom) { 536 dims.height += nodeMarginTop; 537 } 538 } 539 540 if(node.getStyle("marginLeft")) { 541 var nodeMarginLeft = parseInt(node.getStyle("marginLeft")); 542 var previousSiblingMarginBottom = parseInt(previousSibling.getStyle("marginRight")); 543 544 // only add if the next node's marginTop does not trump our marginBottom 545 if(nodeMarginLeft > previousSiblingMarginBottom) { 546 dims.width += nodeMarginLeft; 547 } 548 } 549 } 550 551 return dims; 552 }, 553 554 /* 555 * Returns the dimensions occupied by an element on screen, including padding 556 557 getOccupiedDimensions: function(node) { 558 var dims = {height: 0, width: 0}; 559 560 if(!(node.getStyle instanceof Function)) { 561 return dims; 562 } 563 564 dims.height = parseInt(node.getStyle("height")); 565 dims.height = DOMUtil._addStyleCalculation(dims.height, node, "borderTopWidth"); 566 dims.height = DOMUtil._addStyleCalculation(dims.height, node, "borderBottomWidth"); 567 dims.height = DOMUtil._addStyleCalculation(dims.height, node, "marginTop"); 568 dims.height = DOMUtil._minusStyleCalculation(dims.height, node, "marginBottom"); 569 dims.height = DOMUtil._addStyleCalculation(dims.height, node, "paddingTop"); 570 dims.height = DOMUtil._addStyleCalculation(dims.height, node, "paddingBottom"); 571 572 dims.width = parseInt(node.getStyle("width")); 573 dims.width = DOMUtil._addStyleCalculation(dims.width, node, "borderLeftWidth"); 574 dims.width = DOMUtil._addStyleCalculation(dims.width, node, "borderRightWidth"); 575 dims.width = DOMUtil._minusStyleCalculation(dims.width, node, "marginLeft"); 576 dims.width = DOMUtil._minusStyleCalculation(dims.width, node, "marginRight"); 577 dims.width = DOMUtil._addStyleCalculation(dims.width, node, "paddingLeft"); 578 dims.width = DOMUtil._addStyleCalculation(dims.width, node, "paddingRight"); 579 580 return dims; 581 }, 582 583 _addStyleCalculation: function(dim, node, style) { 584 return DOMUtil._doStyleCalculation(dim, node, style, true); 585 }, 586 587 _minusStyleCalculation: function(dim, node, style) { 588 return DOMUtil._doStyleCalculation(dim, node, style, false); 589 }, 590 591 _doStyleCalculation: function(dim, node, style, add) { 592 var value = parseInt(node.getStyle(style)); 593 594 if(!isNaN(value)) { 595 if(add) { 596 dim += value; 597 } else { 598 dim -= value; 599 } 600 } 601 602 return dim; 603 }, */ 604 605 /** 606 * Returns true if the passed node is in the DOM 607 * 608 * @param {Node} a DOM Node 609 * @returns {boolean} 610 */ 611 isInDOM: function(node) { 612 while(true) { 613 if(!node.parentNode) { 614 return false; 615 } else if(node.parentNode.tagName == "BODY") { 616 return true; 617 } 618 619 node = node.parentNode; 620 } 621 }, 622 623 append: function(what, toNode) { 624 if(Object.isString(what) || Object.isNumber(what)) { 625 // string or number 626 toNode.appendChild(document.createTextNode(what)); 627 } else if(what.appendTo) { 628 // GUIWidget 629 what.appendTo(toNode); 630 } else if(what.appendChild) { 631 // Node 632 toNode.appendChild(what); 633 } else if(Object.isArray(what)) { 634 // Array of items 635 what.each(function(item) { 636 DOMUtil.append(item, toNode); 637 }); 638 } 639 }, 640 641 elementWasClickedOn: function(event, element) { 642 var position = Element.positionedOffset(element); 643 var dims = Element.getDimensions(element); 644 var mouseX = Event.pointerX(event); 645 var mouseY = Event.pointerY(event); 646 var inBoxHorizontal = mouseX > position.left && mouseX < position.left + dims.width; 647 var inBoxVertical = mouseY > position.top && mouseY < position.top + dims.height; 648 649 return inBoxHorizontal && inBoxVertical; 650 } 651 } 652