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