]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/dom/Query.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / dom / Query.js
CommitLineData
6527f429
DM
1/**\r
2 * @class Ext.dom.Query\r
3 * @alternateClassName Ext.DomQuery\r
4 * @alternateClassName Ext.core.DomQuery\r
5 * @singleton\r
6 *\r
7 * Provides high performance selector/xpath processing by compiling queries into reusable functions. New pseudo classes\r
8 * and matchers can be plugged. It works on HTML and XML documents (if a content node is passed in).\r
9 *\r
10 * DomQuery supports most of the [CSS3 selectors spec][1], along with some custom selectors and basic XPath.\r
11 *\r
12 * All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example\r
13 * `div.foo:nth-child(odd)[@foo=bar].bar:first` would be a perfectly valid selector. Node filters are processed\r
14 * in the order in which they appear, which allows you to optimize your queries for your document structure.\r
15 * \r
16 * ## Simple Selectors\r
17 * \r
18 * For performance reasons, some query methods accept selectors that are termed as **simple selectors**. A simple\r
19 * selector is a selector that does not include contextual information about any parent/sibling elements.\r
20 * \r
21 * Some examples of valid simple selectors:\r
22 * \r
23 * var simple = '.foo'; // Only asking for the class name on the element\r
24 * var simple = 'div.bar'; // Only asking for the tag/class name on the element\r
25 * var simple = '[href];' // Asking for an attribute on the element.\r
26 * var simple = ':not(.foo)'; // Only asking for the non-matches against the class name\r
27 * var simple = 'span:first-child'; // Doesn't require any contextual information about the parent node\r
28 * \r
29 * Simple examples of invalid simple selectors:\r
30 * \r
31 * var notSimple = 'div.foo div.bar'; // Requires matching a parent node by class name\r
32 * var notSimple = 'span + div'; // Requires matching a sibling by tag name\r
33 *\r
34 * ## Element Selectors:\r
35 *\r
36 * - **`*`** any element\r
37 * - **`E`** an element with the tag E\r
38 * - **`E F`** All descendent elements of E that have the tag F\r
39 * - **`E > F`** or **E/F** all direct children elements of E that have the tag F\r
40 * - **`E + F`** all elements with the tag F that are immediately preceded by an element with the tag E\r
41 * - **`E ~ F`** all elements with the tag F that are preceded by a sibling element with the tag E\r
42 *\r
43 * ## Attribute Selectors:\r
44 *\r
45 * The use of `@` and quotes are optional. For example, `div[@foo='bar']` is also a valid attribute selector.\r
46 *\r
47 * - **`E[foo]`** has an attribute "foo"\r
48 * - **`E[foo=bar]`** has an attribute "foo" that equals "bar"\r
49 * - **`E[foo^=bar]`** has an attribute "foo" that starts with "bar"\r
50 * - **`E[foo$=bar]`** has an attribute "foo" that ends with "bar"\r
51 * - **`E[foo*=bar]`** has an attribute "foo" that contains the substring "bar"\r
52 * - **`E[foo%=2]`** has an attribute "foo" that is evenly divisible by 2\r
53 * - **`E[foo!=bar]`** attribute "foo" does not equal "bar"\r
54 *\r
55 * ## Pseudo Classes:\r
56 *\r
57 * - **`E:first-child`** E is the first child of its parent\r
58 * - **`E:last-child`** E is the last child of its parent\r
59 * - **`E:nth-child(_n_)`** E is the _n_th child of its parent (1 based as per the spec)\r
60 * - **`E:nth-child(odd)`** E is an odd child of its parent\r
61 * - **`E:nth-child(even)`** E is an even child of its parent\r
62 * - **`E:only-child`** E is the only child of its parent\r
63 * - **`E:checked`** E is an element that is has a checked attribute that is true (e.g. a radio or checkbox)\r
64 * - **`E:first`** the first E in the resultset\r
65 * - **`E:last`** the last E in the resultset\r
66 * - **`E:nth(_n_)`** the _n_th E in the resultset (1 based)\r
67 * - **`E:odd`** shortcut for :nth-child(odd)\r
68 * - **`E:even`** shortcut for :nth-child(even)\r
69 * - **`E:contains(foo)`** E's innerHTML contains the substring "foo"\r
70 * - **`E:nodeValue(foo)`** E contains a textNode with a nodeValue that equals "foo"\r
71 * - **`E:not(S)`** an E element that does not match simple selector S\r
72 * - **`E:has(S)`** an E element that has a descendent that matches simple selector S\r
73 * - **`E:next(S)`** an E element whose next sibling matches simple selector S\r
74 * - **`E:prev(S)`** an E element whose previous sibling matches simple selector S\r
75 * - **`E:any(S1|S2|S2)`** an E element which matches any of the simple selectors S1, S2 or S3\r
76 * - **`E:visible(true)`** an E element which is deeply visible according to {@link Ext.dom.Element#isVisible}\r
77 *\r
78 * ## CSS Value Selectors:\r
79 *\r
80 * - **`E{display=none}`** css value "display" that equals "none"\r
81 * - **`E{display^=none}`** css value "display" that starts with "none"\r
82 * - **`E{display$=none}`** css value "display" that ends with "none"\r
83 * - **`E{display*=none}`** css value "display" that contains the substring "none"\r
84 * - **`E{display%=2}`** css value "display" that is evenly divisible by 2\r
85 * - **`E{display!=none}`** css value "display" that does not equal "none"\r
86 * \r
87 * ## XML Namespaces:\r
88 * - **`ns|E`** an element with tag E and namespace prefix ns\r
89 *\r
90 * [1]: http://www.w3.org/TR/2005/WD-css3-selectors-20051215/#selectors\r
91 */\r
92Ext.define('Ext.dom.Query', function() {\r
93 var DQ,\r
94 doc = document,\r
95 cache, simpleCache, valueCache,\r
96 useClassList = !!doc.documentElement.classList,\r
97 useElementPointer = !!doc.documentElement.firstElementChild,\r
98 useChildrenCollection = (function() {\r
99 var d = doc.createElement('div');\r
100 d.innerHTML = '<!-- -->text<!-- -->';\r
101 return d.children && (d.children.length === 0);\r
102 })(),\r
103 nonSpace = /\S/,\r
104 trimRe = /^\s+|\s+$/g,\r
105 tplRe = /\{(\d+)\}/g,\r
106 modeRe = /^(\s?[\/>+~]\s?|\s|$)/,\r
107 tagTokenRe = /^(#)?([\w\-\*\|\\]+)/,\r
108 nthRe = /(\d*)n\+?(\d*)/,\r
109 nthRe2 = /\D/,\r
110 startIdRe = /^\s*#/,\r
111 // This is for IE MSXML which does not support expandos.\r
112 // IE runs the same speed using setAttribute, however FF slows way down\r
113 // and Safari completely fails so they need to continue to use expandos.\r
114 isIE = window.ActiveXObject ? true : false,\r
115 key = 30803,\r
116 longHex = /\\([0-9a-fA-F]{6})/g,\r
117 shortHex = /\\([0-9a-fA-F]{1,6})\s{0,1}/g,\r
118 nonHex = /\\([^0-9a-fA-F]{1})/g,\r
119 escapes = /\\/g,\r
120 num, hasEscapes,\r
121 // True if the browser supports the following syntax:\r
122 // document.getElementsByTagName('namespacePrefix:tagName')\r
123 supportsColonNsSeparator = (function () {\r
124 var xmlDoc,\r
125 xmlString = '<r><a:b xmlns:a="n"></a:b></r>';\r
126\r
127 if (window.DOMParser) {\r
128 xmlDoc = (new DOMParser()).parseFromString(xmlString, "application/xml");\r
129 } else {\r
130 xmlDoc = new ActiveXObject("Microsoft.XMLDOM");\r
131 xmlDoc.loadXML(xmlString);\r
132 }\r
133\r
134 return !!xmlDoc.getElementsByTagName('a:b').length;\r
135 })(),\r
136\r
137 // replaces a long hex regex match group with the appropriate ascii value\r
138 // $args indicate regex match pos\r
139 longHexToChar = function($0, $1) {\r
140 return String.fromCharCode(parseInt($1, 16));\r
141 },\r
142\r
143 // converts a shortHex regex match to the long form\r
144 shortToLongHex = function($0, $1) {\r
145 while ($1.length < 6) {\r
146 $1 = '0' + $1;\r
147 }\r
148 return '\\' + $1;\r
149 },\r
150\r
151 // converts a single char escape to long escape form\r
152 charToLongHex = function($0, $1) {\r
153 num = $1.charCodeAt(0).toString(16);\r
154 if (num.length === 1) {\r
155 num = '0' + num;\r
156 }\r
157 return '\\0000' + num;\r
158 },\r
159\r
160 // Un-escapes an input selector string. Assumes all escape sequences have been\r
161 // normalized to the css '\\0000##' 6-hex-digit style escape sequence :\r
162 // will not handle any other escape formats\r
163 unescapeCssSelector = function(selector) {\r
164 return (hasEscapes) ? selector.replace(longHex, longHexToChar) : selector;\r
165 },\r
166\r
167 // checks if the path has escaping & does any appropriate replacements\r
168 setupEscapes = function(path) {\r
169 hasEscapes = (path.indexOf('\\') > -1);\r
170 if (hasEscapes) {\r
171 path = path\r
172 .replace(shortHex, shortToLongHex)\r
173 .replace(nonHex, charToLongHex)\r
174 .replace(escapes, '\\\\'); // double the '\' for js compilation\r
175 }\r
176 return path;\r
177 };\r
178\r
179 // this eval is stop the compressor from\r
180 // renaming the variable to something shorter\r
181 eval("var batch = 30803, child, next, prev, byClassName;");\r
182\r
183 // Retrieve the child node from a particular\r
184 // parent at the specified index.\r
185 child = useChildrenCollection ?\r
186 function child(parent, index) {\r
187 return parent.children[index];\r
188 } :\r
189 function child(parent, index) {\r
190 var i = 0,\r
191 n = parent.firstChild;\r
192 while (n) {\r
193 if (n.nodeType == 1) {\r
194 if (++i == index) {\r
195 return n;\r
196 }\r
197 }\r
198 n = n.nextSibling;\r
199 }\r
200 return null;\r
201 };\r
202\r
203 // retrieve the next element node\r
204 next = useElementPointer ?\r
205 function(n) {\r
206 return n.nextElementSibling;\r
207 } :\r
208 function(n) {\r
209 while ((n = n.nextSibling) && n.nodeType != 1);\r
210 return n;\r
211 };\r
212\r
213 // retrieve the previous element node\r
214 prev = useElementPointer ?\r
215 function(n) {\r
216 return n.previousElementSibling;\r
217 } :\r
218 function(n) {\r
219 while ((n = n.previousSibling) && n.nodeType != 1);\r
220 return n;\r
221 };\r
222\r
223 // Mark each child node with a nodeIndex skipping and\r
224 // removing empty text nodes.\r
225 function children(parent) {\r
226 var n = parent.firstChild,\r
227 nodeIndex = -1,\r
228 nextNode;\r
229\r
230 while (n) {\r
231 nextNode = n.nextSibling;\r
232 // clean worthless empty nodes.\r
233 if (n.nodeType == 3 && !nonSpace.test(n.nodeValue)) {\r
234 parent.removeChild(n);\r
235 } else {\r
236 // add an expando nodeIndex\r
237 n.nodeIndex = ++nodeIndex;\r
238 }\r
239 n = nextNode;\r
240 }\r
241 return this;\r
242 }\r
243\r
244 // nodeSet - array of nodes\r
245 // cls - CSS Class\r
246 byClassName = useClassList ? // Use classList API where available: http://jsperf.com/classlist-vs-old-school-check/\r
247 function (nodeSet, cls) {\r
248 cls = unescapeCssSelector(cls);\r
249 if (!cls) {\r
250 return nodeSet;\r
251 }\r
252 var result = [], ri = -1,\r
253 i, ci, classList;\r
254\r
255 for (i = 0; ci = nodeSet[i]; i++) {\r
256 classList = ci.classList;\r
257 if (classList) {\r
258 if (classList.contains(cls)) {\r
259 result[++ri] = ci;\r
260 }\r
261 } else if ((' ' + ci.className + ' ').indexOf(cls) !== -1) {\r
262 // Some elements types (SVG) may not always have a classList\r
263 // in some browsers, so fallback to the old style here\r
264 result[++ri] = ci;\r
265 }\r
266 }\r
267 return result;\r
268 } :\r
269 function (nodeSet, cls) {\r
270 cls = unescapeCssSelector(cls);\r
271 if (!cls) {\r
272 return nodeSet;\r
273 }\r
274 var result = [], ri = -1,\r
275 i, ci;\r
276\r
277 for (i = 0; ci = nodeSet[i]; i++) {\r
278 if ((' ' + ci.className + ' ').indexOf(cls) !== -1) {\r
279 result[++ri] = ci;\r
280 }\r
281 }\r
282 return result;\r
283 };\r
284\r
285 function attrValue(n, attr) {\r
286 // if its an array, use the first node.\r
287 if (!n.tagName && typeof n.length != "undefined") {\r
288 n = n[0];\r
289 }\r
290 if (!n) {\r
291 return null;\r
292 }\r
293\r
294 if (attr == "for") {\r
295 return n.htmlFor;\r
296 }\r
297 if (attr == "class" || attr == "className") {\r
298 return n.className;\r
299 }\r
300 return n.getAttribute(attr) || n[attr];\r
301\r
302 }\r
303\r
304 // ns - nodes\r
305 // mode - false, /, >, +, ~\r
306 // tagName - defaults to "*"\r
307 function getNodes(ns, mode, tagName) {\r
308 var result = [], ri = -1, cs,\r
309 i, ni, j, ci, cn, utag, n, cj;\r
310 if (!ns) {\r
311 return result;\r
312 }\r
313 tagName = tagName.replace('|', ':') || "*";\r
314 // convert to array\r
315 if (typeof ns.getElementsByTagName != "undefined") {\r
316 ns = [ns];\r
317 }\r
318\r
319 // no mode specified, grab all elements by tagName\r
320 // at any depth\r
321 if (!mode) {\r
322 tagName = unescapeCssSelector(tagName);\r
323 if (!supportsColonNsSeparator && DQ.isXml(ns[0]) &&\r
324 tagName.indexOf(':') !== -1) {\r
325 // Some browsers (e.g. WebKit and Opera do not support the following syntax\r
326 // in xml documents: getElementsByTagName('ns:tagName'). To work around\r
327 // this, we remove the namespace prefix from the tagName, get the elements\r
328 // by tag name only, and then compare each element's tagName property to\r
329 // the tagName with namespace prefix attached to ensure that the tag is in\r
330 // the proper namespace.\r
331 for (i = 0; ni = ns[i]; i++) {\r
332 cs = ni.getElementsByTagName(tagName.split(':').pop());\r
333 for (j = 0; ci = cs[j]; j++) {\r
334 if (ci.tagName === tagName) {\r
335 result[++ri] = ci;\r
336 }\r
337 }\r
338 }\r
339 } else {\r
340 for (i = 0; ni = ns[i]; i++) {\r
341 cs = ni.getElementsByTagName(tagName);\r
342 for (j = 0; ci = cs[j]; j++) {\r
343 result[++ri] = ci;\r
344 }\r
345 }\r
346 }\r
347 // Direct Child mode (/ or >)\r
348 // E > F or E/F all direct children elements of E that have the tag\r
349 } else if (mode == "/" || mode == ">") {\r
350 utag = tagName.toUpperCase();\r
351 for (i = 0; ni = ns[i]; i++) {\r
352 cn = ni.childNodes;\r
353 for (j = 0; cj = cn[j]; j++) {\r
354 if (cj.nodeName == utag || cj.nodeName == tagName || tagName == '*') {\r
355 result[++ri] = cj;\r
356 }\r
357 }\r
358 }\r
359 // Immediately Preceding mode (+)\r
360 // E + F all elements with the tag F that are immediately preceded by an element with the tag E\r
361 } else if (mode == "+") {\r
362 utag = tagName.toUpperCase();\r
363 for (i = 0; n = ns[i]; i++) {\r
364 while ((n = n.nextSibling) && n.nodeType != 1);\r
365 if (n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')) {\r
366 result[++ri] = n;\r
367 }\r
368 }\r
369 // Sibling mode (~)\r
370 // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E\r
371 } else if (mode == "~") {\r
372 utag = tagName.toUpperCase();\r
373 for (i = 0; n = ns[i]; i++) {\r
374 while ((n = n.nextSibling)) {\r
375 if (n.nodeName == utag || n.nodeName == tagName || tagName == '*') {\r
376 result[++ri] = n;\r
377 }\r
378 }\r
379 }\r
380 }\r
381 return result;\r
382 }\r
383\r
384 function concat(a, b) {\r
385 a.push.apply(a, b);\r
386 return a;\r
387 }\r
388\r
389 function byTag(cs, tagName) {\r
390 if (cs.tagName || cs === doc) {\r
391 cs = [cs];\r
392 }\r
393 if (!tagName) {\r
394 return cs;\r
395 }\r
396 var result = [], ri = -1,\r
397 i, ci;\r
398 tagName = tagName.toLowerCase();\r
399 for (i = 0; ci = cs[i]; i++) {\r
400 if (ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName) {\r
401 result[++ri] = ci;\r
402 }\r
403 }\r
404 return result;\r
405 }\r
406\r
407 function byId(cs, id) {\r
408 id = unescapeCssSelector(id);\r
409 if (cs.tagName || cs === doc) {\r
410 cs = [cs];\r
411 }\r
412 if (!id) {\r
413 return cs;\r
414 }\r
415 var result = [], ri = -1,\r
416 i, ci;\r
417 for (i = 0; ci = cs[i]; i++) {\r
418 if (ci && ci.id == id) {\r
419 result[++ri] = ci;\r
420 return result;\r
421 }\r
422 }\r
423 return result;\r
424 }\r
425\r
426 // operators are =, !=, ^=, $=, *=, %=, |= and ~=\r
427 // custom can be "{"\r
428 function byAttribute(cs, attr, value, op, custom) {\r
429 var result = [],\r
430 ri = -1,\r
431 useGetStyle = custom == "{",\r
432 fn = DQ.operators[op],\r
433 a,\r
434 xml,\r
435 hasXml,\r
436 i, ci;\r
437\r
438 value = unescapeCssSelector(value);\r
439\r
440 for (i = 0; ci = cs[i]; i++) {\r
441 // skip non-element nodes.\r
442 if (ci.nodeType === 1) {\r
443 // only need to do this for the first node\r
444 if (!hasXml) {\r
445 xml = DQ.isXml(ci);\r
446 hasXml = true;\r
447 }\r
448\r
449 // we only need to change the property names if we're dealing with html nodes, not XML\r
450 if (!xml) {\r
451 if (useGetStyle) {\r
452 a = DQ.getStyle(ci, attr);\r
453 } else if (attr == "class" || attr == "className") {\r
454 a = ci.className;\r
455 } else if (attr == "for") {\r
456 a = ci.htmlFor;\r
457 } else if (attr == "href") {\r
458 // getAttribute href bug\r
459 // http://www.glennjones.net/Post/809/getAttributehrefbug.htm\r
460 a = ci.getAttribute("href", 2);\r
461 } else {\r
462 a = ci.getAttribute(attr);\r
463 }\r
464 } else {\r
465 a = ci.getAttribute(attr);\r
466 }\r
467 if ((fn && fn(a, value)) || (!fn && a)) {\r
468 result[++ri] = ci;\r
469 }\r
470 }\r
471 }\r
472 return result;\r
473 }\r
474\r
475 function byPseudo(cs, name, value) {\r
476 value = unescapeCssSelector(value);\r
477 return DQ.pseudos[name](cs, value);\r
478 }\r
479\r
480 function nodupIEXml(cs) {\r
481 var d = ++key,\r
482 r,\r
483 i, len, c;\r
484 cs[0].setAttribute("_nodup", d);\r
485 r = [cs[0]];\r
486 for (i = 1, len = cs.length; i < len; i++) {\r
487 c = cs[i];\r
488 if (!c.getAttribute("_nodup") != d) {\r
489 c.setAttribute("_nodup", d);\r
490 r[r.length] = c;\r
491 }\r
492 }\r
493 for (i = 0, len = cs.length; i < len; i++) {\r
494 cs[i].removeAttribute("_nodup");\r
495 }\r
496 return r;\r
497 }\r
498\r
499 function nodup(cs) {\r
500 if (!cs) {\r
501 return [];\r
502 }\r
503 var len = cs.length, c, i, r = cs, cj, ri = -1, d, j;\r
504 if (!len || typeof cs.nodeType != "undefined" || len == 1) {\r
505 return cs;\r
506 }\r
507 if (isIE && typeof cs[0].selectSingleNode != "undefined") {\r
508 return nodupIEXml(cs);\r
509 }\r
510 d = ++key;\r
511 cs[0]._nodup = d;\r
512 for (i = 1; c = cs[i]; i++) {\r
513 if (c._nodup != d) {\r
514 c._nodup = d;\r
515 } else {\r
516 r = [];\r
517 for (j = 0; j < i; j++) {\r
518 r[++ri] = cs[j];\r
519 }\r
520 for (j = i + 1; cj = cs[j]; j++) {\r
521 if (cj._nodup != d) {\r
522 cj._nodup = d;\r
523 r[++ri] = cj;\r
524 }\r
525 }\r
526 return r;\r
527 }\r
528 }\r
529 return r;\r
530 }\r
531\r
532 function quickDiffIEXml(c1, c2) {\r
533 var d = ++key,\r
534 r = [],\r
535 i, len;\r
536 for (i = 0, len = c1.length; i < len; i++) {\r
537 c1[i].setAttribute("_qdiff", d);\r
538 }\r
539 for (i = 0, len = c2.length; i < len; i++) {\r
540 if (c2[i].getAttribute("_qdiff") != d) {\r
541 r[r.length] = c2[i];\r
542 }\r
543 }\r
544 for (i = 0, len = c1.length; i < len; i++) {\r
545 c1[i].removeAttribute("_qdiff");\r
546 }\r
547 return r;\r
548 }\r
549\r
550 function quickDiff(c1, c2) {\r
551 var len1 = c1.length,\r
552 d = ++key,\r
553 r = [],\r
554 i, len;\r
555 if (!len1) {\r
556 return c2;\r
557 }\r
558 if (isIE && typeof c1[0].selectSingleNode != "undefined") {\r
559 return quickDiffIEXml(c1, c2);\r
560 }\r
561 for (i = 0; i < len1; i++) {\r
562 c1[i]._qdiff = d;\r
563 }\r
564 for (i = 0, len = c2.length; i < len; i++) {\r
565 if (c2[i]._qdiff != d) {\r
566 r[r.length] = c2[i];\r
567 }\r
568 }\r
569 return r;\r
570 }\r
571\r
572 function quickId(ns, mode, root, id) {\r
573 if (ns == root) {\r
574 id = unescapeCssSelector(id);\r
575 var d = root.ownerDocument || root;\r
576 return d.getElementById(id);\r
577 }\r
578 ns = getNodes(ns, mode, "*");\r
579 return byId(ns, id);\r
580 }\r
581\r
582 return {\r
583 singleton: true,\r
584\r
585 alternateClassName: [\r
586 'Ext.core.DomQuery',\r
587 'Ext.DomQuery'\r
588 ],\r
589\r
590 requires: [\r
591 'Ext.dom.Helper',\r
592 'Ext.util.Operators'\r
593 ],\r
594\r
595 _init: function() {\r
596 DQ = this;\r
597 DQ.operators = Ext.Object.chain(Ext.util.Operators); // can capture now\r
598 DQ._cache = cache = new Ext.util.LruCache({\r
599 maxSize: 200\r
600 });\r
601 DQ._valueCache = valueCache = new Ext.util.LruCache({\r
602 maxSize: 200\r
603 });\r
604 DQ._simpleCache = simpleCache = new Ext.util.LruCache({\r
605 maxSize: 200\r
606 });\r
607 },\r
608\r
609 clearCache: function () {\r
610 cache.clear();\r
611 valueCache.clear();\r
612 simpleCache.clear();\r
613 },\r
614\r
615 getStyle: function(el, name) {\r
616 return Ext.fly(el, '_DomQuery').getStyle(name);\r
617 },\r
618\r
619 /**\r
620 * Compiles a selector/xpath query into a reusable function. The returned function\r
621 * takes one parameter "root" (optional), which is the context node from where the query should start.\r
622 * @param {String} selector The selector/xpath query\r
623 * @param {String} [type="select"] Either "select" or "simple" for a simple selector match\r
624 * @return {Function}\r
625 */\r
626 compile: function(path, type) {\r
627 type = type || "select";\r
628\r
629 // setup fn preamble\r
630 var fn = ["var f = function(root) {\n var mode; ++batch; var n = root || document;\n"],\r
631 lastPath,\r
632 matchers = DQ.matchers,\r
633 matchersLn = matchers.length,\r
634 modeMatch,\r
635 // accept leading mode switch\r
636 lmode = path.match(modeRe),\r
637 tokenMatch, matched, j, t, m;\r
638\r
639 path = setupEscapes(path);\r
640\r
641 if (lmode && lmode[1]) {\r
642 fn[fn.length] = 'mode="' + lmode[1].replace(trimRe, "") + '";';\r
643 path = path.replace(lmode[1], "");\r
644 }\r
645\r
646 // strip leading slashes\r
647 while (path.substr(0, 1) == "/") {\r
648 path = path.substr(1);\r
649 }\r
650\r
651 while (path && lastPath != path) {\r
652 lastPath = path;\r
653 tokenMatch = path.match(tagTokenRe);\r
654 if (type == "select") {\r
655 if (tokenMatch) {\r
656 // ID Selector\r
657 if (tokenMatch[1] == "#") {\r
658 fn[fn.length] = 'n = quickId(n, mode, root, "' + tokenMatch[2] + '");';\r
659 } else {\r
660 fn[fn.length] = 'n = getNodes(n, mode, "' + tokenMatch[2] + '");';\r
661 }\r
662 path = path.replace(tokenMatch[0], "");\r
663 } else if (path.substr(0, 1) != '@') {\r
664 fn[fn.length] = 'n = getNodes(n, mode, "*");';\r
665 }\r
666 // type of "simple"\r
667 } else {\r
668 if (tokenMatch) {\r
669 if (tokenMatch[1] == "#") {\r
670 fn[fn.length] = 'n = byId(n, "' + tokenMatch[2] + '");';\r
671 } else {\r
672 fn[fn.length] = 'n = byTag(n, "' + tokenMatch[2] + '");';\r
673 }\r
674 path = path.replace(tokenMatch[0], "");\r
675 }\r
676 }\r
677 while (!(modeMatch = path.match(modeRe))) {\r
678 matched = false;\r
679 for (j = 0; j < matchersLn; j++) {\r
680 t = matchers[j];\r
681 m = path.match(t.re);\r
682 if (m) {\r
683 fn[fn.length] = t.select.replace(tplRe, function(x, i) {\r
684 return m[i];\r
685 });\r
686 path = path.replace(m[0], "");\r
687 matched = true;\r
688 break;\r
689 }\r
690 }\r
691 // prevent infinite loop on bad selector\r
692 if (!matched) {\r
693 Ext.raise({\r
694 sourceClass:'Ext.DomQuery',\r
695 sourceMethod:'compile',\r
696 msg:'Error parsing selector. Parsing failed at "' + path + '"'\r
697 });\r
698 }\r
699 }\r
700 if (modeMatch[1]) {\r
701 fn[fn.length] = 'mode="' + modeMatch[1].replace(trimRe, "") + '";';\r
702 path = path.replace(modeMatch[1], "");\r
703 }\r
704 }\r
705 // close fn out\r
706 fn[fn.length] = "return nodup(n);\n}";\r
707\r
708 // eval fn and return it\r
709 eval(fn.join(""));\r
710 return f;\r
711 },\r
712\r
713 /**\r
714 * Selects an array of DOM nodes using JavaScript-only implementation.\r
715 *\r
716 * Use {@link #select} to take advantage of browsers built-in support for CSS selectors.\r
717 * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)\r
718 * @param {HTMLElement/String} [root=document] The start of the query.\r
719 * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are\r
720 * no matches, and empty Array is returned.\r
721 */\r
722 jsSelect: function(path, root, type) {\r
723 // set root to doc if not specified.\r
724 root = root || doc;\r
725\r
726 if (typeof root == "string") {\r
727 root = doc.getElementById(root);\r
728 }\r
729 var paths = Ext.splitAndUnescape(path, ","),\r
730 results = [],\r
731 query, i, len, subPath, result;\r
732\r
733 // loop over each selector\r
734 for (i = 0, len = paths.length; i < len; i++) {\r
735 subPath = paths[i].replace(trimRe, "");\r
736 // compile and place in cache\r
737 query = cache.get(subPath);\r
738 if (!query) {\r
739 // When we compile, escaping is handled inside the compile method\r
740 query = DQ.compile(subPath, type);\r
741 if (!query) {\r
742 Ext.raise({\r
743 sourceClass:'Ext.DomQuery',\r
744 sourceMethod:'jsSelect',\r
745 msg:subPath + ' is not a valid selector'\r
746 });\r
747 }\r
748 cache.add(subPath, query);\r
749 } else {\r
750 // If we've already compiled, we still need to check if the\r
751 // selector has escaping and setup the appropriate flags\r
752 setupEscapes(subPath);\r
753 }\r
754 result = query(root);\r
755 if (result && result !== doc) {\r
756 results = results.concat(result);\r
757 }\r
758 }\r
759\r
760 // if there were multiple selectors, make sure dups\r
761 // are eliminated\r
762 if (paths.length > 1) {\r
763 return nodup(results);\r
764 }\r
765 return results;\r
766 },\r
767\r
768 isXml: function(el) {\r
769 var docEl = (el ? el.ownerDocument || el : 0).documentElement;\r
770 return docEl ? docEl.nodeName !== "HTML" : false;\r
771 },\r
772\r
773 /**\r
774 * Selects an array of DOM nodes by CSS/XPath selector.\r
775 *\r
776 * Uses [document.querySelectorAll][0] if browser supports that, otherwise falls back to\r
777 * {@link Ext.dom.Query#jsSelect} to do the work.\r
778 *\r
779 * [0]: https://developer.mozilla.org/en/DOM/document.querySelectorAll\r
780 *\r
781 * @param {String} path The selector/xpath query\r
782 * @param {HTMLElement} [root=document] The start of the query.\r
783 * @return {HTMLElement[]} An array of DOM elements (not a NodeList as returned by `querySelectorAll`).\r
784 * @param {String} [type="select"] Either "select" or "simple" for a simple selector match (only valid when\r
785 * used when the call is deferred to the jsSelect method)\r
786 * @param {Boolean} [single] Pass `true` to select only the first matching node using `document.querySelector` (where available)\r
787 * @method\r
788 */\r
789 select: doc.querySelectorAll ? function(path, root, type, single) {\r
790 root = root || doc;\r
791 if (!DQ.isXml(root)) {\r
792 try {\r
793 /*\r
794 * This checking here is to "fix" the behaviour of querySelectorAll\r
795 * for non root document queries. The way qsa works is intentional,\r
796 * however it's definitely not the expected way it should work.\r
797 * When descendant selectors are used, only the lowest selector must be inside the root!\r
798 * More info: http://ejohn.org/blog/thoughts-on-queryselectorall/\r
799 * So we create a descendant selector by prepending the root's ID, and query the parent node.\r
800 * UNLESS the root has no parent in which qsa will work perfectly.\r
801 *\r
802 * We only modify the path for single selectors (ie, no multiples),\r
803 * without a full parser it makes it difficult to do this correctly.\r
804 */\r
805 if (root.parentNode && (root.nodeType !== 9) && path.indexOf(',') === -1 && !startIdRe.test(path)) {\r
806 path = Ext.makeIdSelector(Ext.id(root)) + ' ' + path;\r
807 root = root.parentNode;\r
808 }\r
809 return single ? [ root.querySelector(path) ]\r
810 : Ext.Array.toArray(root.querySelectorAll(path));\r
811 }\r
812 catch (e) {\r
813 }\r
814 }\r
815 return DQ.jsSelect.call(this, path, root, type);\r
816 } : function(path, root, type) {\r
817 return DQ.jsSelect.call(this, path, root, type);\r
818 },\r
819\r
820 /**\r
821 * Selects a single element.\r
822 * @param {String} selector The selector/xpath query\r
823 * @param {HTMLElement} [root=document] The start of the query.\r
824 * @return {HTMLElement} The DOM element which matched the selector.\r
825 */\r
826 selectNode: function(path, root){\r
827 return Ext.DomQuery.select(path, root, null, true)[0];\r
828 },\r
829\r
830 /**\r
831 * Selects the value of a node, optionally replacing null with the defaultValue.\r
832 * @param {String} selector The selector/xpath query\r
833 * @param {HTMLElement} [root=document] The start of the query.\r
834 * @param {String} [defaultValue] When specified, this is return as empty value.\r
835 * @return {String}\r
836 */\r
837 selectValue: function(path, root, defaultValue) {\r
838 path = path.replace(trimRe, "");\r
839 var query = valueCache.get(path),\r
840 n, v;\r
841\r
842 if (!query) {\r
843 query = DQ.compile(path, "select");\r
844 valueCache.add(path, query);\r
845 } else {\r
846 setupEscapes(path);\r
847 }\r
848\r
849 n = query(root);\r
850 return DQ.getNodeValue(n[0] || n, defaultValue);\r
851 },\r
852\r
853 /**\r
854 * Get the text value for a node, optionally replacing null with the defaultValue.\r
855 * @param {Object} node The node\r
856 * @param {String} [defaultValue] When specified, this is return as empty value.\r
857 * @return {String} The value\r
858 */\r
859 getNodeValue: function(node, defaultValue) {\r
860 // overcome a limitation of maximum textnode size\r
861 // Rumored to potentially crash IE6 but has not been confirmed.\r
862 // http://reference.sitepoint.com/javascript/Node/normalize\r
863 // https://developer.mozilla.org/En/DOM/Node.normalize\r
864 if (typeof node.normalize == 'function') {\r
865 node.normalize();\r
866 }\r
867\r
868 var firstChild = node && node.firstChild,\r
869 v = firstChild ? firstChild.nodeValue : null;\r
870\r
871 // If we have a defaultValue, and v is null, undefined or '', use that\r
872 // value instead.\r
873 //\r
874 if (defaultValue !== undefined && (v == null || v === '')) {\r
875 v = defaultValue;\r
876 }\r
877\r
878 return v;\r
879 },\r
880\r
881 /**\r
882 * Selects the value of a node, parsing integers and floats.\r
883 * Returns the defaultValue, or 0 if none is specified.\r
884 * @param {String} selector The selector/xpath query\r
885 * @param {HTMLElement} [root=document] The start of the query.\r
886 * @param {Number} [defaultValue] When specified, this is return as empty value.\r
887 * @return {Number}\r
888 */\r
889 selectNumber: function(path, root, defaultValue) {\r
890 var v = DQ.selectValue(path, root, defaultValue || 0);\r
891 return parseFloat(v);\r
892 },\r
893\r
894 /**\r
895 * Returns true if the passed element(s) match the passed simple selector\r
896 * @param {String/HTMLElement/HTMLElement[]} el An element id, element or array of elements\r
897 * @param {String} selector The simple selector to test\r
898 * @return {Boolean}\r
899 */\r
900 is: function(el, ss) {\r
901 if (typeof el == "string") {\r
902 el = doc.getElementById(el);\r
903 }\r
904 var isArray = Ext.isArray(el),\r
905 result = DQ.filter(isArray ? el : [el], ss);\r
906 return isArray ? (result.length == el.length) : (result.length > 0);\r
907 },\r
908\r
909 /**\r
910 * Filters an array of elements to only include matches of a simple selector\r
911 * @param {HTMLElement[]} el An array of elements to filter\r
912 * @param {String} selector The simple selector to test\r
913 * @param {Boolean} nonMatches If true, it returns the elements that DON'T match the selector instead of the\r
914 * ones that match\r
915 * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are no matches, and empty\r
916 * Array is returned.\r
917 */\r
918 filter: function(els, ss, nonMatches) {\r
919 ss = ss.replace(trimRe, "");\r
920 var query = simpleCache.get(ss),\r
921 result;\r
922\r
923 if (!query) {\r
924 query = DQ.compile(ss, "simple");\r
925 simpleCache.add(ss, query);\r
926 } else {\r
927 setupEscapes(ss);\r
928 }\r
929\r
930 result = query(els);\r
931 return nonMatches ? quickDiff(result, els) : result;\r
932 },\r
933\r
934 /**\r
935 * Collection of matching regular expressions and code snippets.\r
936 * Each capture group within `()` will be replace the `{}` in the select\r
937 * statement as specified by their index.\r
938 */\r
939 matchers: [{\r
940 re: /^\.([\w\-\\]+)/,\r
941 select: useClassList ? 'n = byClassName(n, "{1}");' : 'n = byClassName(n, " {1} ");'\r
942 }, {\r
943 re: /^\:([\w\-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,\r
944 select: 'n = byPseudo(n, "{1}", "{2}");'\r
945 }, {\r
946 re: /^(?:([\[\{])(?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,\r
947 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'\r
948 }, {\r
949 re: /^#([\w\-\\]+)/,\r
950 select: 'n = byId(n, "{1}");'\r
951 }, {\r
952 re: /^@([\w\-\.]+)/,\r
953 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'\r
954 }],\r
955\r
956 /**\r
957 * Collection of operator comparison functions.\r
958 * The default operators are `=`, `!=`, `^=`, `$=`, `*=`, `%=`, `|=` and `~=`.\r
959 *\r
960 * New operators can be added as long as the match the format *c*`=` where *c*\r
961 * is any character other than space, `>`, or `<`.\r
962 *\r
963 * Operator functions are passed the following parameters:\r
964 *\r
965 * * `propValue` : The property value to test.\r
966 * * `compareTo` : The value to compare to.\r
967 *\r
968 * @property {Object} operators\r
969 */\r
970\r
971 /**\r
972 * Object hash of "pseudo class" filter functions which are used when filtering selections.\r
973 * Each function is passed two parameters:\r
974 *\r
975 * - **c** : Array\r
976 * An Array of DOM elements to filter.\r
977 *\r
978 * - **v** : String\r
979 * The argument (if any) supplied in the selector.\r
980 *\r
981 * A filter function returns an Array of DOM elements which conform to the pseudo class.\r
982 * In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,\r
983 * developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.\r
984 *\r
985 * For example, to filter `a` elements to only return links to __external__ resources:\r
986 *\r
987 * Ext.DomQuery.pseudos.external = function(c, v) {\r
988 * var r = [], ri = -1;\r
989 * for(var i = 0, ci; ci = c[i]; i++) {\r
990 * // Include in result set only if it's a link to an external resource\r
991 * if (ci.hostname != location.hostname) {\r
992 * r[++ri] = ci;\r
993 * }\r
994 * }\r
995 * return r;\r
996 * };\r
997 *\r
998 * Then external links could be gathered with the following statement:\r
999 *\r
1000 * var externalLinks = Ext.select("a:external");\r
1001 */\r
1002 pseudos: {\r
1003 "first-child": function(c) {\r
1004 var r = [], ri = -1, n,\r
1005 i, ci;\r
1006 for (i = 0; (ci = n = c[i]); i++) {\r
1007 while ((n = n.previousSibling) && n.nodeType != 1);\r
1008 if (!n) {\r
1009 r[++ri] = ci;\r
1010 }\r
1011 }\r
1012 return r;\r
1013 },\r
1014\r
1015 "last-child": function(c) {\r
1016 var r = [], ri = -1, n,\r
1017 i, ci;\r
1018 for (i = 0; (ci = n = c[i]); i++) {\r
1019 while ((n = n.nextSibling) && n.nodeType != 1);\r
1020 if (!n) {\r
1021 r[++ri] = ci;\r
1022 }\r
1023 }\r
1024 return r;\r
1025 },\r
1026\r
1027 "nth-child": function(c, a) {\r
1028 var r = [], ri = -1,\r
1029 m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),\r
1030 f = (m[1] || 1) - 0, l = m[2] - 0,\r
1031 i, n, j, cn, pn;\r
1032 for (i = 0; n = c[i]; i++) {\r
1033 pn = n.parentNode;\r
1034 if (batch != pn._batch) {\r
1035 j = 0;\r
1036 for (cn = pn.firstChild; cn; cn = cn.nextSibling) {\r
1037 if (cn.nodeType == 1) {\r
1038 cn.nodeIndex = ++j;\r
1039 }\r
1040 }\r
1041 pn._batch = batch;\r
1042 }\r
1043 if (f == 1) {\r
1044 if (l === 0 || n.nodeIndex == l) {\r
1045 r[++ri] = n;\r
1046 }\r
1047 } else if ((n.nodeIndex + l) % f === 0) {\r
1048 r[++ri] = n;\r
1049 }\r
1050 }\r
1051\r
1052 return r;\r
1053 },\r
1054\r
1055 "only-child": function(c) {\r
1056 var r = [], ri = -1,\r
1057 i, ci;\r
1058 for (i = 0; ci = c[i]; i++) {\r
1059 if (!prev(ci) && !next(ci)) {\r
1060 r[++ri] = ci;\r
1061 }\r
1062 }\r
1063 return r;\r
1064 },\r
1065\r
1066 "empty": function(c) {\r
1067 var r = [], ri = -1,\r
1068 i, ci, cns, j, cn, empty;\r
1069 for (i = 0; ci = c[i]; i++) {\r
1070 cns = ci.childNodes;\r
1071 j = 0;\r
1072 empty = true;\r
1073 while (cn = cns[j]) {\r
1074 ++j;\r
1075 if (cn.nodeType == 1 || cn.nodeType == 3) {\r
1076 empty = false;\r
1077 break;\r
1078 }\r
1079 }\r
1080 if (empty) {\r
1081 r[++ri] = ci;\r
1082 }\r
1083 }\r
1084 return r;\r
1085 },\r
1086\r
1087 "contains": function(c, v) {\r
1088 var r = [], ri = -1,\r
1089 i, ci;\r
1090 for (i = 0; ci = c[i]; i++) {\r
1091 if ((ci.textContent || ci.innerText || ci.text || '').indexOf(v) != -1) {\r
1092 r[++ri] = ci;\r
1093 }\r
1094 }\r
1095 return r;\r
1096 },\r
1097\r
1098 "nodeValue": function(c, v) {\r
1099 var r = [], ri = -1,\r
1100 i, ci;\r
1101 for (i = 0; ci = c[i]; i++) {\r
1102 if (ci.firstChild && ci.firstChild.nodeValue == v) {\r
1103 r[++ri] = ci;\r
1104 }\r
1105 }\r
1106 return r;\r
1107 },\r
1108\r
1109 "checked": function(c) {\r
1110 var r = [], ri = -1,\r
1111 i, ci;\r
1112 for (i = 0; ci = c[i]; i++) {\r
1113 if (ci.checked === true) {\r
1114 r[++ri] = ci;\r
1115 }\r
1116 }\r
1117 return r;\r
1118 },\r
1119\r
1120 "not": function(c, ss) {\r
1121 return DQ.filter(c, ss, true);\r
1122 },\r
1123\r
1124 "any": function(c, selectors) {\r
1125 var ss = selectors.split('|'),\r
1126 r = [], ri = -1, s,\r
1127 i, ci, j;\r
1128 for (i = 0; ci = c[i]; i++) {\r
1129 for (j = 0; s = ss[j]; j++) {\r
1130 if (DQ.is(ci, s)) {\r
1131 r[++ri] = ci;\r
1132 break;\r
1133 }\r
1134 }\r
1135 }\r
1136 return r;\r
1137 },\r
1138\r
1139 "odd": function(c) {\r
1140 return this["nth-child"](c, "odd");\r
1141 },\r
1142\r
1143 "even": function(c) {\r
1144 return this["nth-child"](c, "even");\r
1145 },\r
1146\r
1147 "nth": function(c, a) {\r
1148 return c[a - 1] || [];\r
1149 },\r
1150\r
1151 "first": function(c) {\r
1152 return c[0] || [];\r
1153 },\r
1154\r
1155 "last": function(c) {\r
1156 return c[c.length - 1] || [];\r
1157 },\r
1158\r
1159 "has": function(c, ss) {\r
1160 var s = DQ.select,\r
1161 r = [], ri = -1,\r
1162 i, ci;\r
1163 for (i = 0; ci = c[i]; i++) {\r
1164 if (s(ss, ci).length > 0) {\r
1165 r[++ri] = ci;\r
1166 }\r
1167 }\r
1168 return r;\r
1169 },\r
1170\r
1171 "next": function(c, ss) {\r
1172 var is = DQ.is,\r
1173 r = [], ri = -1,\r
1174 i, ci, n;\r
1175 for (i = 0; ci = c[i]; i++) {\r
1176 n = next(ci);\r
1177 if (n && is(n, ss)) {\r
1178 r[++ri] = ci;\r
1179 }\r
1180 }\r
1181 return r;\r
1182 },\r
1183\r
1184 "prev": function(c, ss) {\r
1185 var is = DQ.is,\r
1186 r = [], ri = -1,\r
1187 i, ci, n;\r
1188 for (i = 0; ci = c[i]; i++) {\r
1189 n = prev(ci);\r
1190 if (n && is(n, ss)) {\r
1191 r[++ri] = ci;\r
1192 }\r
1193 }\r
1194 return r;\r
1195 },\r
1196\r
1197 focusable: function(candidates) {\r
1198 var len = candidates.length,\r
1199 results = [],\r
1200 i = 0,\r
1201 c;\r
1202\r
1203 for (; i < len; i++) {\r
1204 c = candidates[i];\r
1205 if (Ext.fly(c, '_DomQuery').isFocusable()) {\r
1206 results.push(c);\r
1207 }\r
1208 }\r
1209\r
1210 return results;\r
1211 },\r
1212 \r
1213 visible: function(candidates, deep) {\r
1214 var len = candidates.length,\r
1215 results = [],\r
1216 i = 0,\r
1217 c;\r
1218\r
1219 for (; i < len; i++) {\r
1220 c = candidates[i];\r
1221 if (Ext.fly(c, '_DomQuery').isVisible(deep)) {\r
1222 results.push(c);\r
1223 }\r
1224 }\r
1225\r
1226 return results;\r
1227 },\r
1228\r
1229 isScrolled: function(c) {\r
1230 var r = [], ri = -1,\r
1231 i, ci, s;\r
1232 for (i = 0; ci = c[i]; i++) {\r
1233 s = Ext.fly(ci, '_DomQuery').getScroll();\r
1234 if (s.top > 0 || s.left > 0) {\r
1235 r[++ri] = ci;\r
1236 }\r
1237 }\r
1238 return r;\r
1239 }\r
1240 }\r
1241 };\r
1242}, function() {\r
1243 this._init();\r
1244});\r