Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / core / core / DomQuery.js
1 /*!
2  * Ext JS Library 3.0.3
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /*\r
8  * This is code is also distributed under MIT license for use\r
9  * with jQuery and prototype JavaScript libraries.\r
10  */\r
11 /**\r
12  * @class Ext.DomQuery\r
13 Provides high performance selector/xpath processing by compiling queries into reusable functions. New pseudo classes and matchers can be plugged. It works on HTML and XML documents (if a content node is passed in).\r
14 <p>\r
15 DomQuery supports most of the <a href="http://www.w3.org/TR/2005/WD-css3-selectors-20051215/#selectors">CSS3 selectors spec</a>, along with some custom selectors and basic XPath.</p>\r
16 \r
17 <p>\r
18 All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example "div.foo:nth-child(odd)[@foo=bar].bar:first" would be a perfectly valid selector. Node filters are processed in the order in which they appear, which allows you to optimize your queries for your document structure.\r
19 </p>\r
20 <h4>Element Selectors:</h4>\r
21 <ul class="list">\r
22     <li> <b>*</b> any element</li>\r
23     <li> <b>E</b> an element with the tag E</li>\r
24     <li> <b>E F</b> All descendent elements of E that have the tag F</li>\r
25     <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>\r
26     <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>\r
27     <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>\r
28 </ul>\r
29 <h4>Attribute Selectors:</h4>\r
30 <p>The use of &#64; and quotes are optional. For example, div[&#64;foo='bar'] is also a valid attribute selector.</p>\r
31 <ul class="list">\r
32     <li> <b>E[foo]</b> has an attribute "foo"</li>\r
33     <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>\r
34     <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>\r
35     <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>\r
36     <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>\r
37     <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>\r
38     <li> <b>E[foo!=bar]</b> has an attribute "foo" that does not equal "bar"</li>\r
39 </ul>\r
40 <h4>Pseudo Classes:</h4>\r
41 <ul class="list">\r
42     <li> <b>E:first-child</b> E is the first child of its parent</li>\r
43     <li> <b>E:last-child</b> E is the last child of its parent</li>\r
44     <li> <b>E:nth-child(<i>n</i>)</b> E is the <i>n</i>th child of its parent (1 based as per the spec)</li>\r
45     <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>\r
46     <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>\r
47     <li> <b>E:only-child</b> E is the only child of its parent</li>\r
48     <li> <b>E:checked</b> E is an element that is has a checked attribute that is true (e.g. a radio or checkbox) </li>\r
49     <li> <b>E:first</b> the first E in the resultset</li>\r
50     <li> <b>E:last</b> the last E in the resultset</li>\r
51     <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>\r
52     <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>\r
53     <li> <b>E:even</b> shortcut for :nth-child(even)</li>\r
54     <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>\r
55     <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>\r
56     <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>\r
57     <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>\r
58     <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>\r
59     <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>\r
60 </ul>\r
61 <h4>CSS Value Selectors:</h4>\r
62 <ul class="list">\r
63     <li> <b>E{display=none}</b> css value "display" that equals "none"</li>\r
64     <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>\r
65     <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>\r
66     <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>\r
67     <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>\r
68     <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>\r
69 </ul>\r
70  * @singleton\r
71  */\r
72 Ext.DomQuery = function(){\r
73     var cache = {}, \r
74         simpleCache = {}, \r
75         valueCache = {},\r
76         nonSpace = /\S/,\r
77         trimRe = /^\s+|\s+$/g,\r
78         tplRe = /\{(\d+)\}/g,\r
79         modeRe = /^(\s?[\/>+~]\s?|\s|$)/,\r
80         tagTokenRe = /^(#)?([\w-\*]+)/,\r
81         nthRe = /(\d*)n\+?(\d*)/, \r
82         nthRe2 = /\D/,\r
83         // This is for IE MSXML which does not support expandos.\r
84             // IE runs the same speed using setAttribute, however FF slows way down\r
85             // and Safari completely fails so they need to continue to use expandos.\r
86             isIE = window.ActiveXObject ? true : false,\r
87         isOpera = Ext.isOpera,\r
88             key = 30803;\r
89             \r
90     // this eval is stop the compressor from\r
91         // renaming the variable to something shorter\r
92         eval("var batch = 30803;");     \r
93 \r
94     function child(p, index){\r
95         var i = 0,\r
96                 n = p.firstChild;\r
97         while(n){\r
98             if(n.nodeType == 1){\r
99                if(++i == index){\r
100                    return n;\r
101                }\r
102             }\r
103             n = n.nextSibling;\r
104         }\r
105         return null;\r
106     };\r
107 \r
108     function next(n){\r
109         while((n = n.nextSibling) && n.nodeType != 1);\r
110         return n;\r
111     };\r
112 \r
113     function prev(n){\r
114         while((n = n.previousSibling) && n.nodeType != 1);\r
115         return n;\r
116     };\r
117 \r
118     function children(d){\r
119         var n = d.firstChild, ni = -1,\r
120                 nx;\r
121             while(n){\r
122                 nx = n.nextSibling;\r
123                 if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){\r
124                     d.removeChild(n);\r
125                 }else{\r
126                     n.nodeIndex = ++ni;\r
127                 }\r
128                 n = nx;\r
129             }\r
130             return this;\r
131         };\r
132 \r
133     function byClassName(c, a, v){\r
134         if(!v){\r
135             return c;\r
136         }\r
137         var r = [], ri = -1, cn;\r
138         for(var i = 0, ci; ci = c[i]; i++){\r
139             if((' '+ci.className+' ').indexOf(v) != -1){\r
140                 r[++ri] = ci;\r
141             }\r
142         }\r
143         return r;\r
144     };\r
145 \r
146     function attrValue(n, attr){\r
147         if(!n.tagName && typeof n.length != "undefined"){\r
148             n = n[0];\r
149         }\r
150         if(!n){\r
151             return null;\r
152         }\r
153         if(attr == "for"){\r
154             return n.htmlFor;\r
155         }\r
156         if(attr == "class" || attr == "className"){\r
157             return n.className;\r
158         }\r
159         return n.getAttribute(attr) || n[attr];\r
160 \r
161     };\r
162 \r
163     function getNodes(ns, mode, tagName){\r
164         var result = [], ri = -1, cs;\r
165         if(!ns){\r
166             return result;\r
167         }\r
168         tagName = tagName || "*";\r
169         if(typeof ns.getElementsByTagName != "undefined"){\r
170             ns = [ns];\r
171         }\r
172         if(!mode){\r
173             for(var i = 0, ni; ni = ns[i]; i++){\r
174                 cs = ni.getElementsByTagName(tagName);\r
175                 for(var j = 0, ci; ci = cs[j]; j++){\r
176                     result[++ri] = ci;\r
177                 }\r
178             }\r
179         }else if(mode == "/" || mode == ">"){\r
180             var utag = tagName.toUpperCase();\r
181             for(var i = 0, ni, cn; ni = ns[i]; i++){\r
182                 cn = isOpera ? ni.childNodes : (ni.children || ni.childNodes);\r
183                 for(var j = 0, cj; cj = cn[j]; j++){\r
184                     if(cj.nodeName == utag || cj.nodeName == tagName  || tagName == '*'){\r
185                         result[++ri] = cj;\r
186                     }\r
187                 }\r
188             }\r
189         }else if(mode == "+"){\r
190             var utag = tagName.toUpperCase();\r
191             for(var i = 0, n; n = ns[i]; i++){\r
192                 while((n = n.nextSibling) && n.nodeType != 1);\r
193                 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){\r
194                     result[++ri] = n;\r
195                 }\r
196             }\r
197         }else if(mode == "~"){\r
198             var utag = tagName.toUpperCase();\r
199             for(var i = 0, n; n = ns[i]; i++){\r
200                 while((n = n.nextSibling)){\r
201                     if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){\r
202                         result[++ri] = n;\r
203                     }\r
204                 }\r
205             }\r
206         }\r
207         return result;\r
208     };\r
209 \r
210     function concat(a, b){\r
211         if(b.slice){\r
212             return a.concat(b);\r
213         }\r
214         for(var i = 0, l = b.length; i < l; i++){\r
215             a[a.length] = b[i];\r
216         }\r
217         return a;\r
218     }\r
219 \r
220     function byTag(cs, tagName){\r
221         if(cs.tagName || cs == document){\r
222             cs = [cs];\r
223         }\r
224         if(!tagName){\r
225             return cs;\r
226         }\r
227         var r = [], ri = -1;\r
228         tagName = tagName.toLowerCase();\r
229         for(var i = 0, ci; ci = cs[i]; i++){\r
230             if(ci.nodeType == 1 && ci.tagName.toLowerCase()==tagName){\r
231                 r[++ri] = ci;\r
232             }\r
233         }\r
234         return r;\r
235     };\r
236 \r
237     function byId(cs, attr, id){\r
238         if(cs.tagName || cs == document){\r
239             cs = [cs];\r
240         }\r
241         if(!id){\r
242             return cs;\r
243         }\r
244         var r = [], ri = -1;\r
245         for(var i = 0,ci; ci = cs[i]; i++){\r
246             if(ci && ci.id == id){\r
247                 r[++ri] = ci;\r
248                 return r;\r
249             }\r
250         }\r
251         return r;\r
252     };\r
253 \r
254     function byAttribute(cs, attr, value, op, custom){\r
255         var r = [], \r
256                 ri = -1, \r
257                 st = custom=="{",\r
258                 f = Ext.DomQuery.operators[op];\r
259         for(var i = 0, ci; ci = cs[i]; i++){\r
260             if(ci.nodeType != 1){\r
261                 continue;\r
262             }\r
263             var a;\r
264             if(st){\r
265                 a = Ext.DomQuery.getStyle(ci, attr);\r
266             }\r
267             else if(attr == "class" || attr == "className"){\r
268                 a = ci.className;\r
269             }else if(attr == "for"){\r
270                 a = ci.htmlFor;\r
271             }else if(attr == "href"){\r
272                 a = ci.getAttribute("href", 2);\r
273             }else{\r
274                 a = ci.getAttribute(attr);\r
275             }\r
276             if((f && f(a, value)) || (!f && a)){\r
277                 r[++ri] = ci;\r
278             }\r
279         }\r
280         return r;\r
281     };\r
282 \r
283     function byPseudo(cs, name, value){\r
284         return Ext.DomQuery.pseudos[name](cs, value);\r
285     };\r
286 \r
287     function nodupIEXml(cs){\r
288         var d = ++key, \r
289                 r;\r
290         cs[0].setAttribute("_nodup", d);\r
291         r = [cs[0]];\r
292         for(var i = 1, len = cs.length; i < len; i++){\r
293             var c = cs[i];\r
294             if(!c.getAttribute("_nodup") != d){\r
295                 c.setAttribute("_nodup", d);\r
296                 r[r.length] = c;\r
297             }\r
298         }\r
299         for(var i = 0, len = cs.length; i < len; i++){\r
300             cs[i].removeAttribute("_nodup");\r
301         }\r
302         return r;\r
303     }\r
304 \r
305     function nodup(cs){\r
306         if(!cs){\r
307             return [];\r
308         }\r
309         var len = cs.length, c, i, r = cs, cj, ri = -1;\r
310         if(!len || typeof cs.nodeType != "undefined" || len == 1){\r
311             return cs;\r
312         }\r
313         if(isIE && typeof cs[0].selectSingleNode != "undefined"){\r
314             return nodupIEXml(cs);\r
315         }\r
316         var d = ++key;\r
317         cs[0]._nodup = d;\r
318         for(i = 1; c = cs[i]; i++){\r
319             if(c._nodup != d){\r
320                 c._nodup = d;\r
321             }else{\r
322                 r = [];\r
323                 for(var j = 0; j < i; j++){\r
324                     r[++ri] = cs[j];\r
325                 }\r
326                 for(j = i+1; cj = cs[j]; j++){\r
327                     if(cj._nodup != d){\r
328                         cj._nodup = d;\r
329                         r[++ri] = cj;\r
330                     }\r
331                 }\r
332                 return r;\r
333             }\r
334         }\r
335         return r;\r
336     }\r
337 \r
338     function quickDiffIEXml(c1, c2){\r
339         var d = ++key,\r
340                 r = [];\r
341         for(var i = 0, len = c1.length; i < len; i++){\r
342             c1[i].setAttribute("_qdiff", d);\r
343         }        \r
344         for(var i = 0, len = c2.length; i < len; i++){\r
345             if(c2[i].getAttribute("_qdiff") != d){\r
346                 r[r.length] = c2[i];\r
347             }\r
348         }\r
349         for(var i = 0, len = c1.length; i < len; i++){\r
350            c1[i].removeAttribute("_qdiff");\r
351         }\r
352         return r;\r
353     }\r
354 \r
355     function quickDiff(c1, c2){\r
356         var len1 = c1.length,\r
357                 d = ++key,\r
358                 r = [];\r
359         if(!len1){\r
360             return c2;\r
361         }\r
362         if(isIE && c1[0].selectSingleNode){\r
363             return quickDiffIEXml(c1, c2);\r
364         }        \r
365         for(var i = 0; i < len1; i++){\r
366             c1[i]._qdiff = d;\r
367         }        \r
368         for(var i = 0, len = c2.length; i < len; i++){\r
369             if(c2[i]._qdiff != d){\r
370                 r[r.length] = c2[i];\r
371             }\r
372         }\r
373         return r;\r
374     }\r
375 \r
376     function quickId(ns, mode, root, id){\r
377         if(ns == root){\r
378            var d = root.ownerDocument || root;\r
379            return d.getElementById(id);\r
380         }\r
381         ns = getNodes(ns, mode, "*");\r
382         return byId(ns, null, id);\r
383     }\r
384 \r
385     return {\r
386         getStyle : function(el, name){\r
387             return Ext.fly(el).getStyle(name);\r
388         },\r
389         /**\r
390          * Compiles a selector/xpath query into a reusable function. The returned function\r
391          * takes one parameter "root" (optional), which is the context node from where the query should start.\r
392          * @param {String} selector The selector/xpath query\r
393          * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match\r
394          * @return {Function}\r
395          */\r
396         compile : function(path, type){\r
397             type = type || "select";\r
398 \r
399             var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],\r
400                 q = path, mode, lq,\r
401                 tk = Ext.DomQuery.matchers,\r
402                 tklen = tk.length,\r
403                 mm,\r
404                 // accept leading mode switch\r
405                 lmode = q.match(modeRe);\r
406             \r
407             if(lmode && lmode[1]){\r
408                 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';\r
409                 q = q.replace(lmode[1], "");\r
410             }\r
411             // strip leading slashes\r
412             while(path.substr(0, 1)=="/"){\r
413                 path = path.substr(1);\r
414             }\r
415 \r
416             while(q && lq != q){\r
417                 lq = q;\r
418                 var tm = q.match(tagTokenRe);\r
419                 if(type == "select"){\r
420                     if(tm){\r
421                         if(tm[1] == "#"){\r
422                             fn[fn.length] = 'n = quickId(n, mode, root, "'+tm[2]+'");';\r
423                         }else{\r
424                             fn[fn.length] = 'n = getNodes(n, mode, "'+tm[2]+'");';\r
425                         }\r
426                         q = q.replace(tm[0], "");\r
427                     }else if(q.substr(0, 1) != '@'){\r
428                         fn[fn.length] = 'n = getNodes(n, mode, "*");';\r
429                     }\r
430                 }else{\r
431                     if(tm){\r
432                         if(tm[1] == "#"){\r
433                             fn[fn.length] = 'n = byId(n, null, "'+tm[2]+'");';\r
434                         }else{\r
435                             fn[fn.length] = 'n = byTag(n, "'+tm[2]+'");';\r
436                         }\r
437                         q = q.replace(tm[0], "");\r
438                     }\r
439                 }\r
440                 while(!(mm = q.match(modeRe))){\r
441                     var matched = false;\r
442                     for(var j = 0; j < tklen; j++){\r
443                         var t = tk[j];\r
444                         var m = q.match(t.re);\r
445                         if(m){\r
446                             fn[fn.length] = t.select.replace(tplRe, function(x, i){\r
447                                                     return m[i];\r
448                                                 });\r
449                             q = q.replace(m[0], "");\r
450                             matched = true;\r
451                             break;\r
452                         }\r
453                     }\r
454                     // prevent infinite loop on bad selector\r
455                     if(!matched){\r
456                         throw 'Error parsing selector, parsing failed at "' + q + '"';\r
457                     }\r
458                 }\r
459                 if(mm[1]){\r
460                     fn[fn.length] = 'mode="'+mm[1].replace(trimRe, "")+'";';\r
461                     q = q.replace(mm[1], "");\r
462                 }\r
463             }\r
464             fn[fn.length] = "return nodup(n);\n}";\r
465             eval(fn.join(""));\r
466             return f;\r
467         },\r
468 \r
469         /**\r
470          * Selects a group of elements.\r
471          * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)\r
472          * @param {Node} root (optional) The start of the query (defaults to document).\r
473          * @return {Array} An Array of DOM elements which match the selector. If there are\r
474          * no matches, and empty Array is returned.\r
475          */\r
476         select : function(path, root, type){\r
477             if(!root || root == document){\r
478                 root = document;\r
479             }\r
480             if(typeof root == "string"){\r
481                 root = document.getElementById(root);\r
482             }\r
483             var paths = path.split(","),\r
484                 results = [];\r
485             for(var i = 0, len = paths.length; i < len; i++){\r
486                 var p = paths[i].replace(trimRe, "");\r
487                 if(!cache[p]){\r
488                     cache[p] = Ext.DomQuery.compile(p);\r
489                     if(!cache[p]){\r
490                         throw p + " is not a valid selector";\r
491                     }\r
492                 }\r
493                 var result = cache[p](root);\r
494                 if(result && result != document){\r
495                     results = results.concat(result);\r
496                 }\r
497             }\r
498             if(paths.length > 1){\r
499                 return nodup(results);\r
500             }\r
501             return results;\r
502         },\r
503 \r
504         /**\r
505          * Selects a single element.\r
506          * @param {String} selector The selector/xpath query\r
507          * @param {Node} root (optional) The start of the query (defaults to document).\r
508          * @return {Element} The DOM element which matched the selector.\r
509          */\r
510         selectNode : function(path, root){\r
511             return Ext.DomQuery.select(path, root)[0];\r
512         },\r
513 \r
514         /**\r
515          * Selects the value of a node, optionally replacing null with the defaultValue.\r
516          * @param {String} selector The selector/xpath query\r
517          * @param {Node} root (optional) The start of the query (defaults to document).\r
518          * @param {String} defaultValue\r
519          * @return {String}\r
520          */\r
521         selectValue : function(path, root, defaultValue){\r
522             path = path.replace(trimRe, "");\r
523             if(!valueCache[path]){\r
524                 valueCache[path] = Ext.DomQuery.compile(path, "select");\r
525             }\r
526             var n = valueCache[path](root),\r
527                 v;\r
528             n = n[0] ? n[0] : n;\r
529             v = (n && n.firstChild ? n.firstChild.nodeValue : null);\r
530             return ((v === null||v === undefined||v==='') ? defaultValue : v);\r
531         },\r
532 \r
533         /**\r
534          * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.\r
535          * @param {String} selector The selector/xpath query\r
536          * @param {Node} root (optional) The start of the query (defaults to document).\r
537          * @param {Number} defaultValue\r
538          * @return {Number}\r
539          */\r
540         selectNumber : function(path, root, defaultValue){\r
541             var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);\r
542             return parseFloat(v);\r
543         },\r
544 \r
545         /**\r
546          * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)\r
547          * @param {String/HTMLElement/Array} el An element id, element or array of elements\r
548          * @param {String} selector The simple selector to test\r
549          * @return {Boolean}\r
550          */\r
551         is : function(el, ss){\r
552             if(typeof el == "string"){\r
553                 el = document.getElementById(el);\r
554             }\r
555             var isArray = Ext.isArray(el),\r
556                 result = Ext.DomQuery.filter(isArray ? el : [el], ss);\r
557             return isArray ? (result.length == el.length) : (result.length > 0);\r
558         },\r
559 \r
560         /**\r
561          * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)\r
562          * @param {Array} el An array of elements to filter\r
563          * @param {String} selector The simple selector to test\r
564          * @param {Boolean} nonMatches If true, it returns the elements that DON'T match\r
565          * the selector instead of the ones that match\r
566          * @return {Array} An Array of DOM elements which match the selector. If there are\r
567          * no matches, and empty Array is returned.\r
568          */\r
569         filter : function(els, ss, nonMatches){\r
570             ss = ss.replace(trimRe, "");\r
571             if(!simpleCache[ss]){\r
572                 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");\r
573             }\r
574             var result = simpleCache[ss](els);\r
575             return nonMatches ? quickDiff(result, els) : result;\r
576         },\r
577 \r
578         /**\r
579          * Collection of matching regular expressions and code snippets.\r
580          */\r
581         matchers : [{\r
582                 re: /^\.([\w-]+)/,\r
583                 select: 'n = byClassName(n, null, " {1} ");'\r
584             }, {\r
585                 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,\r
586                 select: 'n = byPseudo(n, "{1}", "{2}");'\r
587             },{\r
588                 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,\r
589                 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'\r
590             }, {\r
591                 re: /^#([\w-]+)/,\r
592                 select: 'n = byId(n, null, "{1}");'\r
593             },{\r
594                 re: /^@([\w-]+)/,\r
595                 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'\r
596             }\r
597         ],\r
598 \r
599         /**\r
600          * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.\r
601          * New operators can be added as long as the match the format <i>c</i>= where <i>c</i> is any character other than space, &gt; &lt;.\r
602          */\r
603         operators : {\r
604             "=" : function(a, v){\r
605                 return a == v;\r
606             },\r
607             "!=" : function(a, v){\r
608                 return a != v;\r
609             },\r
610             "^=" : function(a, v){\r
611                 return a && a.substr(0, v.length) == v;\r
612             },\r
613             "$=" : function(a, v){\r
614                 return a && a.substr(a.length-v.length) == v;\r
615             },\r
616             "*=" : function(a, v){\r
617                 return a && a.indexOf(v) !== -1;\r
618             },\r
619             "%=" : function(a, v){\r
620                 return (a % v) == 0;\r
621             },\r
622             "|=" : function(a, v){\r
623                 return a && (a == v || a.substr(0, v.length+1) == v+'-');\r
624             },\r
625             "~=" : function(a, v){\r
626                 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;\r
627             }\r
628         },\r
629 \r
630         /**\r
631          * <p>Object hash of "pseudo class" filter functions which are used when filtering selections. Each function is passed\r
632          * two parameters:</p><div class="mdetail-params"><ul>\r
633          * <li><b>c</b> : Array<div class="sub-desc">An Array of DOM elements to filter.</div></li>\r
634          * <li><b>v</b> : String<div class="sub-desc">The argument (if any) supplied in the selector.</div></li>\r
635          * </ul></div>\r
636          * <p>A filter function returns an Array of DOM elements which conform to the pseudo class.</p>\r
637          * <p>In addition to the provided pseudo classes listed above such as <code>first-child</code> and <code>nth-child</code>,\r
638          * developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.</p>\r
639          * <p>For example, to filter <code>&lt;a></code> elements to only return links to <i>external</i> resources:</p>\r
640          * <code><pre>\r
641 Ext.DomQuery.pseudos.external = function(c, v){\r
642     var r = [], ri = -1;\r
643     for(var i = 0, ci; ci = c[i]; i++){\r
644 //      Include in result set only if it's a link to an external resource\r
645         if(ci.hostname != location.hostname){\r
646             r[++ri] = ci;\r
647         }\r
648     }\r
649     return r;\r
650 };</pre></code>\r
651          * Then external links could be gathered with the following statement:<code><pre>\r
652 var externalLinks = Ext.select("a:external");\r
653 </code></pre>\r
654          */\r
655         pseudos : {\r
656             "first-child" : function(c){\r
657                 var r = [], ri = -1, n;\r
658                 for(var i = 0, ci; ci = n = c[i]; i++){\r
659                     while((n = n.previousSibling) && n.nodeType != 1);\r
660                     if(!n){\r
661                         r[++ri] = ci;\r
662                     }\r
663                 }\r
664                 return r;\r
665             },\r
666 \r
667             "last-child" : function(c){\r
668                 var r = [], ri = -1, n;\r
669                 for(var i = 0, ci; ci = n = c[i]; i++){\r
670                     while((n = n.nextSibling) && n.nodeType != 1);\r
671                     if(!n){\r
672                         r[++ri] = ci;\r
673                     }\r
674                 }\r
675                 return r;\r
676             },\r
677 \r
678             "nth-child" : function(c, a) {\r
679                 var r = [], ri = -1,\r
680                         m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),\r
681                         f = (m[1] || 1) - 0, l = m[2] - 0;\r
682                 for(var i = 0, n; n = c[i]; i++){\r
683                     var pn = n.parentNode;\r
684                     if (batch != pn._batch) {\r
685                         var j = 0;\r
686                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){\r
687                             if(cn.nodeType == 1){\r
688                                cn.nodeIndex = ++j;\r
689                             }\r
690                         }\r
691                         pn._batch = batch;\r
692                     }\r
693                     if (f == 1) {\r
694                         if (l == 0 || n.nodeIndex == l){\r
695                             r[++ri] = n;\r
696                         }\r
697                     } else if ((n.nodeIndex + l) % f == 0){\r
698                         r[++ri] = n;\r
699                     }\r
700                 }\r
701 \r
702                 return r;\r
703             },\r
704 \r
705             "only-child" : function(c){\r
706                 var r = [], ri = -1;;\r
707                 for(var i = 0, ci; ci = c[i]; i++){\r
708                     if(!prev(ci) && !next(ci)){\r
709                         r[++ri] = ci;\r
710                     }\r
711                 }\r
712                 return r;\r
713             },\r
714 \r
715             "empty" : function(c){\r
716                 var r = [], ri = -1;\r
717                 for(var i = 0, ci; ci = c[i]; i++){\r
718                     var cns = ci.childNodes, j = 0, cn, empty = true;\r
719                     while(cn = cns[j]){\r
720                         ++j;\r
721                         if(cn.nodeType == 1 || cn.nodeType == 3){\r
722                             empty = false;\r
723                             break;\r
724                         }\r
725                     }\r
726                     if(empty){\r
727                         r[++ri] = ci;\r
728                     }\r
729                 }\r
730                 return r;\r
731             },\r
732 \r
733             "contains" : function(c, v){\r
734                 var r = [], ri = -1;\r
735                 for(var i = 0, ci; ci = c[i]; i++){\r
736                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){\r
737                         r[++ri] = ci;\r
738                     }\r
739                 }\r
740                 return r;\r
741             },\r
742 \r
743             "nodeValue" : function(c, v){\r
744                 var r = [], ri = -1;\r
745                 for(var i = 0, ci; ci = c[i]; i++){\r
746                     if(ci.firstChild && ci.firstChild.nodeValue == v){\r
747                         r[++ri] = ci;\r
748                     }\r
749                 }\r
750                 return r;\r
751             },\r
752 \r
753             "checked" : function(c){\r
754                 var r = [], ri = -1;\r
755                 for(var i = 0, ci; ci = c[i]; i++){\r
756                     if(ci.checked == true){\r
757                         r[++ri] = ci;\r
758                     }\r
759                 }\r
760                 return r;\r
761             },\r
762 \r
763             "not" : function(c, ss){\r
764                 return Ext.DomQuery.filter(c, ss, true);\r
765             },\r
766 \r
767             "any" : function(c, selectors){\r
768                 var ss = selectors.split('|'),\r
769                         r = [], ri = -1, s;\r
770                 for(var i = 0, ci; ci = c[i]; i++){\r
771                     for(var j = 0; s = ss[j]; j++){\r
772                         if(Ext.DomQuery.is(ci, s)){\r
773                             r[++ri] = ci;\r
774                             break;\r
775                         }\r
776                     }\r
777                 }\r
778                 return r;\r
779             },\r
780 \r
781             "odd" : function(c){\r
782                 return this["nth-child"](c, "odd");\r
783             },\r
784 \r
785             "even" : function(c){\r
786                 return this["nth-child"](c, "even");\r
787             },\r
788 \r
789             "nth" : function(c, a){\r
790                 return c[a-1] || [];\r
791             },\r
792 \r
793             "first" : function(c){\r
794                 return c[0] || [];\r
795             },\r
796 \r
797             "last" : function(c){\r
798                 return c[c.length-1] || [];\r
799             },\r
800 \r
801             "has" : function(c, ss){\r
802                 var s = Ext.DomQuery.select,\r
803                         r = [], ri = -1;\r
804                 for(var i = 0, ci; ci = c[i]; i++){\r
805                     if(s(ss, ci).length > 0){\r
806                         r[++ri] = ci;\r
807                     }\r
808                 }\r
809                 return r;\r
810             },\r
811 \r
812             "next" : function(c, ss){\r
813                 var is = Ext.DomQuery.is,\r
814                         r = [], ri = -1;\r
815                 for(var i = 0, ci; ci = c[i]; i++){\r
816                     var n = next(ci);\r
817                     if(n && is(n, ss)){\r
818                         r[++ri] = ci;\r
819                     }\r
820                 }\r
821                 return r;\r
822             },\r
823 \r
824             "prev" : function(c, ss){\r
825                 var is = Ext.DomQuery.is,\r
826                         r = [], ri = -1;\r
827                 for(var i = 0, ci; ci = c[i]; i++){\r
828                     var n = prev(ci);\r
829                     if(n && is(n, ss)){\r
830                         r[++ri] = ci;\r
831                     }\r
832                 }\r
833                 return r;\r
834             }\r
835         }\r
836     };\r
837 }();\r
838 \r
839 /**\r
840  * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}\r
841  * @param {String} path The selector/xpath query\r
842  * @param {Node} root (optional) The start of the query (defaults to document).\r
843  * @return {Array}\r
844  * @member Ext\r
845  * @method query\r
846  */\r
847 Ext.query = Ext.DomQuery.select;\r