4 * Copyright(c) 2006-2007, Ext JS, LLC.
6 * Originally Released Under LGPL - original licence link has changed is not relivant.
9 * <script type="text/javascript">
14 * This is code is also distributed under MIT license for use
15 * with jQuery and prototype JavaScript libraries.
19 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).
21 DomQuery supports most of the <a href="http://www.w3.org/TR/2005/WD-css3-selectors-20051215/">CSS3 selectors spec</a>, along with some custom selectors and basic XPath.</p>
24 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.
26 <h4>Element Selectors:</h4>
28 <li> <b>*</b> any element</li>
29 <li> <b>E</b> an element with the tag E</li>
30 <li> <b>E F</b> All descendent elements of E that have the tag F</li>
31 <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
32 <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
33 <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
35 <h4>Attribute Selectors:</h4>
36 <p>The use of @ and quotes are optional. For example, div[@foo='bar'] is also a valid attribute selector.</p>
38 <li> <b>E[foo]</b> has an attribute "foo"</li>
39 <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
40 <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
41 <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
42 <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
43 <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
44 <li> <b>E[foo!=bar]</b> has an attribute "foo" that does not equal "bar"</li>
46 <h4>Pseudo Classes:</h4>
48 <li> <b>E:first-child</b> E is the first child of its parent</li>
49 <li> <b>E:last-child</b> E is the last child of its parent</li>
50 <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>
51 <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
52 <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
53 <li> <b>E:only-child</b> E is the only child of its parent</li>
54 <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>
55 <li> <b>E:first</b> the first E in the resultset</li>
56 <li> <b>E:last</b> the last E in the resultset</li>
57 <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
58 <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
59 <li> <b>E:even</b> shortcut for :nth-child(even)</li>
60 <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
61 <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
62 <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
63 <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
64 <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
65 <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
67 <h4>CSS Value Selectors:</h4>
69 <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
70 <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
71 <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
72 <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
73 <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
74 <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
78 Roo.DomQuery = function(){
79 var cache = {}, simpleCache = {}, valueCache = {};
81 var trimRe = /^\s+|\s+$/g;
82 var tplRe = /\{(\d+)\}/g;
83 var modeRe = /^(\s?[\/>+~]\s?|\s|$)/;
84 var tagTokenRe = /^(#)?([\w-\*]+)/;
85 var nthRe = /(\d*)n\+?(\d*)/, nthRe2 = /\D/;
87 function child(p, index){
102 while((n = n.nextSibling) && n.nodeType != 1);
107 while((n = n.previousSibling) && n.nodeType != 1);
111 function children(d){
112 var n = d.firstChild, ni = -1;
114 var nx = n.nextSibling;
115 if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
125 function byClassName(c, a, v){
129 var r = [], ri = -1, cn;
130 for(var i = 0, ci; ci = c[i]; i++){
134 ( (ci instanceof SVGElement) ? ci.className.baseVal : ci.className)
135 +' ').indexOf(v) != -1){
142 function attrValue(n, attr){
143 if(!n.tagName && typeof n.length != "undefined"){
152 if(attr == "class" || attr == "className"){
153 return (n instanceof SVGElement) ? n.className.baseVal : n.className;
155 return n.getAttribute(attr) || n[attr];
159 function getNodes(ns, mode, tagName){
160 var result = [], ri = -1, cs;
164 tagName = tagName || "*";
165 if(typeof ns.getElementsByTagName != "undefined"){
169 for(var i = 0, ni; ni = ns[i]; i++){
170 cs = ni.getElementsByTagName(tagName);
171 for(var j = 0, ci; ci = cs[j]; j++){
175 }else if(mode == "/" || mode == ">"){
176 var utag = tagName.toUpperCase();
177 for(var i = 0, ni, cn; ni = ns[i]; i++){
178 cn = ni.children || ni.childNodes;
179 for(var j = 0, cj; cj = cn[j]; j++){
180 if(cj.nodeName == utag || cj.nodeName == tagName || tagName == '*'){
185 }else if(mode == "+"){
186 var utag = tagName.toUpperCase();
187 for(var i = 0, n; n = ns[i]; i++){
188 while((n = n.nextSibling) && n.nodeType != 1);
189 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
193 }else if(mode == "~"){
194 for(var i = 0, n; n = ns[i]; i++){
195 while((n = n.nextSibling) && (n.nodeType != 1 || (tagName == '*' || n.tagName.toLowerCase()!=tagName)));
204 function concat(a, b){
208 for(var i = 0, l = b.length; i < l; i++){
214 function byTag(cs, tagName){
215 if(cs.tagName || cs == document){
222 tagName = tagName.toLowerCase();
223 for(var i = 0, ci; ci = cs[i]; i++){
224 if(ci.nodeType == 1 && ci.tagName.toLowerCase()==tagName){
231 function byId(cs, attr, id){
232 if(cs.tagName || cs == document){
239 for(var i = 0,ci; ci = cs[i]; i++){
240 if(ci && ci.id == id){
248 function byAttribute(cs, attr, value, op, custom){
249 var r = [], ri = -1, st = custom=="{";
250 var f = Roo.DomQuery.operators[op];
251 for(var i = 0, ci; ci = cs[i]; i++){
254 a = Roo.DomQuery.getStyle(ci, attr);
256 else if(attr == "class" || attr == "className"){
257 a = (ci instanceof SVGElement) ? ci.className.baseVal : ci.className;
258 }else if(attr == "for"){
260 }else if(attr == "href"){
261 a = ci.getAttribute("href", 2);
263 a = ci.getAttribute(attr);
265 if((f && f(a, value)) || (!f && a)){
272 function byPseudo(cs, name, value){
273 return Roo.DomQuery.pseudos[name](cs, value);
276 // This is for IE MSXML which does not support expandos.
277 // IE runs the same speed using setAttribute, however FF slows way down
278 // and Safari completely fails so they need to continue to use expandos.
279 var isIE = window.ActiveXObject ? true : false;
281 // this eval is stop the compressor from
282 // renaming the variable to something shorter
284 /** eval:var:batch */
289 function nodupIEXml(cs){
291 cs[0].setAttribute("_nodup", d);
293 for(var i = 1, len = cs.length; i < len; i++){
295 if(!c.getAttribute("_nodup") != d){
296 c.setAttribute("_nodup", d);
300 for(var i = 0, len = cs.length; i < len; i++){
301 cs[i].removeAttribute("_nodup");
310 var len = cs.length, c, i, r = cs, cj, ri = -1;
311 if(!len || typeof cs.nodeType != "undefined" || len == 1){
314 if(isIE && typeof cs[0].selectSingleNode != "undefined"){
315 return nodupIEXml(cs);
319 for(i = 1; c = cs[i]; i++){
324 for(var j = 0; j < i; j++){
327 for(j = i+1; cj = cs[j]; j++){
339 function quickDiffIEXml(c1, c2){
341 for(var i = 0, len = c1.length; i < len; i++){
342 c1[i].setAttribute("_qdiff", d);
345 for(var i = 0, len = c2.length; i < len; i++){
346 if(c2[i].getAttribute("_qdiff") != d){
350 for(var i = 0, len = c1.length; i < len; i++){
351 c1[i].removeAttribute("_qdiff");
356 function quickDiff(c1, c2){
357 var len1 = c1.length;
361 if(isIE && c1[0].selectSingleNode){
362 return quickDiffIEXml(c1, c2);
365 for(var i = 0; i < len1; i++){
369 for(var i = 0, len = c2.length; i < len; i++){
370 if(c2[i]._qdiff != d){
377 function quickId(ns, mode, root, id){
379 var d = root.ownerDocument || root;
380 return d.getElementById(id);
382 ns = getNodes(ns, mode, "*");
383 return byId(ns, null, id);
387 getStyle : function(el, name){
388 return Roo.fly(el).getStyle(name);
391 * Compiles a selector/xpath query into a reusable function. The returned function
392 * takes one parameter "root" (optional), which is the context node from where the query should start.
393 * @param {String} selector The selector/xpath query
394 * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
397 compile : function(path, type){
398 type = type || "select";
400 var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"];
401 var q = path, mode, lq;
402 var tk = Roo.DomQuery.matchers;
403 var tklen = tk.length;
406 // accept leading mode switch
407 var lmode = q.match(modeRe);
408 if(lmode && lmode[1]){
409 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
410 q = q.replace(lmode[1], "");
412 // strip leading slashes
413 while(path.substr(0, 1)=="/"){
414 path = path.substr(1);
419 var tm = q.match(tagTokenRe);
420 if(type == "select"){
423 fn[fn.length] = 'n = quickId(n, mode, root, "'+tm[2]+'");';
425 fn[fn.length] = 'n = getNodes(n, mode, "'+tm[2]+'");';
427 q = q.replace(tm[0], "");
428 }else if(q.substr(0, 1) != '@'){
429 fn[fn.length] = 'n = getNodes(n, mode, "*");';
434 fn[fn.length] = 'n = byId(n, null, "'+tm[2]+'");';
436 fn[fn.length] = 'n = byTag(n, "'+tm[2]+'");';
438 q = q.replace(tm[0], "");
441 while(!(mm = q.match(modeRe))){
443 for(var j = 0; j < tklen; j++){
445 var m = q.match(t.re);
447 fn[fn.length] = t.select.replace(tplRe, function(x, i){
450 q = q.replace(m[0], "");
455 // prevent infinite loop on bad selector
457 throw 'Error parsing selector, parsing failed at "' + q + '"';
461 fn[fn.length] = 'mode="'+mm[1].replace(trimRe, "")+'";';
462 q = q.replace(mm[1], "");
465 fn[fn.length] = "return nodup(n);\n}";
468 * list of variables that need from compression as they are used by eval.
478 * eval:var:byClassName
480 * eval:var:byAttribute
489 * Selects a group of elements.
490 * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
491 * @param {Node} root (optional) The start of the query (defaults to document).
494 select : function(path, root, type){
495 if(!root || root == document){
498 if(typeof root == "string"){
499 root = document.getElementById(root);
501 var paths = path.split(",");
503 for(var i = 0, len = paths.length; i < len; i++){
504 var p = paths[i].replace(trimRe, "");
506 cache[p] = Roo.DomQuery.compile(p);
508 throw p + " is not a valid selector";
511 var result = cache[p](root);
512 if(result && result != document){
513 results = results.concat(result);
516 if(paths.length > 1){
517 return nodup(results);
523 * Selects a single element.
524 * @param {String} selector The selector/xpath query
525 * @param {Node} root (optional) The start of the query (defaults to document).
528 selectNode : function(path, root){
529 return Roo.DomQuery.select(path, root)[0];
533 * Selects the value of a node, optionally replacing null with the defaultValue.
534 * @param {String} selector The selector/xpath query
535 * @param {Node} root (optional) The start of the query (defaults to document).
536 * @param {String} defaultValue
538 selectValue : function(path, root, defaultValue){
539 path = path.replace(trimRe, "");
540 if(!valueCache[path]){
541 valueCache[path] = Roo.DomQuery.compile(path, "select");
543 var n = valueCache[path](root);
545 var v = (n && n.firstChild ? n.firstChild.nodeValue : null);
546 return ((v === null||v === undefined||v==='') ? defaultValue : v);
550 * Selects the value of a node, parsing integers and floats.
551 * @param {String} selector The selector/xpath query
552 * @param {Node} root (optional) The start of the query (defaults to document).
553 * @param {Number} defaultValue
556 selectNumber : function(path, root, defaultValue){
557 var v = Roo.DomQuery.selectValue(path, root, defaultValue || 0);
558 return parseFloat(v);
562 * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
563 * @param {String/HTMLElement/Array} el An element id, element or array of elements
564 * @param {String} selector The simple selector to test
567 is : function(el, ss){
568 if(typeof el == "string"){
569 el = document.getElementById(el);
571 var isArray = (el instanceof Array);
572 var result = Roo.DomQuery.filter(isArray ? el : [el], ss);
573 return isArray ? (result.length == el.length) : (result.length > 0);
577 * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
578 * @param {Array} el An array of elements to filter
579 * @param {String} selector The simple selector to test
580 * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
581 * the selector instead of the ones that match
584 filter : function(els, ss, nonMatches){
585 ss = ss.replace(trimRe, "");
586 if(!simpleCache[ss]){
587 simpleCache[ss] = Roo.DomQuery.compile(ss, "simple");
589 var result = simpleCache[ss](els);
590 return nonMatches ? quickDiff(result, els) : result;
594 * Collection of matching regular expressions and code snippets.
598 select: 'n = byClassName(n, null, " {1} ");'
600 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
601 select: 'n = byPseudo(n, "{1}", "{2}");'
603 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
604 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
607 select: 'n = byId(n, null, "{1}");'
610 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
615 * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
616 * 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, > <.
619 "=" : function(a, v){
622 "!=" : function(a, v){
625 "^=" : function(a, v){
626 return a && a.substr(0, v.length) == v;
628 "$=" : function(a, v){
629 return a && a.substr(a.length-v.length) == v;
631 "*=" : function(a, v){
632 return a && a.indexOf(v) !== -1;
634 "%=" : function(a, v){
637 "|=" : function(a, v){
638 return a && (a == v || a.substr(0, v.length+1) == v+'-');
640 "~=" : function(a, v){
641 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
646 * Collection of "pseudo class" processors. Each processor is passed the current nodeset (array)
647 * and the argument (if any) supplied in the selector.
650 "first-child" : function(c){
651 var r = [], ri = -1, n;
652 for(var i = 0, ci; ci = n = c[i]; i++){
653 while((n = n.previousSibling) && n.nodeType != 1);
661 "last-child" : function(c){
662 var r = [], ri = -1, n;
663 for(var i = 0, ci; ci = n = c[i]; i++){
664 while((n = n.nextSibling) && n.nodeType != 1);
672 "nth-child" : function(c, a) {
674 var m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a);
675 var f = (m[1] || 1) - 0, l = m[2] - 0;
676 for(var i = 0, n; n = c[i]; i++){
677 var pn = n.parentNode;
678 if (batch != pn._batch) {
680 for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
681 if(cn.nodeType == 1){
688 if (l == 0 || n.nodeIndex == l){
691 } else if ((n.nodeIndex + l) % f == 0){
699 "only-child" : function(c){
700 var r = [], ri = -1;;
701 for(var i = 0, ci; ci = c[i]; i++){
702 if(!prev(ci) && !next(ci)){
709 "empty" : function(c){
711 for(var i = 0, ci; ci = c[i]; i++){
712 var cns = ci.childNodes, j = 0, cn, empty = true;
715 if(cn.nodeType == 1 || cn.nodeType == 3){
727 "contains" : function(c, v){
729 for(var i = 0, ci; ci = c[i]; i++){
730 if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
737 "nodeValue" : function(c, v){
739 for(var i = 0, ci; ci = c[i]; i++){
740 if(ci.firstChild && ci.firstChild.nodeValue == v){
747 "checked" : function(c){
749 for(var i = 0, ci; ci = c[i]; i++){
750 if(ci.checked == true){
757 "not" : function(c, ss){
758 return Roo.DomQuery.filter(c, ss, true);
762 return this["nth-child"](c, "odd");
765 "even" : function(c){
766 return this["nth-child"](c, "even");
769 "nth" : function(c, a){
773 "first" : function(c){
777 "last" : function(c){
778 return c[c.length-1] || [];
781 "has" : function(c, ss){
782 var s = Roo.DomQuery.select;
784 for(var i = 0, ci; ci = c[i]; i++){
785 if(s(ss, ci).length > 0){
792 "next" : function(c, ss){
793 var is = Roo.DomQuery.is;
795 for(var i = 0, ci; ci = c[i]; i++){
804 "prev" : function(c, ss){
805 var is = Roo.DomQuery.is;
807 for(var i = 0, ci; ci = c[i]; i++){
820 * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Roo.DomQuery#select}
821 * @param {String} path The selector/xpath query
822 * @param {Node} root (optional) The start of the query (defaults to document).
827 Roo.query = Roo.DomQuery.select;