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