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