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 stylesheets = document.styleSheets,
 59                 finalCss = [],
 60                 UDEF = "undefined",
 61                 applyStyle = this._Lang.hitch(this, 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 && !this._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 = this._Lang.hitch(this, function(item) {
 73                     var equalsIgnoreCase = this._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     deleteScripts: function(nodeList) {
115         if (!nodeList || !nodeList.length) return;
116         var len = nodeList.length;
117         for (var cnt = 0; cnt < len; cnt++) {
118             var item = nodeList[cnt];
119             var src = item.getAttribute('src');
120             if (src && src.length > 0 && (src.indexOf("/jsf.js") != -1 || src.indexOf("/jsf-uncompressed.js") != -1)) {
121                 continue;
122             }
123             this.deleteItem(item);
124         }
125     },
126 
127     /**
128      * Run through the given Html item and execute the inline scripts
129      * (IE doesn't do this by itself)
130      * @param {Node} item
131      */
132     runScripts: function(item, xmlData) {
133         var _Lang = this._Lang;
134         var finalScripts = [],
135 
136                 execScrpt = _Lang.hitch(this, function(item) {
137                     var tagName = item.tagName;
138                     if (tagName && _Lang.equalsIgnoreCase(tagName, "script")) {
139                         var src = item.getAttribute('src');
140                         if ('undefined' != typeof src
141                                 && null != src
142                                 && src.length > 0
143                                 ) {
144                             //we have to move this into an inner if because chrome otherwise chokes
145                             //due to changing the and order instead of relying on left to right
146                             if ((src.indexOf("ln=scripts") == -1 && src.indexOf("ln=javax.faces") == -1) || (src.indexOf("/jsf.js") == -1
147                                     && src.indexOf("/jsf-uncompressed.js") == -1)) {
148                                 if (finalScripts.length) {
149                                     //script source means we have to eval the existing
150                                     //scripts before running the include
151                                     this._RT.globalEval(finalScripts.join("\n"));
152 
153                                     finalScripts = [];
154                                 }
155                                 //if jsf.js is already registered we do not replace it anymore
156                                 if (!window.jsf) {
157                                     this._RT.loadScriptEval(src, item.getAttribute('type'), false, "UTF-8", false);
158                                 }
159                             }
160                             //TODO handle embedded scripts
161                         } else {
162                             // embedded script auto eval
163                             var test = (!xmlData) ? item.text : _Lang.serializeChilds(item);
164                             var go = true;
165                             while (go) {
166                                 go = false;
167                                 if (test.substring(0, 1) == " ") {
168                                     test = test.substring(1);
169                                     go = true;
170                                 }
171                                 if (test.substring(0, 4) == "<!--") {
172                                     test = test.substring(4);
173                                     go = true;
174                                 }
175                                 if (test.substring(0, 11) == "//<![CDATA[") {
176                                     test = test.substring(11);
177                                     go = true;
178                                 }
179                             }
180                             // we have to run the script under a global context
181                             //we store the script for less calls to eval
182                             finalScripts.push(test);
183 
184                         }
185                     }
186                 });
187         try {
188             var scriptElements = this.findByTagName(item, "script", true);
189             if (scriptElements == null) return;
190             for (var cnt = 0; cnt < scriptElements.length; cnt++) {
191                 execScrpt(scriptElements[cnt]);
192             }
193             if (finalScripts.length) {
194                 this._RT.globalEval(finalScripts.join("\n"));
195             }
196         } finally {
197             //the usual ie6 fix code
198             //the IE6 garbage collector is broken
199             //nulling closures helps somewhat to reduce
200             //mem leaks, which are impossible to avoid
201             //at this browser
202             execScrpt = null;
203         }
204     },
205 
206 
207     /**
208      * determines to fetch a node
209      * from its id or name, the name case
210      * only works if the element is unique in its name
211      * @param {String} elem
212      */
213     byIdOrName: function(elem) {
214         if (!elem) return null;
215         if (!this._Lang.isString(elem)) return elem;
216 
217         var ret = this.byId(elem);
218         if (ret) return ret;
219         //we try the unique name fallback
220         var items = document.getElementsByName(elem);
221         return ((items.length == 1) ? items[0] : null);
222     },
223 
224     /**
225      * node id or name, determines the valid form identifier of a node
226      * depending on its uniqueness
227      *
228      * Usually the id is chosen for an elem, but if the id does not
229      * exist we try a name fallback. If the passed element has a unique
230      * name we can use that one as subsequent identifier.
231      *
232      *
233      * @param {String} elem
234      */
235     nodeIdOrName: function(elem) {
236         if (elem) {
237             //just to make sure that the pas
238 
239             elem = this.byId(elem);
240             if (!elem) return null;
241             //detached element handling, we also store the element name
242             //to get a fallback option in case the identifier is not determinable
243             // anymore, in case of a framework induced detachment the element.name should
244             // be shared if the identifier is not determinable anymore
245             //the downside of this method is the element name must be unique
246             //which in case of jsf it is
247             var elementId = elem.id || elem.name;
248             if ((elem.id == null || elem.id == '') && elem.name) {
249                 elementId = elem.name;
250 
251                 //last check for uniqueness
252                 if (this.getElementsByName(elementId).length > 1) {
253                     //no unique element name so we need to perform
254                     //a return null to let the caller deal with this issue
255                     return null;
256                 }
257             }
258             return elementId;
259         }
260         return null;
261     },
262 
263     deleteItems: function(items) {
264         if (! items || ! items.length) return;
265         for (var cnt = 0; cnt < items.length; cnt++) {
266             this.deleteItem(items[cnt]);
267         }
268     },
269 
270     /**
271      * Simple delete on an existing item
272      */
273     deleteItem: function(itemIdToReplace) {
274         var item = this.byId(itemIdToReplace);
275         if (!item) {
276             throw Error("_Dom.deleteItem  Unknown Html-Component-ID: " + itemIdToReplace);
277         }
278 
279         this._removeNode(item, false);
280     },
281 
282     /**
283      * creates a node upon a given node name
284      * @param nodeName {String} the node name to be created
285      * @param attrs {Array} a set of attributes to be set
286      */
287     createElement: function(nodeName, attrs) {
288         var ret = document.createElement(nodeName);
289         if (attrs) {
290             for (var key in attrs) {
291                 this.setAttribute(ret, key, attrs[key]);
292             }
293         }
294         return ret;
295     },
296 
297     /**
298      * Checks whether the browser is dom compliant.
299      * Dom compliant means that it performs the basic dom operations safely
300      * without leaking and also is able to perform a native setAttribute
301      * operation without freaking out
302      *
303      *
304      * Not dom compliant browsers are all microsoft browsers in quirks mode
305      * and ie6 and ie7 to some degree in standards mode
306      * and pretty much every browser who cannot create ranges
307      * (older mobile browsers etc...)
308      *
309      * We dont do a full browser detection here because it probably is safer
310      * to test for existing features to make an assumption about the
311      * browsers capabilities
312      */
313     isDomCompliant: function() {
314         return true;
315     },
316 
317     /**
318      * proper insert before which takes tables into consideration as well as
319      * browser deficiencies
320      * @param item the node to insert before
321      * @param markup the markup to be inserted
322      */
323     insertBefore: function(item, markup) {
324         this._assertStdParams(item, markup, "insertBefore");
325 
326         markup = this._Lang.trim(markup);
327         if (markup === "") return null;
328 
329         var evalNodes = this._buildEvalNodes(item, markup),
330                 currentRef = item,
331                 parentNode = item.parentNode,
332                 ret = [];
333         for (var cnt = evalNodes.length - 1; cnt >= 0; cnt--) {
334             currentRef = parentNode.insertBefore(evalNodes[cnt], currentRef);
335             ret.push(currentRef);
336         }
337         ret = ret.reverse();
338         this._eval(ret);
339         return ret;
340     },
341 
342     /**
343      * proper insert before which takes tables into consideration as well as
344      * browser deficiencies
345      * @param item the node to insert before
346      * @param markup the markup to be inserted
347      */
348     insertAfter: function(item, markup) {
349         this._assertStdParams(item, markup, "insertAfter");
350         markup = this._Lang.trim(markup);
351         if (markup === "") return null;
352 
353         var evalNodes = this._buildEvalNodes(item, markup),
354                 currentRef = item,
355                 parentNode = item.parentNode,
356                 ret = [];
357 
358         for (var cnt = 0; cnt < evalNodes.length; cnt++) {
359             if (currentRef.nextSibling) {
360                 //TODO winmobile6 has problems with this strategy
361                 currentRef = parentNode.insertBefore(evalNodes[cnt], currentRef.nextSibling);
362             } else {
363                 currentRef = parentNode.appendChild(evalNodes[cnt]);
364             }
365             ret.push(currentRef);
366         }
367         this._eval(ret);
368         return ret;
369     },
370 
371 
372     /**
373      * outerHTML replacement which works cross browserlike
374      * but still is speed optimized
375      *
376      * @param item the item to be replaced
377      * @param markup the markup for the replacement
378      */
379     outerHTML : function(item, markup) {
380         this._assertStdParams(item, markup, "outerHTML");
381 
382         markup = this._Lang.trim(markup);
383         if (markup !== "") {
384             var ret = null;
385 
386             // we try to determine the browsers compatibility
387             // level to standards dom level 2 via various methods
388             if (this.isDomCompliant()) {
389                 ret = this._outerHTMLCompliant(item, markup);
390             } else {
391                 //call into abstract method
392                 ret = this._outerHTMLNonCompliant(item, markup);
393             }
394 
395             // and remove the old item
396             //first we have to save the node newly insert for easier access in our eval part
397             this._eval(ret);
398             return ret;
399         }
400         // and remove the old item, in case of an empty newtag and do nothing else
401         this._removeNode(item, false);
402         return null;
403     },
404 
405     /**
406      * detaches a set of nodes from their parent elements
407      * in a browser independend manner
408      * @param {Object} items the items which need to be detached
409      * @return {Array} an array of nodes with the detached dom nodes
410      */
411     detach: function(items) {
412         var ret = [];
413         if ('undefined' != typeof items.nodeType) {
414             if (items.parentNode) {
415                 ret.push(items.parentNode.removeChild(items));
416             } else {
417                 ret.push(items);
418             }
419             return ret;
420         }
421         //all ies treat node lists not as arrays so we have to take
422         //an intermediate step
423         var nodeArr = this._Lang.objToArray(items);
424         for (var cnt = 0; cnt < nodeArr.length; cnt++) {
425             ret.push(nodeArr[cnt].parentNode.removeChild(nodeArr[cnt]));
426         }
427         return ret;
428     },
429 
430     _outerHTMLCompliant: function(item, markup) {
431         //table element replacements like thead, tbody etc... have to be treated differently
432         var evalNodes = this._buildEvalNodes(item, markup);
433 
434         if (evalNodes.length == 1) {
435             var ret = evalNodes[0];
436             item.parentNode.replaceChild(ret, item);
437             return ret;
438         } else {
439             return this.replaceElements(item, evalNodes);
440         }
441     },
442 
443     _isTable: function(item) {
444         return "table" == (item.nodeName || item.tagName).toLowerCase();
445     },
446 
447     /**
448      * checks if the provided element is a subelement of a table element
449      * @param itemNodeName
450      */
451     _isTableElement: function(item) {
452         return !!this.TABLE_ELEMS[(item.nodeName || item.tagName).toLowerCase()];
453     },
454 
455     /**
456      * non ie browsers do not have problems with embedded scripts or any other construct
457      * we simply can use an innerHTML in a placeholder
458      *
459      * @param markup the markup to be used
460      */
461     _buildNodesCompliant: function(markup) {
462         var dummyPlaceHolder = this.getDummyPlaceHolder(); //document.createElement("div");
463         dummyPlaceHolder.innerHTML = markup;
464         return this._Lang.objToArray(dummyPlaceHolder.childNodes);
465     },
466 
467 
468 
469 
470     /**
471      * builds up a correct dom subtree
472      * if the markup is part of table nodes
473      * The usecase for this is to allow subtable rendering
474      * like single rows thead or tbody
475      *
476      * @param item
477      * @param markup
478      */
479     _buildTableNodes: function(item, markup) {
480         var itemNodeName = (item.nodeName || item.tagName).toLowerCase(),
481                 probe = document.createElement("div");
482 
483         var tmpNodeName = itemNodeName;
484         var depth = 0;
485         while (tmpNodeName != "table") {
486             item = item.parentNode;
487             tmpNodeName = (item.nodeName || item.tagName).toLowerCase();
488             depth++;
489         }
490 
491         var dummyPlaceHolder = document.createElement("div");
492         if (itemNodeName == "td") {
493             dummyPlaceHolder.innerHTML = "<table><tbody><tr>" + markup + "</tr></tbody></table>";
494         } else {
495             dummyPlaceHolder.innerHTML = "<table>" + markup + "</table>";
496         }
497 
498         for (var cnt = 0; cnt < depth; cnt++) {
499             dummyPlaceHolder = dummyPlaceHolder.childNodes[0];
500         }
501 
502         return this.detach(dummyPlaceHolder.childNodes);
503     },
504 
505     _removeChildNodes: function(node, breakEventsOpen) {
506         if (!node) return;
507         node.innerHTML = "";
508     },
509 
510 
511 
512     _removeNode: function(node, breakEventsOpen) {
513         if (!node) return;
514         var parentNode = node.parentNode;
515         if (parentNode) //if the node has a parent
516             parentNode.removeChild(node);
517         return;
518     },
519 
520 
521     /**
522      * build up the nodes from html markup in a browser independend way
523      * so that it also works with table nodes
524      *
525      * @param item the parent item upon the nodes need to be processed upon after building
526      * @param markup the markup to be built up
527      */
528     _buildEvalNodes: function(item, markup) {
529         var evalNodes = null;
530         if (this._isTableElement(item)) {
531             evalNodes = this._buildTableNodes(item, markup);
532         } else {
533             evalNodes = (this.isDomCompliant()) ? this._buildNodesCompliant(markup) : this._buildNodesNonCompliant(markup);
534         }
535         return evalNodes;
536     },
537 
538     /**
539      * we have lots of methods with just an item and a markup as params
540      * this method builds an assertion for those methods to reduce code
541      *
542      * @param item  the item to be tested
543      * @param markup the mark
544      * @param caller
545      */
546     _assertStdParams: function(item, markup, caller, params) {
547         //internal error
548         if (!caller) throw Error("Caller must be set for assertion");
549         var _Lang = this._Lang,
550                 ERR_PROV = "ERR_MUST_BE_PROVIDED1",
551                 DOM = "myfaces._impl._util._Dom.",
552                 finalParams = params || ["item", "markup"];
553 
554         if (!item || !markup) {
555             throw Error(_Lang.getMessage(ERR_PROV, null, DOM + caller, (!item) ? params[0] : params[1]));
556         }
557 
558     },
559 
560     /**
561      * internal eval handler used by various functions
562      * @param _nodeArr
563      */
564     _eval: function(_nodeArr) {
565         if (this.isManualScriptEval()) {
566             var isArr = _nodeArr instanceof Array;
567             if (isArr && _nodeArr.length) {
568                 for (var cnt = 0; cnt < _nodeArr.length; cnt++) {
569                     this.runScripts(_nodeArr[cnt]);
570                 }
571             } else if (!isArr) {
572                 this.runScripts(_nodeArr);
573             }
574         }
575     },
576 
577 
578 
579     /**
580      * for performance reasons we work with replaceElement and replaceElements here
581      * after measuring performance it has shown that passing down an array instead
582      * of a single node makes replaceElement twice as slow, however
583      * a single node case is the 95% case
584      *
585      * @param item
586      * @param evalNodes
587      */
588     replaceElement: function(item, evalNode) {
589         //browsers with defect garbage collection
590         item.parentNode.insertBefore(evalNode, item);
591         this._removeNode(item, false);
592     },
593 
594 
595     /**
596      * replaces an element with another element or a set of elements
597      *
598      * @param item the item to be replaced
599      *
600      * @param evalNodes the elements
601      */
602     replaceElements: function (item, evalNodes) {
603         var evalNodesDefined = evalNodes && 'undefined' != typeof evalNodes.length;
604         if (!evalNodesDefined) {
605             throw new Error(this._Lang.getMessage("ERR_REPLACE_EL"));
606         }
607 
608         var parentNode = item.parentNode,
609 
610                 sibling = item.nextSibling,
611                 resultArr = this._Lang.objToArray(evalNodes);
612 
613         for (var cnt = 0; cnt < resultArr.length; cnt++) {
614             if (cnt == 0) {
615                 this.replaceElement(item, resultArr[cnt]);
616             } else {
617                 if (sibling) {
618                     parentNode.insertBefore(resultArr[cnt], sibling);
619                 } else {
620                     parentNode.appendChild(resultArr[cnt]);
621                 }
622             }
623         }
624         return resultArr;
625     },
626 
627     /**
628      * optimized search for an array of tag names
629      * deep scan will always be performed.
630      * @param fragment the fragment which should be searched for
631      * @param tagNames an map indx of tag names which have to be found
632      *
633      */
634     findByTagNames: function(fragment, tagNames) {
635         this._assertStdParams(fragment, tagNames, "findByTagNames", ["fragment", "tagNames"]);
636 
637         var nodeType = fragment.nodeType;
638         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
639 
640         //we can use the shortcut
641         if (fragment.querySelectorAll) {
642             var query = [];
643             for (var key in tagNames) {
644                 query.push(key);
645             }
646             var res = [];
647             if (fragment.tagName && tagNames[fragment.tagName.toLowerCase()]) {
648                 res.push(fragment);
649             }
650             return res.concat(this._Lang.objToArray(fragment.querySelectorAll(query.join(", "))));
651         }
652 
653         //now the filter function checks case insensitively for the tag names needed
654         var filter = function(node) {
655             return node.tagName && tagNames[node.tagName.toLowerCase()];
656         };
657 
658         //now we run an optimized find all on it
659         try {
660             return this.findAll(fragment, filter, true);
661         } finally {
662             //the usual IE6 is broken, fix code
663             filter = null;
664         }
665     },
666 
667     /**
668      * determines the number of nodes according to their tagType
669      *
670      * @param {Node} fragment (Node or fragment) the fragment to be investigated
671      * @param {String} tagName the tag name (lowercase)
672      * (the normal usecase is false, which means if the element is found only its
673      * adjacent elements will be scanned, due to the recursive descension
674      * this should work out with elements with different nesting depths but not being
675      * parent and child to each other
676      *
677      * @return the child elements as array or null if nothing is found
678      *
679      */
680     findByTagName : function(fragment, tagName) {
681         this._assertStdParams(fragment, tagName, "findByTagName", ["fragment", "tagName"]);
682         var _Lang = this._Lang,
683                 nodeType = fragment.nodeType;
684         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
685 
686         //remapping to save a few bytes
687 
688         var ret = _Lang.objToArray(fragment.getElementsByTagName(tagName));
689         if (fragment.tagName && _Lang.equalsIgnoreCase(fragment.tagName, tagName)) ret.unshift(fragment);
690         return ret;
691     },
692 
693     findByName : function(fragment, name) {
694         this._assertStdParams(fragment, name, "findByName", ["fragment", "name"]);
695 
696         var nodeType = fragment.nodeType;
697         if (nodeType != 1 && nodeType != 9 && nodeType != 11) return null;
698 
699         var ret = this._Lang.objToArray(fragment.getElementsByName(name));
700         if (fragment.name == name) ret.unshift(fragment);
701         return ret;
702     },
703 
704 
705 
706     /**
707      * a filtered findAll for subdom treewalking
708      * (which uses browser optimizations wherever possible)
709      *
710      * @param {|Node|} rootNode the rootNode so start the scan
711      * @param filter filter closure with the syntax {boolean} filter({Node} node)
712      * @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)
713      */
714     findAll : function(rootNode, filter, deepScan) {
715         this._Lang.assertType(filter, "function");
716         deepScan = !!deepScan;
717 
718         if (document.createTreeWalker && NodeFilter) {
719             return this._iteratorSearchAll(rootNode, filter, deepScan);
720         } else {
721             //will not be called in dom level3 compliant browsers
722             return this._recursionSearchAll(rootNode, filter, deepScan);
723         }
724 
725     },
726 
727 
728 
729     /**
730      * the faster dom iterator based search, works on all newer browsers
731      * except ie8 which already have implemented the dom iterator functions
732      * of html 5 (which is pretty all standard compliant browsers)
733      *
734      * The advantage of this method is a faster tree iteration compared
735      * to the normal recursive tree walking.
736      *
737      * @param rootNode the root node to be iterated over
738      * @param filter the iteration filter
739      * @param deepScan if set to true a deep scan is performed
740      */
741     _iteratorSearchAll: function(rootNode, filter, deepScan) {
742         var retVal = [];
743         //Works on firefox and webkit, opera and ie have to use the slower fallback mechanis
744         //we have a tree walker in place this allows for an optimized deep scan
745         if (filter(rootNode)) {
746 
747             retVal.push(rootNode);
748             if (!deepScan) {
749                 return retVal;
750             }
751         }
752         //we use the reject mechanism to prevent a deep scan reject means any
753         //child elements will be omitted from the scan
754         var FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT,
755                 FILTER_SKIP = NodeFilter.FILTER_SKIP,
756                 FILTER_REJECT = NodeFilter.FILTER_REJECT;
757 
758         var walkerFilter = function (node) {
759             var retCode = (filter(node)) ? FILTER_ACCEPT : FILTER_SKIP;
760             retCode = (!deepScan && retCode == FILTER_ACCEPT) ? FILTER_REJECT : retCode;
761             if (retCode == FILTER_ACCEPT || retCode == FILTER_REJECT) {
762                 retVal.push(node);
763             }
764             return retCode;
765         };
766 
767         var treeWalker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT, walkerFilter, false);
768         //noinspection StatementWithEmptyBodyJS
769         while (treeWalker.nextNode());
770         return retVal;
771     },
772 
773     /**
774      * bugfixing for ie6 which does not cope properly with setAttribute
775      */
776     setAttribute : function(node, attr, val) {
777         this._assertStdParams(node, attr, "setAttribute", ["fragment", "name"]);
778         if (!node.setAttribute) {
779             return;
780         }
781         node.setAttribute(attr, val);
782         return;
783     },
784 
785     /**
786      * fuzzy form detection which tries to determine the form
787      * an item has been detached.
788      *
789      * The problem is some Javascript libraries simply try to
790      * detach controls by reusing the names
791      * of the detached input controls. Most of the times,
792      * the name is unique in a jsf scenario, due to the inherent form mapping.
793      * One way or the other, we will try to fix that by
794      * identifying the proper form over the name
795      *
796      * We do it in several ways, in case of no form null is returned
797      * in case of multiple forms we check all elements with a given name (which we determine
798      * out of a name or id of the detached element) and then iterate over them
799      * to find whether they are in a form or not.
800      *
801      * If only one element within a form and a given identifier found then we can pull out
802      * and move on
803      *
804      * We cannot do much further because in case of two identical named elements
805      * all checks must fail and the first elements form is served.
806      *
807      * Note, this method is only triggered in case of the issuer or an ajax request
808      * is a detached element, otherwise already existing code has served the correct form.
809      *
810      * This method was added because of
811      * https://issues.apache.org/jira/browse/MYFACES-2599
812      * to support the integration of existing ajax libraries which do heavy dom manipulation on the
813      * controls side (Dojos Dijit library for instance).
814      *
815      * @param {Node} elem - element as source, can be detached, undefined or null
816      *
817      * @return either null or a form node if it could be determined
818      *
819      * TODO move this into extended and replace it with a simpler algorithm
820      */
821     fuzzyFormDetection : function(elem) {
822         var forms = document.forms, _Lang = this._Lang;
823 
824         if (!forms || !forms.length) {
825             return null;
826         }
827 
828         // This will not work well on portlet case, because we cannot be sure
829         // the returned form is right one.
830         //we can cover that case by simply adding one of our config params
831         //the default is the weaker, but more correct portlet code
832         //you can override it with myfaces_config.no_portlet_env = true globally
833         else if (1 == forms.length && this._RT.getGlobalConfig("no_portlet_env", false)) {
834             return forms[0];
835         }
836 
837         //before going into the more complicated stuff we try the simple approach
838         var finalElem = this.byId(elem);
839         var fetchForm = _Lang.hitch(this, function(elem) {
840             //element of type form then we are already
841             //at form level for the issuing element
842             //https://issues.apache.org/jira/browse/MYFACES-2793
843 
844             return (_Lang.equalsIgnoreCase(elem.tagName, "form")) ? elem :
845                     ( this.html5FormDetection(elem) || this.getParent(elem, "form"));
846         });
847 
848         if (finalElem) {
849             var elemForm = fetchForm(finalElem);
850             if (elemForm) return elemForm;
851         }
852 
853         /**
854          * name check
855          */
856         var foundElements = [];
857         var name = (_Lang.isString(elem)) ? elem : elem.name;
858         //id detection did not work
859         if (!name) return null;
860         /**
861          * the lesser chance is the elements which have the same name
862          * (which is the more likely case in case of a brute dom replacement)
863          */
864         var nameElems = document.getElementsByName(name);
865         if (nameElems) {
866             for (var cnt = 0; cnt < nameElems.length && foundElements.length < 2; cnt++) {
867                 // we already have covered the identifier case hence we only can deal with names,
868                 var foundForm = fetchForm(nameElems[cnt]);
869                 if (foundForm) {
870                     foundElements.push(foundForm);
871                 }
872             }
873         }
874 
875         return (1 == foundElements.length ) ? foundElements[0] : null;
876     },
877 
878     html5FormDetection: function(item) {
879         return null;
880     },
881 
882 
883     /**
884      * gets a parent of an item with a given tagname
885      * @param {Node} item - child element
886      * @param {String} tagName - TagName of parent element
887      */
888     getParent : function(item, tagName) {
889 
890         if (!item) {
891             throw Error(this._Lang.getMessage("ERR_MUST_BE_PROVIDED1", null, "_Dom.getParent", "item {DomNode}"));
892         }
893 
894         var _Lang = this._Lang;
895         var searchClosure = function(parentItem) {
896             return parentItem && parentItem.tagName
897                     && _Lang.equalsIgnoreCase(parentItem.tagName, tagName);
898         };
899         try {
900             return this.getFilteredParent(item, searchClosure);
901         } finally {
902             searchClosure = null;
903             _Lang = null;
904         }
905     },
906 
907     /**
908      * A parent walker which uses
909      * a filter closure for filtering
910      *
911      * @param {Node} item the root item to ascend from
912      * @param {function} filter the filter closure
913      */
914     getFilteredParent : function(item, filter) {
915         this._assertStdParams(item, filter, "getFilteredParent", ["item", "filter"]);
916 
917         //search parent tag parentName
918         var parentItem = (item.parentNode) ? item.parentNode : null;
919 
920         while (parentItem && !filter(parentItem)) {
921             parentItem = parentItem.parentNode;
922         }
923         return (parentItem) ? parentItem : null;
924     },
925 
926     /**
927      * cross ported from dojo
928      * fetches an attribute from a node
929      *
930      * @param {String} node the node
931      * @param {String} attr the attribute
932      * @return the attributes value or null
933      */
934     getAttribute : function(/* HTMLElement */node, /* string */attr) {
935         return node.getAttribute(attr);
936     },
937 
938     /**
939      * checks whether the given node has an attribute attached
940      *
941      * @param {String|Object} node the node to search for
942      * @param {String} attr the attribute to search for
943      * @true if the attribute was found
944      */
945     hasAttribute : function(/* HTMLElement */node, /* string */attr) {
946         //	summary
947         //	Determines whether or not the specified node carries a value for the attribute in question.
948         return this.getAttribute(node, attr) ? true : false;	//	boolean
949     },
950 
951     /**
952      * concatenation routine which concats all childnodes of a node which
953      * contains a set of CDATA blocks to one big string
954      * @param {Node} node the node to concat its blocks for
955      */
956     concatCDATABlocks : function(/*Node*/ node) {
957         var cDataBlock = [];
958         // response may contain several blocks
959         for (var i = 0; i < node.childNodes.length; i++) {
960             cDataBlock.push(node.childNodes[i].data);
961         }
962         return cDataBlock.join('');
963     },
964 
965     //all modern browsers evaluate the scripts
966     //manually this is a w3d recommendation
967     isManualScriptEval: function() {
968         return true;
969     },
970 
971     isMultipartCandidate: function(executes) {
972         //implementation in the experimental part
973         return false;
974     },
975 
976     insertFirst: function(newNode) {
977         var body = document.body;
978         if (body.childNodes.length > 0) {
979             body.insertBefore(newNode, body.firstChild);
980         } else {
981             body.appendChild(newNode);
982         }
983     },
984 
985     byId: function(id) {
986         return this._Lang.byId(id);
987     },
988 
989     getDummyPlaceHolder: function() {
990         var created = false;
991         if (!this._dummyPlaceHolder) {
992             this._dummyPlaceHolder = this.createElement("div");
993             created = true;
994         }
995         return this._dummyPlaceHolder;
996     },
997 
998     /**
999      * fetches the window id for the current request
1000      * note, this is a preparation method for jsf 2.2
1001      *
1002      * todo move this into the experimental part
1003      */
1004     getWindowId: function() {
1005         //implementation in the experimental part
1006         return null;
1007     }
1008 });
1009 
1010 
1011