1 /* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to you under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /** 18 * @class 19 * @name _Dom 20 * @memberOf myfaces._impl._util 21 * @extends myfaces._impl.core._Runtime 22 * @description Object singleton collection of dom helper routines 23 * (which in later incarnations will 24 * get browser specific speed optimizations) 25 * 26 * Since we have to be as tight as possible 27 * we will focus with our dom routines to only 28 * the parts which our impl uses. 29 * A jquery like query API would be nice 30 * but this would increase up our codebase significantly 31 * 32 * <p>This class provides the proper fallbacks for ie8- and Firefox 3.6-</p> 33 */ 34 _MF_SINGLTN(_PFX_UTIL + "_Dom", Object, /** @lends myfaces._impl._util._Dom.prototype */ { 35 36 /*table elements which are used in various parts */ 37 TABLE_ELEMS: { 38 "thead": 1, 39 "tbody": 1, 40 "tr": 1, 41 "th": 1, 42 "td": 1, 43 "tfoot" : 1 44 }, 45 46 _Lang: myfaces._impl._util._Lang, 47 _RT: myfaces._impl.core._Runtime, 48 _dummyPlaceHolder:null, 49 50 /** 51 * standard constructor 52 */ 53 constructor_: function() { 54 }, 55 56 runCss: function(item/*, xmlData*/) { 57 58 var UDEF = "undefined", 59 _RT = this._RT, 60 _Lang = this._Lang, 61 applyStyle = function(item, style) { 62 var newSS = document.createElement("style"); 63 64 newSS.setAttribute("rel", item.getAttribute("rel") || "stylesheet"); 65 newSS.setAttribute("type", item.getAttribute("type") || "text/css"); 66 document.getElementsByTagName("head")[0].appendChild(newSS); 67 //ie merrily again goes its own way 68 if (window.attachEvent && !_RT.isOpera && UDEF != typeof newSS.styleSheet && UDEF != newSS.styleSheet.cssText) newSS.styleSheet.cssText = style; 69 else newSS.appendChild(document.createTextNode(style)); 70 }, 71 72 execCss = function(item) { 73 var equalsIgnoreCase = _Lang.equalsIgnoreCase; 74 var tagName = item.tagName; 75 if (tagName && equalsIgnoreCase(tagName, "link") && equalsIgnoreCase(item.getAttribute("type"), "text/css")) { 76 applyStyle(item, "@import url('" + item.getAttribute("href") + "');"); 77 } else if (tagName && equalsIgnoreCase(tagName, "style") && equalsIgnoreCase(item.getAttribute("type"), "text/css")) { 78 var innerText = []; 79 //compliant browsers know child nodes 80 var childNodes = item.childNodes; 81 if (childNodes) { 82 var len = childNodes.length; 83 for (var cnt = 0; cnt < len; cnt++) { 84 innerText.push(childNodes[cnt].innerHTML || childNodes[cnt].data); 85 } 86 //non compliant ones innerHTML 87 } else if (item.innerHTML) { 88 innerText.push(item.innerHTML); 89 } 90 91 applyStyle(item, innerText.join("")); 92 } 93 }; 94 95 try { 96 var scriptElements = this.findByTagNames(item, {"link":1,"style":1}, true); 97 if (scriptElements == null) return; 98 for (var cnt = 0; cnt < scriptElements.length; cnt++) { 99 execCss(scriptElements[cnt]); 100 } 101 102 } finally { 103 //the usual ie6 fix code 104 //the IE6 garbage collector is broken 105 //nulling closures helps somewhat to reduce 106 //mem leaks, which are impossible to avoid 107 //at this browser 108 execCss = null; 109 applyStyle = null; 110 } 111 }, 112 113 114 /** 115 * Run through the given Html item and execute the inline scripts 116 * (IE doesn't do this by itself) 117 * @param {Node} item 118 */ 119 runScripts: function(item, xmlData) { 120 var _Lang = this._Lang, 121 _RT = this._RT, 122 finalScripts = [], 123 execScrpt = function(item) { 124 var tagName = item.tagName; 125 var itemType = item.type || ""; 126 if(tagName && _Lang.equalsIgnoreCase(tagName, "script") && 127 (itemType === "" || _Lang.equalsIgnoreCase(itemType,"text/javascript") || 128 _Lang.equalsIgnoreCase(itemType,"javascript") || 129 _Lang.equalsIgnoreCase(itemType,"text/ecmascript") || 130 _Lang.equalsIgnoreCase(itemType,"ecmascript"))) { 131 var src = item.getAttribute('src'); 132 if ('undefined' != typeof src 133 && null != src 134 && src.length > 0 135 ) { 136 //we have to move this into an inner if because chrome otherwise chokes 137 //due to changing the and order instead of relying on left to right 138 //if jsf.js is already registered we do not replace it anymore 139 if ((src.indexOf("ln=scripts") == -1 && src.indexOf("ln=javax.faces") == -1) || (src.indexOf("/jsf.js") == -1 140 && src.indexOf("/jsf-uncompressed.js") == -1)) { 141 if (finalScripts.length) { 142 //script source means we have to eval the existing 143 //scripts before running the include 144 _RT.globalEval(finalScripts.join("\n")); 145 146 finalScripts = []; 147 } 148 _RT.loadScriptEval(src, item.getAttribute('type'), false, "UTF-8", false); 149 } 150 151 } else { 152 // embedded script auto eval 153 var test = (!xmlData) ? item.text : _Lang.serializeChilds(item); 154 var go = true; 155 while (go) { 156 go = false; 157 if (test.substring(0, 1) == " ") { 158 test = test.substring(1); 159 go = true; 160 } 161 if (test.substring(0, 4) == "<!--") { 162 test = test.substring(4); 163 go = true; 164 } 165 if (test.substring(0, 11) == "//<![CDATA[") { 166 test = test.substring(11); 167 go = true; 168 } 169 } 170 // we have to run the script under a global context 171 //we store the script for less calls to eval 172 finalScripts.push(test); 173 174 } 175 } 176 }; 177 try { 178 var scriptElements = this.findByTagName(item, "script", true); 179 if (scriptElements == null) return; 180 for (var cnt = 0; cnt < scriptElements.length; cnt++) { 181 execScrpt(scriptElements[cnt]); 182 } 183 if (finalScripts.length) { 184 _RT.globalEval(finalScripts.join("\n")); 185 } 186 } catch (e) { 187 //we are now in accordance with the rest of the system of showing errors only in development mode 188 //the default error output is alert we always can override it with 189 //window.myfaces = window.myfaces || {}; 190 //myfaces.config = myfaces.config || {}; 191 //myfaces.config.defaultErrorOutput = console.error; 192 if(jsf.getProjectStage() === "Development") { 193 var defaultErrorOutput = myfaces._impl.core._Runtime.getGlobalConfig("defaultErrorOutput", alert); 194 defaultErrorOutput("Error in evaluated javascript:"+ (e.message || e.description || e)); 195 } 196 } finally { 197 //the usual ie6 fix code 198 //the IE6 garbage collector is broken 199 //nulling closures helps somewhat to reduce 200 //mem leaks, which are impossible to avoid 201 //at this browser 202 execScrpt = null; 203 } 204 }, 205 206 207 /** 208 * determines to fetch a node 209 * from its id or name, the name case 210 * only works if the element is unique in its name 211 * @param {String} elem 212 */ 213 byIdOrName: function(elem) { 214 if (!elem) return null; 215 if (!this._Lang.isString(elem)) return elem; 216 217 var ret = this.byId(elem); 218 if (ret) return ret; 219 //we try the unique name fallback 220 var items = document.getElementsByName(elem); 221 return ((items.length == 1) ? items[0] : null); 222 }, 223 224 /** 225 * node id or name, determines the valid form identifier of a node 226 * depending on its uniqueness 227 * 228 * Usually the id is chosen for an elem, but if the id does not 229 * exist we try a name fallback. If the passed element has a unique 230 * name we can use that one as subsequent identifier. 231 * 232 * 233 * @param {String} elem 234 */ 235 nodeIdOrName: function(elem) { 236 if (elem) { 237 //just to make sure that the pas 238 239 elem = this.byId(elem); 240 if (!elem) return null; 241 //detached element handling, we also store the element name 242 //to get a fallback option in case the identifier is not determinable 243 // anymore, in case of a framework induced detachment the element.name should 244 // be shared if the identifier is not determinable anymore 245 //the downside of this method is the element name must be unique 246 //which in case of jsf it is 247 var elementId = elem.id || elem.name; 248 if ((elem.id == null || elem.id == '') && elem.name) { 249 elementId = elem.name; 250 251 //last check for uniqueness 252 if (document.getElementsByName(elementId).length > 1) { 253 //no unique element name so we need to perform 254 //a return null to let the caller deal with this issue 255 return null; 256 } 257 } 258 return elementId; 259 } 260 return null; 261 }, 262 263 deleteItems: function(items) { 264 if (! items || ! items.length) return; 265 for (var cnt = 0; cnt < items.length; cnt++) { 266 this.deleteItem(items[cnt]); 267 } 268 }, 269 270 /** 271 * Simple delete on an existing item 272 */ 273 deleteItem: function(itemIdToReplace) { 274 var item = this.byId(itemIdToReplace); 275 if (!item) { 276 throw this._Lang.makeException(new Error(),null, null, this._nameSpace, "deleteItem", "_Dom.deleteItem Unknown Html-Component-ID: " + itemIdToReplace); 277 } 278 279 this._removeNode(item, false); 280 }, 281 282 /** 283 * creates a node upon a given node name 284 * @param nodeName {String} the node name to be created 285 * @param attrs {Array} a set of attributes to be set 286 */ 287 createElement: function(nodeName, attrs) { 288 var ret = document.createElement(nodeName); 289 if (attrs) { 290 for (var key in attrs) { 291 if(!attrs.hasOwnProperty(key)) continue; 292 this.setAttribute(ret, key, attrs[key]); 293 } 294 } 295 return ret; 296 }, 297 298 /** 299 * Checks whether the browser is dom compliant. 300 * Dom compliant means that it performs the basic dom operations safely 301 * without leaking and also is able to perform a native setAttribute 302 * operation without freaking out 303 * 304 * 305 * Not dom compliant browsers are all microsoft browsers in quirks mode 306 * and ie6 and ie7 to some degree in standards mode 307 * and pretty much every browser who cannot create ranges 308 * (older mobile browsers etc...) 309 * 310 * We dont do a full browser detection here because it probably is safer 311 * to test for existing features to make an assumption about the 312 * browsers capabilities 313 */ 314 isDomCompliant: function() { 315 return true; 316 }, 317 318 /** 319 * proper insert before which takes tables into consideration as well as 320 * browser deficiencies 321 * @param item the node to insert before 322 * @param markup the markup to be inserted 323 */ 324 insertBefore: function(item, markup) { 325 this._assertStdParams(item, markup, "insertBefore"); 326 327 markup = this._Lang.trim(markup); 328 if (markup === "") return null; 329 330 var evalNodes = this._buildEvalNodes(item, markup), 331 currentRef = item, 332 parentNode = item.parentNode, 333 ret = []; 334 for (var cnt = evalNodes.length - 1; cnt >= 0; cnt--) { 335 currentRef = parentNode.insertBefore(evalNodes[cnt], currentRef); 336 ret.push(currentRef); 337 } 338 ret = ret.reverse(); 339 this._eval(ret); 340 return ret; 341 }, 342 343 /** 344 * proper insert before which takes tables into consideration as well as 345 * browser deficiencies 346 * @param item the node to insert before 347 * @param markup the markup to be inserted 348 */ 349 insertAfter: function(item, markup) { 350 this._assertStdParams(item, markup, "insertAfter"); 351 markup = this._Lang.trim(markup); 352 if (markup === "") return null; 353 354 var evalNodes = this._buildEvalNodes(item, markup), 355 currentRef = item, 356 parentNode = item.parentNode, 357 ret = []; 358 359 for (var cnt = 0; cnt < evalNodes.length; cnt++) { 360 if (currentRef.nextSibling) { 361 //Winmobile 6 has problems with this strategy, but it is not really fixable 362 currentRef = parentNode.insertBefore(evalNodes[cnt], currentRef.nextSibling); 363 } else { 364 currentRef = parentNode.appendChild(evalNodes[cnt]); 365 } 366 ret.push(currentRef); 367 } 368 this._eval(ret); 369 return ret; 370 }, 371 372 propertyToAttribute: function(name) { 373 if (name === 'className') { 374 return 'class'; 375 } else if (name === 'xmllang') { 376 return 'xml:lang'; 377 } else { 378 return name.toLowerCase(); 379 } 380 }, 381 382 isFunctionNative: function(func) { 383 return /^\s*function[^{]+{\s*\[native code\]\s*}\s*$/.test(String(func)); 384 }, 385 386 detectAttributes: function(element) { 387 //test if 'hasAttribute' method is present and its native code is intact 388 //for example, Prototype can add its own implementation if missing 389 if (element.hasAttribute && this.isFunctionNative(element.hasAttribute)) { 390 return function(name) { 391 return element.hasAttribute(name); 392 } 393 } else { 394 try { 395 //when accessing .getAttribute method without arguments does not throw an error then the method is not available 396 element.getAttribute; 397 398 var html = element.outerHTML; 399 var startTag = html.match(/^<[^>]*>/)[0]; 400 return function(name) { 401 return startTag.indexOf(name + '=') > -1; 402 } 403 } catch (ex) { 404 return function(name) { 405 return element.getAttribute(name); 406 } 407 } 408 } 409 }, 410 411 /** 412 * copy all attributes from one element to another - except id 413 * @param target element to copy attributes to 414 * @param source element to copy attributes from 415 * @ignore 416 */ 417 cloneAttributes: function(target, source) { 418 419 // enumerate core element attributes - without 'dir' as special case 420 var coreElementProperties = ['className', 'title', 'lang', 'xmllang']; 421 // enumerate additional input element attributes 422 var inputElementProperties = [ 423 'name', 'value', 'size', 'maxLength', 'src', 'alt', 'useMap', 'tabIndex', 'accessKey', 'accept', 'type' 424 ]; 425 // enumerate additional boolean input attributes 426 var inputElementBooleanProperties = [ 427 'checked', 'disabled', 'readOnly' 428 ]; 429 430 // Enumerate all the names of the event listeners 431 var listenerNames = 432 [ 'onclick', 'ondblclick', 'onmousedown', 'onmousemove', 'onmouseout', 433 'onmouseover', 'onmouseup', 'onkeydown', 'onkeypress', 'onkeyup', 434 'onhelp', 'onblur', 'onfocus', 'onchange', 'onload', 'onunload', 'onabort', 435 'onreset', 'onselect', 'onsubmit' 436 ]; 437 438 var sourceAttributeDetector = this.detectAttributes(source); 439 var targetAttributeDetector = this.detectAttributes(target); 440 441 var isInputElement = target.nodeName.toLowerCase() === 'input'; 442 var propertyNames = isInputElement ? coreElementProperties.concat(inputElementProperties) : coreElementProperties; 443 var isXML = !source.ownerDocument.contentType || source.ownerDocument.contentType == 'text/xml'; 444 for (var iIndex = 0, iLength = propertyNames.length; iIndex < iLength; iIndex++) { 445 var propertyName = propertyNames[iIndex]; 446 var attributeName = this.propertyToAttribute(propertyName); 447 if (sourceAttributeDetector(attributeName)) { 448 449 //With IE 7 (quirks or standard mode) and IE 8/9 (quirks mode only), 450 //you cannot get the attribute using 'class'. You must use 'className' 451 //which is the same value you use to get the indexed property. The only 452 //reliable way to detect this (without trying to evaluate the browser 453 //mode and version) is to compare the two return values using 'className' 454 //to see if they exactly the same. If they are, then use the property 455 //name when using getAttribute. 456 if( attributeName == 'class'){ 457 if( this._RT.browser.isIE && (source.getAttribute(propertyName) === source[propertyName]) ){ 458 attributeName = propertyName; 459 } 460 } 461 462 var newValue = isXML ? source.getAttribute(attributeName) : source[propertyName]; 463 var oldValue = target[propertyName]; 464 if (oldValue != newValue) { 465 target[propertyName] = newValue; 466 } 467 } else { 468 target.removeAttribute(attributeName); 469 if (attributeName == "value") { 470 target[propertyName] = ''; 471 } 472 } 473 } 474 475 var booleanPropertyNames = isInputElement ? inputElementBooleanProperties : []; 476 for (var jIndex = 0, jLength = booleanPropertyNames.length; jIndex < jLength; jIndex++) { 477 var booleanPropertyName = booleanPropertyNames[jIndex]; 478 var newBooleanValue = source[booleanPropertyName]; 479 var oldBooleanValue = target[booleanPropertyName]; 480 if (oldBooleanValue != newBooleanValue) { 481 target[booleanPropertyName] = newBooleanValue; 482 } 483 } 484 485 //'style' attribute special case 486 if (sourceAttributeDetector('style')) { 487 var newStyle; 488 var oldStyle; 489 if (this._RT.browser.isIE) { 490 newStyle = source.style.cssText; 491 oldStyle = target.style.cssText; 492 if (newStyle != oldStyle) { 493 target.style.cssText = newStyle; 494 } 495 } else { 496 newStyle = source.getAttribute('style'); 497 oldStyle = target.getAttribute('style'); 498 if (newStyle != oldStyle) { 499 target.setAttribute('style', newStyle); 500 } 501 } 502 } else if (targetAttributeDetector('style')){ 503 target.removeAttribute('style'); 504 } 505 506 // Special case for 'dir' attribute 507 if (!this._RT.browser.isIE && source.dir != target.dir) { 508 if (sourceAttributeDetector('dir')) { 509 target.dir = source.dir; 510 } else if (targetAttributeDetector('dir')) { 511 target.dir = ''; 512 } 513 } 514 515 for (var lIndex = 0, lLength = listenerNames.length; lIndex < lLength; lIndex++) { 516 var name = listenerNames[lIndex]; 517 target[name] = source[name] ? source[name] : null; 518 if (source[name]) { 519 source[name] = null; 520 } 521 } 522 523 //clone HTML5 data-* attributes 524 try{ 525 var targetDataset = target.dataset; 526 var sourceDataset = source.dataset; 527 if (targetDataset || sourceDataset) { 528 //cleanup the dataset 529 for (var tp in targetDataset) { 530 delete targetDataset[tp]; 531 } 532 //copy dataset's properties 533 for (var sp in sourceDataset) { 534 targetDataset[sp] = sourceDataset[sp]; 535 } 536 } 537 } catch (ex) { 538 //most probably dataset properties are not supported 539 } 540 }, 541 //from 542 // http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/ 543 getCaretPosition:function (ctrl) { 544 var caretPos = 0; 545 546 try { 547 548 // other browsers make it simpler by simply having a selection start element 549 if (ctrl.selectionStart || ctrl.selectionStart == '0') 550 caretPos = ctrl.selectionStart; 551 // ie 5 quirks mode as second option because 552 // this option is flakey in conjunction with text areas 553 // TODO move this into the quirks class 554 else if (document.selection) { 555 ctrl.focus(); 556 var selection = document.selection.createRange(); 557 //the selection now is start zero 558 selection.moveStart('character', -ctrl.value.length); 559 //the caretposition is the selection start 560 caretPos = selection.text.length; 561 } 562 } catch (e) { 563 //now this is ugly, but not supported input types throw errors for selectionStart 564 //this way we are future proof by having not to define every selection enabled 565 //input in an if (which will be a lot in the near future with html5) 566 } 567 return caretPos; 568 }, 569 570 setCaretPosition:function (ctrl, pos) { 571 572 if (ctrl.createTextRange) { 573 var range = ctrl.createTextRange(); 574 range.collapse(true); 575 range.moveEnd('character', pos); 576 range.moveStart('character', pos); 577 range.select(); 578 } 579 //IE quirks mode again, TODO move this into the quirks class 580 else if (ctrl.setSelectionRange) { 581 ctrl.focus(); 582 //the selection range is our caret position 583 ctrl.setSelectionRange(pos, pos); 584 } 585 }, 586 587 /** 588 * outerHTML replacement which works cross browserlike 589 * but still is speed optimized 590 * 591 * @param item the item to be replaced 592 * @param markup the markup for the replacement 593 * @param preserveFocus, tries to preserve the focus within the outerhtml operation 594 * if set to true a focus preservation algorithm based on document.activeElement is 595 * used to preserve the focus at the exactly same location as it was 596 * 597 */ 598 outerHTML : function(item, markup, preserveFocus) { 599 this._assertStdParams(item, markup, "outerHTML"); 600 // we can work on a single element in a cross browser fashion 601 // regarding the focus thanks to the 602 // icefaces team for providing the code 603 if (item.nodeName.toLowerCase() === 'input') { 604 var replacingInput = this._buildEvalNodes(item, markup)[0]; 605 this.cloneAttributes(item, replacingInput); 606 return item; 607 } else { 608 markup = this._Lang.trim(markup); 609 if (markup !== "") { 610 var ret = null; 611 612 var focusElementId = null; 613 var caretPosition = 0; 614 if (preserveFocus && 'undefined' != typeof document.activeElement) { 615 focusElementId = (document.activeElement) ? document.activeElement.id : null; 616 caretPosition = this.getCaretPosition(document.activeElement); 617 } 618 // we try to determine the browsers compatibility 619 // level to standards dom level 2 via various methods 620 if (this.isDomCompliant()) { 621 ret = this._outerHTMLCompliant(item, markup); 622 } else { 623 //call into abstract method 624 ret = this._outerHTMLNonCompliant(item, markup); 625 } 626 if (focusElementId) { 627 var newFocusElement = this.byId(focusElementId); 628 if (newFocusElement && newFocusElement.nodeName.toLowerCase() === 'input') { 629 //just in case the replacement element is not focusable anymore 630 if ("undefined" != typeof newFocusElement.focus) { 631 newFocusElement.focus(); 632 } 633 } 634 if (newFocusElement && caretPosition) { 635 //zero caret position is set automatically on focus 636 this.setCaretPosition(newFocusElement, caretPosition); 637 } 638 } 639 640 // and remove the old item 641 //first we have to save the node newly insert for easier access in our eval part 642 this._eval(ret); 643 return ret; 644 } 645 // and remove the old item, in case of an empty newtag and do nothing else 646 this._removeNode(item, false); 647 return null; 648 } 649 }, 650 651 /** 652 * detaches a set of nodes from their parent elements 653 * in a browser independend manner 654 * @param {Object} items the items which need to be detached 655 * @return {Array} an array of nodes with the detached dom nodes 656 */ 657 detach: function(items) { 658 var ret = []; 659 if ('undefined' != typeof items.nodeType) { 660 if (items.parentNode) { 661 ret.push(items.parentNode.removeChild(items)); 662 } else { 663 ret.push(items); 664 } 665 return ret; 666 } 667 //all ies treat node lists not as arrays so we have to take 668 //an intermediate step 669 var nodeArr = this._Lang.objToArray(items); 670 for (var cnt = 0; cnt < nodeArr.length; cnt++) { 671 ret.push(nodeArr[cnt].parentNode.removeChild(nodeArr[cnt])); 672 } 673 return ret; 674 }, 675 676 _outerHTMLCompliant: function(item, markup) { 677 //table element replacements like thead, tbody etc... have to be treated differently 678 var evalNodes = this._buildEvalNodes(item, markup); 679 680 if (evalNodes.length == 1) { 681 var ret = evalNodes[0]; 682 item.parentNode.replaceChild(ret, item); 683 return ret; 684 } else { 685 return this.replaceElements(item, evalNodes); 686 } 687 }, 688 689 /** 690 * checks if the provided element is a subelement of a table element 691 * @param item 692 */ 693 _isTableElement: function(item) { 694 return !!this.TABLE_ELEMS[(item.nodeName || item.tagName).toLowerCase()]; 695 }, 696 697 /** 698 * non ie browsers do not have problems with embedded scripts or any other construct 699 * we simply can use an innerHTML in a placeholder 700 * 701 * @param markup the markup to be used 702 */ 703 _buildNodesCompliant: function(markup) { 704 var dummyPlaceHolder = this.getDummyPlaceHolder(); //document.createElement("div"); 705 dummyPlaceHolder.innerHTML = markup; 706 return this._Lang.objToArray(dummyPlaceHolder.childNodes); 707 }, 708 709 710 711 712 /** 713 * builds up a correct dom subtree 714 * if the markup is part of table nodes 715 * The usecase for this is to allow subtable rendering 716 * like single rows thead or tbody 717 * 718 * @param item 719 * @param markup 720 */ 721 _buildTableNodes: function(item, markup) { 722 var itemNodeName = (item.nodeName || item.tagName).toLowerCase(); 723 724 var tmpNodeName = itemNodeName; 725 var depth = 0; 726 while (tmpNodeName != "table") { 727 item = item.parentNode; 728 tmpNodeName = (item.nodeName || item.tagName).toLowerCase(); 729 depth++; 730 } 731 732 var dummyPlaceHolder = this.getDummyPlaceHolder(); 733 if (itemNodeName == "td") { 734 dummyPlaceHolder.innerHTML = "<table><tbody><tr>" + markup + "</tr></tbody></table>"; 735 } else { 736 dummyPlaceHolder.innerHTML = "<table>" + markup + "</table>"; 737 } 738 739 for (var cnt = 0; cnt < depth; cnt++) { 740 dummyPlaceHolder = dummyPlaceHolder.childNodes[0]; 741 } 742 743 return this.detach(dummyPlaceHolder.childNodes); 744 }, 745 746 _removeChildNodes: function(node /*, breakEventsOpen */) { 747 if (!node) return; 748 node.innerHTML = ""; 749 }, 750 751 752 753 _removeNode: function(node /*, breakEventsOpen*/) { 754 if (!node) return; 755 var parentNode = node.parentNode; 756 if (parentNode) //if the node has a parent 757 parentNode.removeChild(node); 758 }, 759 760 761 /** 762 * build up the nodes from html markup in a browser independend way 763 * so that it also works with table nodes 764 * 765 * @param item the parent item upon the nodes need to be processed upon after building 766 * @param markup the markup to be built up 767 */ 768 _buildEvalNodes: function(item, markup) { 769 var evalNodes = null; 770 if (this._isTableElement(item)) { 771 evalNodes = this._buildTableNodes(item, markup); 772 } else { 773 var nonIEQuirks = (!this._RT.browser.isIE || this._RT.browser.isIE > 8); 774 //ie8 has a special problem it still has the swallow scripts and other 775 //elements bug, but it is mostly dom compliant so we have to give it a special 776 //treatment, IE9 finally fixes that issue finally after 10 years 777 evalNodes = (this.isDomCompliant() && nonIEQuirks) ? 778 this._buildNodesCompliant(markup) : 779 //ie8 or quirks mode browsers 780 this._buildNodesNonCompliant(markup); 781 } 782 return evalNodes; 783 }, 784 785 /** 786 * we have lots of methods with just an item and a markup as params 787 * this method builds an assertion for those methods to reduce code 788 * 789 * @param item the item to be tested 790 * @param markup the markup 791 * @param caller caller function 792 * @param {optional} params array of assertion param names 793 */ 794 _assertStdParams: function(item, markup, caller, params) { 795 //internal error 796 if (!caller) { 797 throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "_assertStdParams", "Caller must be set for assertion"); 798 } 799 var _Lang = this._Lang, 800 ERR_PROV = "ERR_MUST_BE_PROVIDED1", 801 DOM = "myfaces._impl._util._Dom.", 802 finalParams = params || ["item", "markup"]; 803 804 if (!item || !markup) { 805 _Lang.makeException(new Error(), null, null,DOM, ""+caller, _Lang.getMessage(ERR_PROV, null, DOM +"."+ caller, (!item) ? finalParams[0] : finalParams[1])); 806 //throw Error(_Lang.getMessage(ERR_PROV, null, DOM + caller, (!item) ? params[0] : params[1])); 807 } 808 }, 809 810 /** 811 * internal eval handler used by various functions 812 * @param _nodeArr 813 */ 814 _eval: function(_nodeArr) { 815 if (this.isManualScriptEval()) { 816 var isArr = _nodeArr instanceof Array; 817 if (isArr && _nodeArr.length) { 818 for (var cnt = 0; cnt < _nodeArr.length; cnt++) { 819 this.runScripts(_nodeArr[cnt]); 820 } 821 } else if (!isArr) { 822 this.runScripts(_nodeArr); 823 } 824 } 825 }, 826 827 /** 828 * for performance reasons we work with replaceElement and replaceElements here 829 * after measuring performance it has shown that passing down an array instead 830 * of a single node makes replaceElement twice as slow, however 831 * a single node case is the 95% case 832 * 833 * @param item 834 * @param evalNode 835 */ 836 replaceElement: function(item, evalNode) { 837 //browsers with defect garbage collection 838 item.parentNode.insertBefore(evalNode, item); 839 this._removeNode(item, false); 840 }, 841 842 843 /** 844 * replaces an element with another element or a set of elements 845 * 846 * @param item the item to be replaced 847 * 848 * @param evalNodes the elements 849 */ 850 replaceElements: function (item, evalNodes) { 851 var evalNodesDefined = evalNodes && 'undefined' != typeof evalNodes.length; 852 if (!evalNodesDefined) { 853 throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "replaceElements", this._Lang.getMessage("ERR_REPLACE_EL")); 854 } 855 856 var parentNode = item.parentNode, 857 858 sibling = item.nextSibling, 859 resultArr = this._Lang.objToArray(evalNodes); 860 861 for (var cnt = 0; cnt < resultArr.length; cnt++) { 862 if (cnt == 0) { 863 this.replaceElement(item, resultArr[cnt]); 864 } else { 865 if (sibling) { 866 parentNode.insertBefore(resultArr[cnt], sibling); 867 } else { 868 parentNode.appendChild(resultArr[cnt]); 869 } 870 } 871 } 872 return resultArr; 873 }, 874 875 /** 876 * optimized search for an array of tag names 877 * deep scan will always be performed. 878 * @param fragment the fragment which should be searched for 879 * @param tagNames an map indx of tag names which have to be found 880 * 881 */ 882 findByTagNames: function(fragment, tagNames) { 883 this._assertStdParams(fragment, tagNames, "findByTagNames", ["fragment", "tagNames"]); 884 885 var nodeType = fragment.nodeType; 886 if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null; 887 888 //we can use the shortcut 889 if (fragment.querySelectorAll) { 890 var query = []; 891 for (var key in tagNames) { 892 if(!tagNames.hasOwnProperty(key)) continue; 893 query.push(key); 894 } 895 var res = []; 896 if (fragment.tagName && tagNames[fragment.tagName.toLowerCase()]) { 897 res.push(fragment); 898 } 899 return res.concat(this._Lang.objToArray(fragment.querySelectorAll(query.join(", ")))); 900 } 901 902 //now the filter function checks case insensitively for the tag names needed 903 var filter = function(node) { 904 return node.tagName && tagNames[node.tagName.toLowerCase()]; 905 }; 906 907 //now we run an optimized find all on it 908 try { 909 return this.findAll(fragment, filter, true); 910 } finally { 911 //the usual IE6 is broken, fix code 912 filter = null; 913 } 914 }, 915 916 /** 917 * determines the number of nodes according to their tagType 918 * 919 * @param {Node} fragment (Node or fragment) the fragment to be investigated 920 * @param {String} tagName the tag name (lowercase) 921 * (the normal usecase is false, which means if the element is found only its 922 * adjacent elements will be scanned, due to the recursive descension 923 * this should work out with elements with different nesting depths but not being 924 * parent and child to each other 925 * 926 * @return the child elements as array or null if nothing is found 927 * 928 */ 929 findByTagName : function(fragment, tagName) { 930 this._assertStdParams(fragment, tagName, "findByTagName", ["fragment", "tagName"]); 931 var _Lang = this._Lang, 932 nodeType = fragment.nodeType; 933 if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null; 934 935 //remapping to save a few bytes 936 937 var ret = _Lang.objToArray(fragment.getElementsByTagName(tagName)); 938 if (fragment.tagName && _Lang.equalsIgnoreCase(fragment.tagName, tagName)) ret.unshift(fragment); 939 return ret; 940 }, 941 942 findByName : function(fragment, name) { 943 this._assertStdParams(fragment, name, "findByName", ["fragment", "name"]); 944 945 var nodeType = fragment.nodeType; 946 if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null; 947 948 var ret = this._Lang.objToArray(fragment.getElementsByName(name)); 949 if (fragment.name == name) ret.unshift(fragment); 950 return ret; 951 }, 952 953 /** 954 * a filtered findAll for subdom treewalking 955 * (which uses browser optimizations wherever possible) 956 * 957 * @param {|Node|} rootNode the rootNode so start the scan 958 * @param filter filter closure with the syntax {boolean} filter({Node} node) 959 * @param deepScan if set to true or not set at all a deep scan is performed (for form scans it does not make much sense to deeply scan) 960 */ 961 findAll : function(rootNode, filter, deepScan) { 962 this._Lang.assertType(filter, "function"); 963 deepScan = !!deepScan; 964 965 if (document.createTreeWalker && NodeFilter) { 966 return this._iteratorSearchAll(rootNode, filter, deepScan); 967 } else { 968 //will not be called in dom level3 compliant browsers 969 return this._recursionSearchAll(rootNode, filter, deepScan); 970 } 971 }, 972 973 /** 974 * the faster dom iterator based search, works on all newer browsers 975 * except ie8 which already have implemented the dom iterator functions 976 * of html 5 (which is pretty all standard compliant browsers) 977 * 978 * The advantage of this method is a faster tree iteration compared 979 * to the normal recursive tree walking. 980 * 981 * @param rootNode the root node to be iterated over 982 * @param filter the iteration filter 983 * @param deepScan if set to true a deep scan is performed 984 */ 985 _iteratorSearchAll: function(rootNode, filter, deepScan) { 986 var retVal = []; 987 //Works on firefox and webkit, opera and ie have to use the slower fallback mechanis 988 //we have a tree walker in place this allows for an optimized deep scan 989 if (filter(rootNode)) { 990 991 retVal.push(rootNode); 992 if (!deepScan) { 993 return retVal; 994 } 995 } 996 //we use the reject mechanism to prevent a deep scan reject means any 997 //child elements will be omitted from the scan 998 var FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT, 999 FILTER_SKIP = NodeFilter.FILTER_SKIP, 1000 FILTER_REJECT = NodeFilter.FILTER_REJECT; 1001 1002 var walkerFilter = function (node) { 1003 var retCode = (filter(node)) ? FILTER_ACCEPT : FILTER_SKIP; 1004 retCode = (!deepScan && retCode == FILTER_ACCEPT) ? FILTER_REJECT : retCode; 1005 if (retCode == FILTER_ACCEPT || retCode == FILTER_REJECT) { 1006 retVal.push(node); 1007 } 1008 return retCode; 1009 }; 1010 1011 var treeWalker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT, walkerFilter, false); 1012 //noinspection StatementWithEmptyBodyJS 1013 while (treeWalker.nextNode()); 1014 return retVal; 1015 }, 1016 1017 /** 1018 * bugfixing for ie6 which does not cope properly with setAttribute 1019 */ 1020 setAttribute : function(node, attr, val) { 1021 this._assertStdParams(node, attr, "setAttribute", ["fragment", "name"]); 1022 if (!node.setAttribute) { 1023 return; 1024 } 1025 1026 if (attr === 'disabled') { 1027 node.disabled = val === 'disabled' || val === 'true'; 1028 } else if (attr === 'checked') { 1029 node.checked = val === 'checked' || val === 'on' || val === 'true'; 1030 } else if (attr == 'readonly') { 1031 node.readOnly = val === 'readonly' || val === 'true'; 1032 } else { 1033 node.setAttribute(attr, val); 1034 } 1035 }, 1036 1037 /** 1038 * fuzzy form detection which tries to determine the form 1039 * an item has been detached. 1040 * 1041 * The problem is some Javascript libraries simply try to 1042 * detach controls by reusing the names 1043 * of the detached input controls. Most of the times, 1044 * the name is unique in a jsf scenario, due to the inherent form mapping. 1045 * One way or the other, we will try to fix that by 1046 * identifying the proper form over the name 1047 * 1048 * We do it in several ways, in case of no form null is returned 1049 * in case of multiple forms we check all elements with a given name (which we determine 1050 * out of a name or id of the detached element) and then iterate over them 1051 * to find whether they are in a form or not. 1052 * 1053 * If only one element within a form and a given identifier found then we can pull out 1054 * and move on 1055 * 1056 * We cannot do much further because in case of two identical named elements 1057 * all checks must fail and the first elements form is served. 1058 * 1059 * Note, this method is only triggered in case of the issuer or an ajax request 1060 * is a detached element, otherwise already existing code has served the correct form. 1061 * 1062 * This method was added because of 1063 * https://issues.apache.org/jira/browse/MYFACES-2599 1064 * to support the integration of existing ajax libraries which do heavy dom manipulation on the 1065 * controls side (Dojos Dijit library for instance). 1066 * 1067 * @param {Node} elem - element as source, can be detached, undefined or null 1068 * 1069 * @return either null or a form node if it could be determined 1070 * 1071 * TODO move this into extended and replace it with a simpler algorithm 1072 */ 1073 fuzzyFormDetection : function(elem) { 1074 var forms = document.forms, _Lang = this._Lang; 1075 1076 if (!forms || !forms.length) { 1077 return null; 1078 } 1079 1080 // This will not work well on portlet case, because we cannot be sure 1081 // the returned form is right one. 1082 //we can cover that case by simply adding one of our config params 1083 //the default is the weaker, but more correct portlet code 1084 //you can override it with myfaces_config.no_portlet_env = true globally 1085 else if (1 == forms.length && this._RT.getGlobalConfig("no_portlet_env", false)) { 1086 return forms[0]; 1087 } 1088 1089 //before going into the more complicated stuff we try the simple approach 1090 var finalElem = this.byId(elem); 1091 var fetchForm = _Lang.hitch(this, function(elem) { 1092 //element of type form then we are already 1093 //at form level for the issuing element 1094 //https://issues.apache.org/jira/browse/MYFACES-2793 1095 1096 return (_Lang.equalsIgnoreCase(elem.tagName, "form")) ? elem : 1097 ( this.html5FormDetection(elem) || this.getParent(elem, "form")); 1098 }); 1099 1100 if (finalElem) { 1101 var elemForm = fetchForm(finalElem); 1102 if (elemForm) return elemForm; 1103 } 1104 1105 /** 1106 * name check 1107 */ 1108 var foundElements = []; 1109 var name = (_Lang.isString(elem)) ? elem : elem.name; 1110 //id detection did not work 1111 if (!name) return null; 1112 /** 1113 * the lesser chance is the elements which have the same name 1114 * (which is the more likely case in case of a brute dom replacement) 1115 */ 1116 var nameElems = document.getElementsByName(name); 1117 if (nameElems) { 1118 for (var cnt = 0; cnt < nameElems.length && foundElements.length < 2; cnt++) { 1119 // we already have covered the identifier case hence we only can deal with names, 1120 var foundForm = fetchForm(nameElems[cnt]); 1121 if (foundForm) { 1122 foundElements.push(foundForm); 1123 } 1124 } 1125 } 1126 1127 return (1 == foundElements.length ) ? foundElements[0] : null; 1128 }, 1129 1130 html5FormDetection: function(/*item*/) { 1131 return null; 1132 }, 1133 1134 1135 /** 1136 * gets a parent of an item with a given tagname 1137 * @param {Node} item - child element 1138 * @param {String} tagName - TagName of parent element 1139 */ 1140 getParent : function(item, tagName) { 1141 1142 if (!item) { 1143 throw this._Lang.makeException(new Error(), null, null, this._nameSpace, "getParent", 1144 this._Lang.getMessage("ERR_MUST_BE_PROVIDED1", null, "_Dom.getParent", "item {DomNode}")); 1145 } 1146 1147 var _Lang = this._Lang; 1148 var searchClosure = function(parentItem) { 1149 return parentItem && parentItem.tagName 1150 && _Lang.equalsIgnoreCase(parentItem.tagName, tagName); 1151 }; 1152 try { 1153 return this.getFilteredParent(item, searchClosure); 1154 } finally { 1155 searchClosure = null; 1156 _Lang = null; 1157 } 1158 }, 1159 1160 /** 1161 * A parent walker which uses 1162 * a filter closure for filtering 1163 * 1164 * @param {Node} item the root item to ascend from 1165 * @param {function} filter the filter closure 1166 */ 1167 getFilteredParent : function(item, filter) { 1168 this._assertStdParams(item, filter, "getFilteredParent", ["item", "filter"]); 1169 1170 //search parent tag parentName 1171 var parentItem = (item.parentNode) ? item.parentNode : null; 1172 1173 while (parentItem && !filter(parentItem)) { 1174 parentItem = parentItem.parentNode; 1175 } 1176 return (parentItem) ? parentItem : null; 1177 }, 1178 1179 /** 1180 * cross ported from dojo 1181 * fetches an attribute from a node 1182 * 1183 * @param {String} node the node 1184 * @param {String} attr the attribute 1185 * @return the attributes value or null 1186 */ 1187 getAttribute : function(/* HTMLElement */node, /* string */attr) { 1188 return node.getAttribute(attr); 1189 }, 1190 1191 /** 1192 * checks whether the given node has an attribute attached 1193 * 1194 * @param {String|Object} node the node to search for 1195 * @param {String} attr the attribute to search for 1196 * @true if the attribute was found 1197 */ 1198 hasAttribute : function(/* HTMLElement */node, /* string */attr) { 1199 // summary 1200 // Determines whether or not the specified node carries a value for the attribute in question. 1201 return this.getAttribute(node, attr) ? true : false; // boolean 1202 }, 1203 1204 /** 1205 * concatenation routine which concats all childnodes of a node which 1206 * contains a set of CDATA blocks to one big string 1207 * @param {Node} node the node to concat its blocks for 1208 */ 1209 concatCDATABlocks : function(/*Node*/ node) { 1210 var cDataBlock = []; 1211 // response may contain several blocks 1212 for (var i = 0; i < node.childNodes.length; i++) { 1213 cDataBlock.push(node.childNodes[i].data); 1214 } 1215 return cDataBlock.join(''); 1216 }, 1217 1218 //all modern browsers evaluate the scripts 1219 //manually this is a w3d recommendation 1220 isManualScriptEval: function() { 1221 return true; 1222 }, 1223 1224 isMultipartCandidate: function(/*executes*/) { 1225 //implementation in the experimental part 1226 return false; 1227 }, 1228 1229 insertFirst: function(newNode) { 1230 var body = document.body; 1231 if (body.childNodes.length > 0) { 1232 body.insertBefore(newNode, body.firstChild); 1233 } else { 1234 body.appendChild(newNode); 1235 } 1236 }, 1237 1238 byId: function(id) { 1239 return this._Lang.byId(id); 1240 }, 1241 1242 getDummyPlaceHolder: function() { 1243 this._dummyPlaceHolder = this._dummyPlaceHolder ||this.createElement("div"); 1244 return this._dummyPlaceHolder; 1245 }, 1246 1247 /** 1248 * fetches the window id for the current request 1249 * note, this is a preparation method for jsf 2.2 1250 * 1251 */ 1252 getWindowId: function() { 1253 //implementation in the experimental part 1254 return null; 1255 } 1256 }); 1257 1258 1259