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