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