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