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