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