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