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