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