Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / src / core / core / DomQuery.js
1 /*!
2  * Ext JS Library 3.0.0
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          * Collection of "pseudo class" processors. Each processor is passed the current nodeset (array)\r
632          * and the argument (if any) supplied in the selector.\r
633          */\r
634         pseudos : {\r
635             "first-child" : function(c){\r
636                 var r = [], ri = -1, n;\r
637                 for(var i = 0, ci; ci = n = c[i]; i++){\r
638                     while((n = n.previousSibling) && n.nodeType != 1);\r
639                     if(!n){\r
640                         r[++ri] = ci;\r
641                     }\r
642                 }\r
643                 return r;\r
644             },\r
645 \r
646             "last-child" : function(c){\r
647                 var r = [], ri = -1, n;\r
648                 for(var i = 0, ci; ci = n = c[i]; i++){\r
649                     while((n = n.nextSibling) && n.nodeType != 1);\r
650                     if(!n){\r
651                         r[++ri] = ci;\r
652                     }\r
653                 }\r
654                 return r;\r
655             },\r
656 \r
657             "nth-child" : function(c, a) {\r
658                 var r = [], ri = -1,\r
659                         m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),\r
660                         f = (m[1] || 1) - 0, l = m[2] - 0;\r
661                 for(var i = 0, n; n = c[i]; i++){\r
662                     var pn = n.parentNode;\r
663                     if (batch != pn._batch) {\r
664                         var j = 0;\r
665                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){\r
666                             if(cn.nodeType == 1){\r
667                                cn.nodeIndex = ++j;\r
668                             }\r
669                         }\r
670                         pn._batch = batch;\r
671                     }\r
672                     if (f == 1) {\r
673                         if (l == 0 || n.nodeIndex == l){\r
674                             r[++ri] = n;\r
675                         }\r
676                     } else if ((n.nodeIndex + l) % f == 0){\r
677                         r[++ri] = n;\r
678                     }\r
679                 }\r
680 \r
681                 return r;\r
682             },\r
683 \r
684             "only-child" : function(c){\r
685                 var r = [], ri = -1;;\r
686                 for(var i = 0, ci; ci = c[i]; i++){\r
687                     if(!prev(ci) && !next(ci)){\r
688                         r[++ri] = ci;\r
689                     }\r
690                 }\r
691                 return r;\r
692             },\r
693 \r
694             "empty" : function(c){\r
695                 var r = [], ri = -1;\r
696                 for(var i = 0, ci; ci = c[i]; i++){\r
697                     var cns = ci.childNodes, j = 0, cn, empty = true;\r
698                     while(cn = cns[j]){\r
699                         ++j;\r
700                         if(cn.nodeType == 1 || cn.nodeType == 3){\r
701                             empty = false;\r
702                             break;\r
703                         }\r
704                     }\r
705                     if(empty){\r
706                         r[++ri] = ci;\r
707                     }\r
708                 }\r
709                 return r;\r
710             },\r
711 \r
712             "contains" : function(c, v){\r
713                 var r = [], ri = -1;\r
714                 for(var i = 0, ci; ci = c[i]; i++){\r
715                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){\r
716                         r[++ri] = ci;\r
717                     }\r
718                 }\r
719                 return r;\r
720             },\r
721 \r
722             "nodeValue" : function(c, v){\r
723                 var r = [], ri = -1;\r
724                 for(var i = 0, ci; ci = c[i]; i++){\r
725                     if(ci.firstChild && ci.firstChild.nodeValue == v){\r
726                         r[++ri] = ci;\r
727                     }\r
728                 }\r
729                 return r;\r
730             },\r
731 \r
732             "checked" : function(c){\r
733                 var r = [], ri = -1;\r
734                 for(var i = 0, ci; ci = c[i]; i++){\r
735                     if(ci.checked == true){\r
736                         r[++ri] = ci;\r
737                     }\r
738                 }\r
739                 return r;\r
740             },\r
741 \r
742             "not" : function(c, ss){\r
743                 return Ext.DomQuery.filter(c, ss, true);\r
744             },\r
745 \r
746             "any" : function(c, selectors){\r
747                 var ss = selectors.split('|'),\r
748                         r = [], ri = -1, s;\r
749                 for(var i = 0, ci; ci = c[i]; i++){\r
750                     for(var j = 0; s = ss[j]; j++){\r
751                         if(Ext.DomQuery.is(ci, s)){\r
752                             r[++ri] = ci;\r
753                             break;\r
754                         }\r
755                     }\r
756                 }\r
757                 return r;\r
758             },\r
759 \r
760             "odd" : function(c){\r
761                 return this["nth-child"](c, "odd");\r
762             },\r
763 \r
764             "even" : function(c){\r
765                 return this["nth-child"](c, "even");\r
766             },\r
767 \r
768             "nth" : function(c, a){\r
769                 return c[a-1] || [];\r
770             },\r
771 \r
772             "first" : function(c){\r
773                 return c[0] || [];\r
774             },\r
775 \r
776             "last" : function(c){\r
777                 return c[c.length-1] || [];\r
778             },\r
779 \r
780             "has" : function(c, ss){\r
781                 var s = Ext.DomQuery.select,\r
782                         r = [], ri = -1;\r
783                 for(var i = 0, ci; ci = c[i]; i++){\r
784                     if(s(ss, ci).length > 0){\r
785                         r[++ri] = ci;\r
786                     }\r
787                 }\r
788                 return r;\r
789             },\r
790 \r
791             "next" : function(c, ss){\r
792                 var is = Ext.DomQuery.is,\r
793                         r = [], ri = -1;\r
794                 for(var i = 0, ci; ci = c[i]; i++){\r
795                     var n = next(ci);\r
796                     if(n && is(n, ss)){\r
797                         r[++ri] = ci;\r
798                     }\r
799                 }\r
800                 return r;\r
801             },\r
802 \r
803             "prev" : function(c, ss){\r
804                 var is = Ext.DomQuery.is,\r
805                         r = [], ri = -1;\r
806                 for(var i = 0, ci; ci = c[i]; i++){\r
807                     var n = prev(ci);\r
808                     if(n && is(n, ss)){\r
809                         r[++ri] = ci;\r
810                     }\r
811                 }\r
812                 return r;\r
813             }\r
814         }\r
815     };\r
816 }();\r
817 \r
818 /**\r
819  * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}\r
820  * @param {String} path The selector/xpath query\r
821  * @param {Node} root (optional) The start of the query (defaults to document).\r
822  * @return {Array}\r
823  * @member Ext\r
824  * @method query\r
825  */\r
826 Ext.query = Ext.DomQuery.select;\r