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