]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/_base/query.js
build custom layer of Dojo to speed up loading of tt-rss (refs #293)
[tt-rss.git] / lib / dojo / _base / query.js
CommitLineData
2f01fe57
AD
1/*
2 Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
5*/
6
7
a089699c
AD
8if(!dojo._hasResource["dojo._base.query"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dojo._base.query"] = true;
10if(typeof dojo != "undefined"){
11 dojo.provide("dojo._base.query");
12 dojo.require("dojo._base.NodeList");
13 dojo.require("dojo._base.lang");
14
2f01fe57 15}
a089699c
AD
16
17/*
18 dojo.query() architectural overview:
19
20 dojo.query is a relatively full-featured CSS3 query library. It is
21 designed to take any valid CSS3 selector and return the nodes matching
22 the selector. To do this quickly, it processes queries in several
23 steps, applying caching where profitable.
24
25 The steps (roughly in reverse order of the way they appear in the code):
26 1.) check to see if we already have a "query dispatcher"
27 - if so, use that with the given parameterization. Skip to step 4.
28 2.) attempt to determine which branch to dispatch the query to:
29 - JS (optimized DOM iteration)
30 - native (FF3.1+, Safari 3.1+, IE 8+)
31 3.) tokenize and convert to executable "query dispatcher"
32 - this is where the lion's share of the complexity in the
33 system lies. In the DOM version, the query dispatcher is
34 assembled as a chain of "yes/no" test functions pertaining to
35 a section of a simple query statement (".blah:nth-child(odd)"
36 but not "div div", which is 2 simple statements). Individual
37 statement dispatchers are cached (to prevent re-definition)
38 as are entire dispatch chains (to make re-execution of the
39 same query fast)
40 4.) the resulting query dispatcher is called in the passed scope
41 (by default the top-level document)
42 - for DOM queries, this results in a recursive, top-down
43 evaluation of nodes based on each simple query section
44 - for native implementations, this may mean working around spec
45 bugs. So be it.
46 5.) matched nodes are pruned to ensure they are unique (if necessary)
47*/
48
49;(function(d){
50 // define everything in a closure for compressability reasons. "d" is an
51 // alias to "dojo" (or the toolkit alias object, e.g., "acme").
52
53 ////////////////////////////////////////////////////////////////////////
54 // Toolkit aliases
55 ////////////////////////////////////////////////////////////////////////
56
57 // if you are extracing dojo.query for use in your own system, you will
58 // need to provide these methods and properties. No other porting should be
59 // necessary, save for configuring the system to use a class other than
60 // dojo.NodeList as the return instance instantiator
61 var trim = d.trim;
62 var each = d.forEach;
63 // d.isIE; // float
64 // d.isSafari; // float
65 // d.isOpera; // float
66 // d.isWebKit; // float
67 // d.doc ; // document element
68 var qlc = d._NodeListCtor = d.NodeList;
69
70 var getDoc = function(){ return d.doc; };
71 // NOTE(alex): the spec is idiotic. CSS queries should ALWAYS be case-sensitive, but nooooooo
72 var cssCaseBug = ((d.isWebKit||d.isMozilla) && ((getDoc().compatMode) == "BackCompat"));
73
74 ////////////////////////////////////////////////////////////////////////
75 // Global utilities
76 ////////////////////////////////////////////////////////////////////////
77
78
79 // on browsers that support the "children" collection we can avoid a lot of
80 // iteration on chaff (non-element) nodes.
81 // why.
82 var childNodesName = !!getDoc().firstChild["children"] ? "children" : "childNodes";
83
84 var specials = ">~+";
85
86 // global thunk to determine whether we should treat the current query as
87 // case sensitive or not. This switch is flipped by the query evaluator
88 // based on the document passed as the context to search.
89 var caseSensitive = false;
90
91 // how high?
92 var yesman = function(){ return true; };
93
94 ////////////////////////////////////////////////////////////////////////
95 // Tokenizer
96 ////////////////////////////////////////////////////////////////////////
97
98 var getQueryParts = function(query){
99 // summary:
100 // state machine for query tokenization
101 // description:
102 // instead of using a brittle and slow regex-based CSS parser,
103 // dojo.query implements an AST-style query representation. This
104 // representation is only generated once per query. For example,
105 // the same query run multiple times or under different root nodes
106 // does not re-parse the selector expression but instead uses the
107 // cached data structure. The state machine implemented here
108 // terminates on the last " " (space) charachter and returns an
109 // ordered array of query component structures (or "parts"). Each
110 // part represents an operator or a simple CSS filtering
111 // expression. The structure for parts is documented in the code
112 // below.
113
114
115 // NOTE:
116 // this code is designed to run fast and compress well. Sacrifices
117 // to readibility and maintainability have been made. Your best
118 // bet when hacking the tokenizer is to put The Donnas on *really*
119 // loud (may we recommend their "Spend The Night" release?) and
120 // just assume you're gonna make mistakes. Keep the unit tests
121 // open and run them frequently. Knowing is half the battle ;-)
122 if(specials.indexOf(query.slice(-1)) >= 0){
123 // if we end with a ">", "+", or "~", that means we're implicitly
124 // searching all children, so make it explicit
125 query += " * "
126 }else{
127 // if you have not provided a terminator, one will be provided for
128 // you...
129 query += " ";
130 }
131
132 var ts = function(/*Integer*/ s, /*Integer*/ e){
133 // trim and slice.
134
135 // take an index to start a string slice from and an end position
136 // and return a trimmed copy of that sub-string
137 return trim(query.slice(s, e));
138 }
139
140 // the overall data graph of the full query, as represented by queryPart objects
141 var queryParts = [];
142
143
144 // state keeping vars
145 var inBrackets = -1, inParens = -1, inMatchFor = -1,
146 inPseudo = -1, inClass = -1, inId = -1, inTag = -1,
147 lc = "", cc = "", pStart;
148
149 // iteration vars
150 var x = 0, // index in the query
151 ql = query.length,
152 currentPart = null, // data structure representing the entire clause
153 _cp = null; // the current pseudo or attr matcher
154
155 // several temporary variables are assigned to this structure durring a
156 // potential sub-expression match:
157 // attr:
158 // a string representing the current full attribute match in a
159 // bracket expression
160 // type:
161 // if there's an operator in a bracket expression, this is
162 // used to keep track of it
163 // value:
164 // the internals of parenthetical expression for a pseudo. for
165 // :nth-child(2n+1), value might be "2n+1"
166
167 var endTag = function(){
168 // called when the tokenizer hits the end of a particular tag name.
169 // Re-sets state variables for tag matching and sets up the matcher
170 // to handle the next type of token (tag or operator).
171 if(inTag >= 0){
172 var tv = (inTag == x) ? null : ts(inTag, x); // .toLowerCase();
173 currentPart[ (specials.indexOf(tv) < 0) ? "tag" : "oper" ] = tv;
174 inTag = -1;
175 }
176 }
177
178 var endId = function(){
179 // called when the tokenizer might be at the end of an ID portion of a match
180 if(inId >= 0){
181 currentPart.id = ts(inId, x).replace(/\\/g, "");
182 inId = -1;
183 }
184 }
185
186 var endClass = function(){
187 // called when the tokenizer might be at the end of a class name
188 // match. CSS allows for multiple classes, so we augment the
189 // current item with another class in its list
190 if(inClass >= 0){
191 currentPart.classes.push(ts(inClass+1, x).replace(/\\/g, ""));
192 inClass = -1;
193 }
194 }
195
196 var endAll = function(){
197 // at the end of a simple fragment, so wall off the matches
198 endId(); endTag(); endClass();
199 }
200
201 var endPart = function(){
202 endAll();
203 if(inPseudo >= 0){
204 currentPart.pseudos.push({ name: ts(inPseudo+1, x) });
205 }
206 // hint to the selector engine to tell it whether or not it
207 // needs to do any iteration. Many simple selectors don't, and
208 // we can avoid significant construction-time work by advising
209 // the system to skip them
210 currentPart.loops = (
211 currentPart.pseudos.length ||
212 currentPart.attrs.length ||
213 currentPart.classes.length );
214
215 currentPart.oquery = currentPart.query = ts(pStart, x); // save the full expression as a string
216
217
218 // otag/tag are hints to suggest to the system whether or not
219 // it's an operator or a tag. We save a copy of otag since the
220 // tag name is cast to upper-case in regular HTML matches. The
221 // system has a global switch to figure out if the current
222 // expression needs to be case sensitive or not and it will use
223 // otag or tag accordingly
224 currentPart.otag = currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*");
225
226 if(currentPart.tag){
227 // if we're in a case-insensitive HTML doc, we likely want
228 // the toUpperCase when matching on element.tagName. If we
229 // do it here, we can skip the string op per node
230 // comparison
231 currentPart.tag = currentPart.tag.toUpperCase();
232 }
233
234 // add the part to the list
235 if(queryParts.length && (queryParts[queryParts.length-1].oper)){
236 // operators are always infix, so we remove them from the
237 // list and attach them to the next match. The evaluator is
238 // responsible for sorting out how to handle them.
239 currentPart.infixOper = queryParts.pop();
240 currentPart.query = currentPart.infixOper.query + " " + currentPart.query;
241 /*
242 console.debug( "swapping out the infix",
243 currentPart.infixOper,
244 "and attaching it to",
245 currentPart);
246 */
247 }
248 queryParts.push(currentPart);
249
250 currentPart = null;
251 }
252
253 // iterate over the query, charachter by charachter, building up a
254 // list of query part objects
255 for(; lc=cc, cc=query.charAt(x), x < ql; x++){
256 // cc: the current character in the match
257 // lc: the last charachter (if any)
258
259 // someone is trying to escape something, so don't try to match any
260 // fragments. We assume we're inside a literal.
261 if(lc == "\\"){ continue; }
262 if(!currentPart){ // a part was just ended or none has yet been created
263 // NOTE: I hate all this alloc, but it's shorter than writing tons of if's
264 pStart = x;
265 // rules describe full CSS sub-expressions, like:
266 // #someId
267 // .className:first-child
268 // but not:
269 // thinger > div.howdy[type=thinger]
270 // the indidual components of the previous query would be
271 // split into 3 parts that would be represented a structure
272 // like:
273 // [
274 // {
275 // query: "thinger",
276 // tag: "thinger",
277 // },
278 // {
279 // query: "div.howdy[type=thinger]",
280 // classes: ["howdy"],
281 // infixOper: {
282 // query: ">",
283 // oper: ">",
284 // }
285 // },
286 // ]
287 currentPart = {
288 query: null, // the full text of the part's rule
289 pseudos: [], // CSS supports multiple pseud-class matches in a single rule
290 attrs: [], // CSS supports multi-attribute match, so we need an array
291 classes: [], // class matches may be additive, e.g.: .thinger.blah.howdy
292 tag: null, // only one tag...
293 oper: null, // ...or operator per component. Note that these wind up being exclusive.
294 id: null, // the id component of a rule
295 getTag: function(){
296 return (caseSensitive) ? this.otag : this.tag;
297 }
298 };
299
300 // if we don't have a part, we assume we're going to start at
301 // the beginning of a match, which should be a tag name. This
302 // might fault a little later on, but we detect that and this
303 // iteration will still be fine.
304 inTag = x;
305 }
306
307 if(inBrackets >= 0){
308 // look for a the close first
309 if(cc == "]"){ // if we're in a [...] clause and we end, do assignment
310 if(!_cp.attr){
311 // no attribute match was previously begun, so we
312 // assume this is an attribute existance match in the
313 // form of [someAttributeName]
314 _cp.attr = ts(inBrackets+1, x);
315 }else{
316 // we had an attribute already, so we know that we're
317 // matching some sort of value, as in [attrName=howdy]
318 _cp.matchFor = ts((inMatchFor||inBrackets+1), x);
319 }
320 var cmf = _cp.matchFor;
321 if(cmf){
322 // try to strip quotes from the matchFor value. We want
323 // [attrName=howdy] to match the same
324 // as [attrName = 'howdy' ]
325 if( (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){
326 _cp.matchFor = cmf.slice(1, -1);
327 }
328 }
329 // end the attribute by adding it to the list of attributes.
330 currentPart.attrs.push(_cp);
331 _cp = null; // necessary?
332 inBrackets = inMatchFor = -1;
333 }else if(cc == "="){
334 // if the last char was an operator prefix, make sure we
335 // record it along with the "=" operator.
336 var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : "";
337 _cp.type = addToCc+cc;
338 _cp.attr = ts(inBrackets+1, x-addToCc.length);
339 inMatchFor = x+1;
340 }
341 // now look for other clause parts
342 }else if(inParens >= 0){
343 // if we're in a parenthetical expression, we need to figure
344 // out if it's attached to a pseduo-selector rule like
345 // :nth-child(1)
346 if(cc == ")"){
347 if(inPseudo >= 0){
348 _cp.value = ts(inParens+1, x);
349 }
350 inPseudo = inParens = -1;
351 }
352 }else if(cc == "#"){
353 // start of an ID match
354 endAll();
355 inId = x+1;
356 }else if(cc == "."){
357 // start of a class match
358 endAll();
359 inClass = x;
360 }else if(cc == ":"){
361 // start of a pseudo-selector match
362 endAll();
363 inPseudo = x;
364 }else if(cc == "["){
365 // start of an attribute match.
366 endAll();
367 inBrackets = x;
368 // provide a new structure for the attribute match to fill-in
369 _cp = {
370 /*=====
371 attr: null, type: null, matchFor: null
372 =====*/
373 };
374 }else if(cc == "("){
375 // we really only care if we've entered a parenthetical
376 // expression if we're already inside a pseudo-selector match
377 if(inPseudo >= 0){
378 // provide a new structure for the pseudo match to fill-in
379 _cp = {
380 name: ts(inPseudo+1, x),
381 value: null
382 }
383 currentPart.pseudos.push(_cp);
384 }
385 inParens = x;
386 }else if(
387 (cc == " ") &&
388 // if it's a space char and the last char is too, consume the
389 // current one without doing more work
390 (lc != cc)
391 ){
392 endPart();
393 }
394 }
395 return queryParts;
396 };
397
398
399 ////////////////////////////////////////////////////////////////////////
400 // DOM query infrastructure
401 ////////////////////////////////////////////////////////////////////////
402
403 var agree = function(first, second){
404 // the basic building block of the yes/no chaining system. agree(f1,
405 // f2) generates a new function which returns the boolean results of
406 // both of the passed functions to a single logical-anded result. If
407 // either are not possed, the other is used exclusively.
408 if(!first){ return second; }
409 if(!second){ return first; }
410
411 return function(){
412 return first.apply(window, arguments) && second.apply(window, arguments);
413 }
414 };
415
416 var getArr = function(i, arr){
417 // helps us avoid array alloc when we don't need it
418 var r = arr||[]; // FIXME: should this be 'new d._NodeListCtor()' ?
419 if(i){ r.push(i); }
420 return r;
421 };
422
423 var _isElement = function(n){ return (1 == n.nodeType); };
424
425 // FIXME: need to coalesce _getAttr with defaultGetter
426 var blank = "";
427 var _getAttr = function(elem, attr){
428 if(!elem){ return blank; }
429 if(attr == "class"){
430 return elem.className || blank;
431 }
432 if(attr == "for"){
433 return elem.htmlFor || blank;
434 }
435 if(attr == "style"){
436 return elem.style.cssText || blank;
437 }
438 return (caseSensitive ? elem.getAttribute(attr) : elem.getAttribute(attr, 2)) || blank;
439 };
440
441 var attrs = {
442 "*=": function(attr, value){
443 return function(elem){
444 // E[foo*="bar"]
445 // an E element whose "foo" attribute value contains
446 // the substring "bar"
447 return (_getAttr(elem, attr).indexOf(value)>=0);
448 }
449 },
450 "^=": function(attr, value){
451 // E[foo^="bar"]
452 // an E element whose "foo" attribute value begins exactly
453 // with the string "bar"
454 return function(elem){
455 return (_getAttr(elem, attr).indexOf(value)==0);
456 }
457 },
458 "$=": function(attr, value){
459 // E[foo$="bar"]
460 // an E element whose "foo" attribute value ends exactly
461 // with the string "bar"
462 var tval = " "+value;
463 return function(elem){
464 var ea = " "+_getAttr(elem, attr);
465 return (ea.lastIndexOf(value)==(ea.length-value.length));
466 }
467 },
468 "~=": function(attr, value){
469 // E[foo~="bar"]
470 // an E element whose "foo" attribute value is a list of
471 // space-separated values, one of which is exactly equal
472 // to "bar"
473
474 // return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]";
475 var tval = " "+value+" ";
476 return function(elem){
477 var ea = " "+_getAttr(elem, attr)+" ";
478 return (ea.indexOf(tval)>=0);
479 }
480 },
481 "|=": function(attr, value){
482 // E[hreflang|="en"]
483 // an E element whose "hreflang" attribute has a
484 // hyphen-separated list of values beginning (from the
485 // left) with "en"
486 var valueDash = " "+value+"-";
487 return function(elem){
488 var ea = " "+_getAttr(elem, attr);
489 return (
490 (ea == value) ||
491 (ea.indexOf(valueDash)==0)
492 );
493 }
494 },
495 "=": function(attr, value){
496 return function(elem){
497 return (_getAttr(elem, attr) == value);
498 }
499 }
500 };
501
502 // avoid testing for node type if we can. Defining this in the negative
503 // here to avoid negation in the fast path.
504 var _noNES = (typeof getDoc().firstChild.nextElementSibling == "undefined");
505 var _ns = !_noNES ? "nextElementSibling" : "nextSibling";
506 var _ps = !_noNES ? "previousElementSibling" : "previousSibling";
507 var _simpleNodeTest = (_noNES ? _isElement : yesman);
508
509 var _lookLeft = function(node){
510 // look left
511 while(node = node[_ps]){
512 if(_simpleNodeTest(node)){ return false; }
513 }
514 return true;
515 };
516
517 var _lookRight = function(node){
518 // look right
519 while(node = node[_ns]){
520 if(_simpleNodeTest(node)){ return false; }
521 }
522 return true;
523 };
524
525 var getNodeIndex = function(node){
526 var root = node.parentNode;
527 var i = 0,
528 tret = root[childNodesName],
529 ci = (node["_i"]||-1),
530 cl = (root["_l"]||-1);
531
532 if(!tret){ return -1; }
533 var l = tret.length;
534
535 // we calcuate the parent length as a cheap way to invalidate the
536 // cache. It's not 100% accurate, but it's much more honest than what
537 // other libraries do
538 if( cl == l && ci >= 0 && cl >= 0 ){
539 // if it's legit, tag and release
540 return ci;
541 }
542
543 // else re-key things
544 root["_l"] = l;
545 ci = -1;
546 for(var te = root["firstElementChild"]||root["firstChild"]; te; te = te[_ns]){
547 if(_simpleNodeTest(te)){
548 te["_i"] = ++i;
549 if(node === te){
550 // NOTE:
551 // shortcuting the return at this step in indexing works
552 // very well for benchmarking but we avoid it here since
553 // it leads to potential O(n^2) behavior in sequential
554 // getNodexIndex operations on a previously un-indexed
555 // parent. We may revisit this at a later time, but for
556 // now we just want to get the right answer more often
557 // than not.
558 ci = i;
559 }
560 }
561 }
562 return ci;
563 };
564
565 var isEven = function(elem){
566 return !((getNodeIndex(elem)) % 2);
567 };
568
569 var isOdd = function(elem){
570 return ((getNodeIndex(elem)) % 2);
571 };
572
573 var pseudos = {
574 "checked": function(name, condition){
575 return function(elem){
576 return !!("checked" in elem ? elem.checked : elem.selected);
577 }
578 },
579 "first-child": function(){ return _lookLeft; },
580 "last-child": function(){ return _lookRight; },
581 "only-child": function(name, condition){
582 return function(node){
583 if(!_lookLeft(node)){ return false; }
584 if(!_lookRight(node)){ return false; }
585 return true;
586 };
587 },
588 "empty": function(name, condition){
589 return function(elem){
590 // DomQuery and jQuery get this wrong, oddly enough.
591 // The CSS 3 selectors spec is pretty explicit about it, too.
592 var cn = elem.childNodes;
593 var cnl = elem.childNodes.length;
594 // if(!cnl){ return true; }
595 for(var x=cnl-1; x >= 0; x--){
596 var nt = cn[x].nodeType;
597 if((nt === 1)||(nt == 3)){ return false; }
598 }
599 return true;
600 }
601 },
602 "contains": function(name, condition){
603 var cz = condition.charAt(0);
604 if( cz == '"' || cz == "'" ){ //remove quote
605 condition = condition.slice(1, -1);
606 }
607 return function(elem){
608 return (elem.innerHTML.indexOf(condition) >= 0);
609 }
610 },
611 "not": function(name, condition){
612 var p = getQueryParts(condition)[0];
613 var ignores = { el: 1 };
614 if(p.tag != "*"){
615 ignores.tag = 1;
616 }
617 if(!p.classes.length){
618 ignores.classes = 1;
619 }
620 var ntf = getSimpleFilterFunc(p, ignores);
621 return function(elem){
622 return (!ntf(elem));
623 }
624 },
625 "nth-child": function(name, condition){
626 var pi = parseInt;
627 // avoid re-defining function objects if we can
628 if(condition == "odd"){
629 return isOdd;
630 }else if(condition == "even"){
631 return isEven;
632 }
633 // FIXME: can we shorten this?
634 if(condition.indexOf("n") != -1){
635 var tparts = condition.split("n", 2);
636 var pred = tparts[0] ? ((tparts[0] == '-') ? -1 : pi(tparts[0])) : 1;
637 var idx = tparts[1] ? pi(tparts[1]) : 0;
638 var lb = 0, ub = -1;
639 if(pred > 0){
640 if(idx < 0){
641 idx = (idx % pred) && (pred + (idx % pred));
642 }else if(idx>0){
643 if(idx >= pred){
644 lb = idx - idx % pred;
645 }
646 idx = idx % pred;
647 }
648 }else if(pred<0){
649 pred *= -1;
650 // idx has to be greater than 0 when pred is negative;
651 // shall we throw an error here?
652 if(idx > 0){
653 ub = idx;
654 idx = idx % pred;
655 }
656 }
657 if(pred > 0){
658 return function(elem){
659 var i = getNodeIndex(elem);
660 return (i>=lb) && (ub<0 || i<=ub) && ((i % pred) == idx);
661 }
662 }else{
663 condition = idx;
664 }
665 }
666 var ncount = pi(condition);
667 return function(elem){
668 return (getNodeIndex(elem) == ncount);
669 }
670 }
671 };
672
673 var defaultGetter = (d.isIE) ? function(cond){
674 var clc = cond.toLowerCase();
675 if(clc == "class"){ cond = "className"; }
676 return function(elem){
677 return (caseSensitive ? elem.getAttribute(cond) : elem[cond]||elem[clc]);
678 }
679 } : function(cond){
680 return function(elem){
681 return (elem && elem.getAttribute && elem.hasAttribute(cond));
682 }
683 };
684
685 var getSimpleFilterFunc = function(query, ignores){
686 // generates a node tester function based on the passed query part. The
687 // query part is one of the structures generatd by the query parser
688 // when it creates the query AST. The "ignores" object specifies which
689 // (if any) tests to skip, allowing the system to avoid duplicating
690 // work where it may have already been taken into account by other
691 // factors such as how the nodes to test were fetched in the first
692 // place
693 if(!query){ return yesman; }
694 ignores = ignores||{};
695
696 var ff = null;
697
698 if(!("el" in ignores)){
699 ff = agree(ff, _isElement);
700 }
701
702 if(!("tag" in ignores)){
703 if(query.tag != "*"){
704 ff = agree(ff, function(elem){
705 return (elem && (elem.tagName == query.getTag()));
706 });
707 }
708 }
709
710 if(!("classes" in ignores)){
711 each(query.classes, function(cname, idx, arr){
712 // get the class name
713 /*
714 var isWildcard = cname.charAt(cname.length-1) == "*";
715 if(isWildcard){
716 cname = cname.substr(0, cname.length-1);
717 }
718 // I dislike the regex thing, even if memozied in a cache, but it's VERY short
719 var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)");
720 */
721 var re = new RegExp("(?:^|\\s)" + cname + "(?:\\s|$)");
722 ff = agree(ff, function(elem){
723 return re.test(elem.className);
724 });
725 ff.count = idx;
726 });
727 }
728
729 if(!("pseudos" in ignores)){
730 each(query.pseudos, function(pseudo){
731 var pn = pseudo.name;
732 if(pseudos[pn]){
733 ff = agree(ff, pseudos[pn](pn, pseudo.value));
734 }
735 });
736 }
737
738 if(!("attrs" in ignores)){
739 each(query.attrs, function(attr){
740 var matcher;
741 var a = attr.attr;
742 // type, attr, matchFor
743 if(attr.type && attrs[attr.type]){
744 matcher = attrs[attr.type](a, attr.matchFor);
745 }else if(a.length){
746 matcher = defaultGetter(a);
747 }
748 if(matcher){
749 ff = agree(ff, matcher);
750 }
751 });
752 }
753
754 if(!("id" in ignores)){
755 if(query.id){
756 ff = agree(ff, function(elem){
757 return (!!elem && (elem.id == query.id));
758 });
759 }
760 }
761
762 if(!ff){
763 if(!("default" in ignores)){
764 ff = yesman;
765 }
766 }
767 return ff;
768 };
769
770 var _nextSibling = function(filterFunc){
771 return function(node, ret, bag){
772 while(node = node[_ns]){
773 if(_noNES && (!_isElement(node))){ continue; }
774 if(
775 (!bag || _isUnique(node, bag)) &&
776 filterFunc(node)
777 ){
778 ret.push(node);
779 }
780 break;
781 }
782 return ret;
783 }
784 };
785
786 var _nextSiblings = function(filterFunc){
787 return function(root, ret, bag){
788 var te = root[_ns];
789 while(te){
790 if(_simpleNodeTest(te)){
791 if(bag && !_isUnique(te, bag)){
792 break;
793 }
794 if(filterFunc(te)){
795 ret.push(te);
796 }
797 }
798 te = te[_ns];
799 }
800 return ret;
801 }
802 };
803
804 // get an array of child *elements*, skipping text and comment nodes
805 var _childElements = function(filterFunc){
806 filterFunc = filterFunc||yesman;
807 return function(root, ret, bag){
808 // get an array of child elements, skipping text and comment nodes
809 var te, x = 0, tret = root[childNodesName];
810 while(te = tret[x++]){
811 if(
812 _simpleNodeTest(te) &&
813 (!bag || _isUnique(te, bag)) &&
814 (filterFunc(te, x))
815 ){
816 ret.push(te);
817 }
818 }
819 return ret;
820 };
821 };
822
823 /*
824 // thanks, Dean!
825 var itemIsAfterRoot = d.isIE ? function(item, root){
826 return (item.sourceIndex > root.sourceIndex);
827 } : function(item, root){
828 return (item.compareDocumentPosition(root) == 2);
829 };
830 */
831
832 // test to see if node is below root
833 var _isDescendant = function(node, root){
834 var pn = node.parentNode;
835 while(pn){
836 if(pn == root){
837 break;
838 }
839 pn = pn.parentNode;
840 }
841 return !!pn;
842 };
843
844 var _getElementsFuncCache = {};
845
846 var getElementsFunc = function(query){
847 var retFunc = _getElementsFuncCache[query.query];
848 // if we've got a cached dispatcher, just use that
849 if(retFunc){ return retFunc; }
850 // else, generate a new on
851
852 // NOTE:
853 // this function returns a function that searches for nodes and
854 // filters them. The search may be specialized by infix operators
855 // (">", "~", or "+") else it will default to searching all
856 // descendants (the " " selector). Once a group of children is
857 // founde, a test function is applied to weed out the ones we
858 // don't want. Many common cases can be fast-pathed. We spend a
859 // lot of cycles to create a dispatcher that doesn't do more work
860 // than necessary at any point since, unlike this function, the
861 // dispatchers will be called every time. The logic of generating
862 // efficient dispatchers looks like this in pseudo code:
863 //
864 // # if it's a purely descendant query (no ">", "+", or "~" modifiers)
865 // if infixOperator == " ":
866 // if only(id):
867 // return def(root):
868 // return d.byId(id, root);
869 //
870 // elif id:
871 // return def(root):
872 // return filter(d.byId(id, root));
873 //
874 // elif cssClass && getElementsByClassName:
875 // return def(root):
876 // return filter(root.getElementsByClassName(cssClass));
877 //
878 // elif only(tag):
879 // return def(root):
880 // return root.getElementsByTagName(tagName);
881 //
882 // else:
883 // # search by tag name, then filter
884 // return def(root):
885 // return filter(root.getElementsByTagName(tagName||"*"));
886 //
887 // elif infixOperator == ">":
888 // # search direct children
889 // return def(root):
890 // return filter(root.children);
891 //
892 // elif infixOperator == "+":
893 // # search next sibling
894 // return def(root):
895 // return filter(root.nextElementSibling);
896 //
897 // elif infixOperator == "~":
898 // # search rightward siblings
899 // return def(root):
900 // return filter(nextSiblings(root));
901
902 var io = query.infixOper;
903 var oper = (io ? io.oper : "");
904 // the default filter func which tests for all conditions in the query
905 // part. This is potentially inefficient, so some optimized paths may
906 // re-define it to test fewer things.
907 var filterFunc = getSimpleFilterFunc(query, { el: 1 });
908 var qt = query.tag;
909 var wildcardTag = ("*" == qt);
910 var ecs = getDoc()["getElementsByClassName"];
911
912 if(!oper){
913 // if there's no infix operator, then it's a descendant query. ID
914 // and "elements by class name" variants can be accelerated so we
915 // call them out explicitly:
916 if(query.id){
917 // testing shows that the overhead of yesman() is acceptable
918 // and can save us some bytes vs. re-defining the function
919 // everywhere.
920 filterFunc = (!query.loops && wildcardTag) ?
921 yesman :
922 getSimpleFilterFunc(query, { el: 1, id: 1 });
923
924 retFunc = function(root, arr){
925 var te = d.byId(query.id, (root.ownerDocument||root));
926 if(!te || !filterFunc(te)){ return; }
927 if(9 == root.nodeType){ // if root's a doc, we just return directly
928 return getArr(te, arr);
929 }else{ // otherwise check ancestry
930 if(_isDescendant(te, root)){
931 return getArr(te, arr);
932 }
933 }
934 }
935 }else if(
936 ecs &&
937 // isAlien check. Workaround for Prototype.js being totally evil/dumb.
938 /\{\s*\[native code\]\s*\}/.test(String(ecs)) &&
939 query.classes.length &&
940 !cssCaseBug
941 ){
942 // it's a class-based query and we've got a fast way to run it.
943
944 // ignore class and ID filters since we will have handled both
945 filterFunc = getSimpleFilterFunc(query, { el: 1, classes: 1, id: 1 });
946 var classesString = query.classes.join(" ");
947 retFunc = function(root, arr, bag){
948 var ret = getArr(0, arr), te, x=0;
949 var tret = root.getElementsByClassName(classesString);
950 while((te = tret[x++])){
951 if(filterFunc(te, root) && _isUnique(te, bag)){
952 ret.push(te);
953 }
954 }
955 return ret;
956 };
957
958 }else if(!wildcardTag && !query.loops){
959 // it's tag only. Fast-path it.
960 retFunc = function(root, arr, bag){
961 var ret = getArr(0, arr), te, x=0;
962 var tret = root.getElementsByTagName(query.getTag());
963 while((te = tret[x++])){
964 if(_isUnique(te, bag)){
965 ret.push(te);
966 }
967 }
968 return ret;
969 };
970 }else{
971 // the common case:
972 // a descendant selector without a fast path. By now it's got
973 // to have a tag selector, even if it's just "*" so we query
974 // by that and filter
975 filterFunc = getSimpleFilterFunc(query, { el: 1, tag: 1, id: 1 });
976 retFunc = function(root, arr, bag){
977 var ret = getArr(0, arr), te, x=0;
978 // we use getTag() to avoid case sensitivity issues
979 var tret = root.getElementsByTagName(query.getTag());
980 while((te = tret[x++])){
981 if(filterFunc(te, root) && _isUnique(te, bag)){
982 ret.push(te);
983 }
984 }
985 return ret;
986 };
987 }
988 }else{
989 // the query is scoped in some way. Instead of querying by tag we
990 // use some other collection to find candidate nodes
991 var skipFilters = { el: 1 };
992 if(wildcardTag){
993 skipFilters.tag = 1;
994 }
995 filterFunc = getSimpleFilterFunc(query, skipFilters);
996 if("+" == oper){
997 retFunc = _nextSibling(filterFunc);
998 }else if("~" == oper){
999 retFunc = _nextSiblings(filterFunc);
1000 }else if(">" == oper){
1001 retFunc = _childElements(filterFunc);
1002 }
1003 }
1004 // cache it and return
1005 return _getElementsFuncCache[query.query] = retFunc;
1006 };
1007
1008 var filterDown = function(root, queryParts){
1009 // NOTE:
1010 // this is the guts of the DOM query system. It takes a list of
1011 // parsed query parts and a root and finds children which match
1012 // the selector represented by the parts
1013 var candidates = getArr(root), qp, x, te, qpl = queryParts.length, bag, ret;
1014
1015 for(var i = 0; i < qpl; i++){
1016 ret = [];
1017 qp = queryParts[i];
1018 x = candidates.length - 1;
1019 if(x > 0){
1020 // if we have more than one root at this level, provide a new
1021 // hash to use for checking group membership but tell the
1022 // system not to post-filter us since we will already have been
1023 // gauranteed to be unique
1024 bag = {};
1025 ret.nozip = true;
1026 }
1027 var gef = getElementsFunc(qp);
1028 for(var j = 0; (te = candidates[j]); j++){
1029 // for every root, get the elements that match the descendant
1030 // selector, adding them to the "ret" array and filtering them
1031 // via membership in this level's bag. If there are more query
1032 // parts, then this level's return will be used as the next
1033 // level's candidates
1034 gef(te, ret, bag);
1035 }
1036 if(!ret.length){ break; }
1037 candidates = ret;
1038 }
1039 return ret;
1040 };
1041
1042 ////////////////////////////////////////////////////////////////////////
1043 // the query runner
1044 ////////////////////////////////////////////////////////////////////////
1045
1046 // these are the primary caches for full-query results. The query
1047 // dispatcher functions are generated then stored here for hash lookup in
1048 // the future
1049 var _queryFuncCacheDOM = {},
1050 _queryFuncCacheQSA = {};
1051
1052 // this is the second level of spliting, from full-length queries (e.g.,
1053 // "div.foo .bar") into simple query expressions (e.g., ["div.foo",
1054 // ".bar"])
1055 var getStepQueryFunc = function(query){
1056 var qparts = getQueryParts(trim(query));
1057
1058 // if it's trivial, avoid iteration and zipping costs
1059 if(qparts.length == 1){
1060 // we optimize this case here to prevent dispatch further down the
1061 // chain, potentially slowing things down. We could more elegantly
1062 // handle this in filterDown(), but it's slower for simple things
1063 // that need to be fast (e.g., "#someId").
1064 var tef = getElementsFunc(qparts[0]);
1065 return function(root){
1066 var r = tef(root, new qlc());
1067 if(r){ r.nozip = true; }
1068 return r;
1069 }
1070 }
1071
1072 // otherwise, break it up and return a runner that iterates over the parts recursively
1073 return function(root){
1074 return filterDown(root, qparts);
1075 }
1076 };
1077
1078 // NOTES:
1079 // * we can't trust QSA for anything but document-rooted queries, so
1080 // caching is split into DOM query evaluators and QSA query evaluators
1081 // * caching query results is dirty and leak-prone (or, at a minimum,
1082 // prone to unbounded growth). Other toolkits may go this route, but
1083 // they totally destroy their own ability to manage their memory
1084 // footprint. If we implement it, it should only ever be with a fixed
1085 // total element reference # limit and an LRU-style algorithm since JS
1086 // has no weakref support. Caching compiled query evaluators is also
1087 // potentially problematic, but even on large documents the size of the
1088 // query evaluators is often < 100 function objects per evaluator (and
1089 // LRU can be applied if it's ever shown to be an issue).
1090 // * since IE's QSA support is currently only for HTML documents and even
1091 // then only in IE 8's "standards mode", we have to detect our dispatch
1092 // route at query time and keep 2 separate caches. Ugg.
1093
1094 // we need to determine if we think we can run a given query via
1095 // querySelectorAll or if we'll need to fall back on DOM queries to get
1096 // there. We need a lot of information about the environment and the query
1097 // to make the determiniation (e.g. does it support QSA, does the query in
1098 // question work in the native QSA impl, etc.).
1099 var nua = navigator.userAgent;
1100 // some versions of Safari provided QSA, but it was buggy and crash-prone.
1101 // We need te detect the right "internal" webkit version to make this work.
1102 var wk = "WebKit/";
1103 var is525 = (
1104 d.isWebKit &&
1105 (nua.indexOf(wk) > 0) &&
1106 (parseFloat(nua.split(wk)[1]) > 528)
1107 );
1108
1109 // IE QSA queries may incorrectly include comment nodes, so we throw the
1110 // zipping function into "remove" comments mode instead of the normal "skip
1111 // it" which every other QSA-clued browser enjoys
1112 var noZip = d.isIE ? "commentStrip" : "nozip";
1113
1114 var qsa = "querySelectorAll";
1115 var qsaAvail = (
1116 !!getDoc()[qsa] &&
1117 // see #5832
1118 (!d.isSafari || (d.isSafari > 3.1) || is525 )
1119 );
1120
1121 //Don't bother with n+3 type of matches, IE complains if we modify those.
1122 var infixSpaceRe = /n\+\d|([^ ])?([>~+])([^ =])?/g;
1123 var infixSpaceFunc = function(match, pre, ch, post) {
1124 return ch ? (pre ? pre + " " : "") + ch + (post ? " " + post : "") : /*n+3*/ match;
1125 };
1126
1127 var getQueryFunc = function(query, forceDOM){
1128 //Normalize query. The CSS3 selectors spec allows for omitting spaces around
1129 //infix operators, >, ~ and +
1130 //Do the work here since detection for spaces is used as a simple "not use QSA"
1131 //test below.
1132 query = query.replace(infixSpaceRe, infixSpaceFunc);
1133
1134 if(qsaAvail){
1135 // if we've got a cached variant and we think we can do it, run it!
1136 var qsaCached = _queryFuncCacheQSA[query];
1137 if(qsaCached && !forceDOM){ return qsaCached; }
1138 }
1139
1140 // else if we've got a DOM cached variant, assume that we already know
1141 // all we need to and use it
1142 var domCached = _queryFuncCacheDOM[query];
1143 if(domCached){ return domCached; }
1144
1145 // TODO:
1146 // today we're caching DOM and QSA branches separately so we
1147 // recalc useQSA every time. If we had a way to tag root+query
1148 // efficiently, we'd be in good shape to do a global cache.
1149
1150 var qcz = query.charAt(0);
1151 var nospace = (-1 == query.indexOf(" "));
1152
1153 // byId searches are wicked fast compared to QSA, even when filtering
1154 // is required
1155 if( (query.indexOf("#") >= 0) && (nospace) ){
1156 forceDOM = true;
1157 }
1158
1159 var useQSA = (
1160 qsaAvail && (!forceDOM) &&
1161 // as per CSS 3, we can't currently start w/ combinator:
1162 // http://www.w3.org/TR/css3-selectors/#w3cselgrammar
1163 (specials.indexOf(qcz) == -1) &&
1164 // IE's QSA impl sucks on pseudos
1165 (!d.isIE || (query.indexOf(":") == -1)) &&
1166
1167 (!(cssCaseBug && (query.indexOf(".") >= 0))) &&
1168
1169 // FIXME:
1170 // need to tighten up browser rules on ":contains" and "|=" to
1171 // figure out which aren't good
1172 // Latest webkit (around 531.21.8) does not seem to do well with :checked on option
1173 // elements, even though according to spec, selected options should
1174 // match :checked. So go nonQSA for it:
1175 // http://bugs.dojotoolkit.org/ticket/5179
1176 (query.indexOf(":contains") == -1) && (query.indexOf(":checked") == -1) &&
1177 (query.indexOf("|=") == -1) // some browsers don't grok it
1178 );
1179
1180 // TODO:
1181 // if we've got a descendant query (e.g., "> .thinger" instead of
1182 // just ".thinger") in a QSA-able doc, but are passed a child as a
1183 // root, it should be possible to give the item a synthetic ID and
1184 // trivially rewrite the query to the form "#synid > .thinger" to
1185 // use the QSA branch
1186
1187
1188 if(useQSA){
1189 var tq = (specials.indexOf(query.charAt(query.length-1)) >= 0) ?
1190 (query + " *") : query;
1191 return _queryFuncCacheQSA[query] = function(root){
1192 try{
1193 // the QSA system contains an egregious spec bug which
1194 // limits us, effectively, to only running QSA queries over
1195 // entire documents. See:
1196 // http://ejohn.org/blog/thoughts-on-queryselectorall/
1197 // despite this, we can also handle QSA runs on simple
1198 // selectors, but we don't want detection to be expensive
1199 // so we're just checking for the presence of a space char
1200 // right now. Not elegant, but it's cheaper than running
1201 // the query parser when we might not need to
1202 if(!((9 == root.nodeType) || nospace)){ throw ""; }
1203 var r = root[qsa](tq);
1204 // skip expensive duplication checks and just wrap in a NodeList
1205 r[noZip] = true;
1206 return r;
1207 }catch(e){
1208 // else run the DOM branch on this query, ensuring that we
1209 // default that way in the future
1210 return getQueryFunc(query, true)(root);
1211 }
1212 }
1213 }else{
1214 // DOM branch
1215 var parts = query.split(/\s*,\s*/);
1216 return _queryFuncCacheDOM[query] = ((parts.length < 2) ?
1217 // if not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher
1218 getStepQueryFunc(query) :
1219 // if it *is* a complex query, break it up into its
1220 // constituent parts and return a dispatcher that will
1221 // merge the parts when run
1222 function(root){
1223 var pindex = 0, // avoid array alloc for every invocation
1224 ret = [],
1225 tp;
1226 while((tp = parts[pindex++])){
1227 ret = ret.concat(getStepQueryFunc(tp)(root));
1228 }
1229 return ret;
1230 }
1231 );
1232 }
1233 };
1234
1235 var _zipIdx = 0;
1236
1237 // NOTE:
1238 // this function is Moo inspired, but our own impl to deal correctly
1239 // with XML in IE
1240 var _nodeUID = d.isIE ? function(node){
1241 if(caseSensitive){
1242 // XML docs don't have uniqueID on their nodes
1243 return (node.getAttribute("_uid") || node.setAttribute("_uid", ++_zipIdx) || _zipIdx);
1244
1245 }else{
1246 return node.uniqueID;
1247 }
1248 } :
1249 function(node){
1250 return (node._uid || (node._uid = ++_zipIdx));
1251 };
1252
1253 // determine if a node in is unique in a "bag". In this case we don't want
1254 // to flatten a list of unique items, but rather just tell if the item in
1255 // question is already in the bag. Normally we'd just use hash lookup to do
1256 // this for us but IE's DOM is busted so we can't really count on that. On
1257 // the upside, it gives us a built in unique ID function.
1258 var _isUnique = function(node, bag){
1259 if(!bag){ return 1; }
1260 var id = _nodeUID(node);
1261 if(!bag[id]){ return bag[id] = 1; }
1262 return 0;
1263 };
1264
1265 // attempt to efficiently determine if an item in a list is a dupe,
1266 // returning a list of "uniques", hopefully in doucment order
1267 var _zipIdxName = "_zipIdx";
1268 var _zip = function(arr){
1269 if(arr && arr.nozip){
1270 return (qlc._wrap) ? qlc._wrap(arr) : arr;
1271 }
1272 // var ret = new d._NodeListCtor();
1273 var ret = new qlc();
1274 if(!arr || !arr.length){ return ret; }
1275 if(arr[0]){
1276 ret.push(arr[0]);
1277 }
1278 if(arr.length < 2){ return ret; }
1279
1280 _zipIdx++;
1281
1282 // we have to fork here for IE and XML docs because we can't set
1283 // expandos on their nodes (apparently). *sigh*
1284 if(d.isIE && caseSensitive){
1285 var szidx = _zipIdx+"";
1286 arr[0].setAttribute(_zipIdxName, szidx);
1287 for(var x = 1, te; te = arr[x]; x++){
1288 if(arr[x].getAttribute(_zipIdxName) != szidx){
1289 ret.push(te);
1290 }
1291 te.setAttribute(_zipIdxName, szidx);
1292 }
1293 }else if(d.isIE && arr.commentStrip){
1294 try{
1295 for(var x = 1, te; te = arr[x]; x++){
1296 if(_isElement(te)){
1297 ret.push(te);
1298 }
1299 }
1300 }catch(e){ /* squelch */ }
1301 }else{
1302 if(arr[0]){ arr[0][_zipIdxName] = _zipIdx; }
1303 for(var x = 1, te; te = arr[x]; x++){
1304 if(arr[x][_zipIdxName] != _zipIdx){
1305 ret.push(te);
1306 }
1307 te[_zipIdxName] = _zipIdx;
1308 }
1309 }
1310 return ret;
1311 };
1312
1313 // the main executor
1314 d.query = function(/*String*/ query, /*String|DOMNode?*/ root){
1315 // summary:
1316 // Returns nodes which match the given CSS3 selector, searching the
1317 // entire document by default but optionally taking a node to scope
1318 // the search by. Returns an instance of dojo.NodeList.
1319 // description:
1320 // dojo.query() is the swiss army knife of DOM node manipulation in
1321 // Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's
1322 // "$" function, dojo.query provides robust, high-performance
1323 // CSS-based node selector support with the option of scoping searches
1324 // to a particular sub-tree of a document.
1325 //
1326 // Supported Selectors:
1327 // --------------------
1328 //
1329 // dojo.query() supports a rich set of CSS3 selectors, including:
1330 //
1331 // * class selectors (e.g., `.foo`)
1332 // * node type selectors like `span`
1333 // * ` ` descendant selectors
1334 // * `>` child element selectors
1335 // * `#foo` style ID selectors
1336 // * `*` universal selector
1337 // * `~`, the immediately preceeded-by sibling selector
1338 // * `+`, the preceeded-by sibling selector
1339 // * attribute queries:
1340 // | * `[foo]` attribute presence selector
1341 // | * `[foo='bar']` attribute value exact match
1342 // | * `[foo~='bar']` attribute value list item match
1343 // | * `[foo^='bar']` attribute start match
1344 // | * `[foo$='bar']` attribute end match
1345 // | * `[foo*='bar']` attribute substring match
1346 // * `:first-child`, `:last-child`, and `:only-child` positional selectors
1347 // * `:empty` content emtpy selector
1348 // * `:checked` pseudo selector
1349 // * `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations
1350 // * `:nth-child(even)`, `:nth-child(odd)` positional selectors
1351 // * `:not(...)` negation pseudo selectors
1352 //
1353 // Any legal combination of these selectors will work with
1354 // `dojo.query()`, including compound selectors ("," delimited).
1355 // Very complex and useful searches can be constructed with this
1356 // palette of selectors and when combined with functions for
1357 // manipulation presented by dojo.NodeList, many types of DOM
1358 // manipulation operations become very straightforward.
1359 //
1360 // Unsupported Selectors:
1361 // ----------------------
1362 //
1363 // While dojo.query handles many CSS3 selectors, some fall outside of
1364 // what's resaonable for a programmatic node querying engine to
1365 // handle. Currently unsupported selectors include:
1366 //
1367 // * namespace-differentiated selectors of any form
1368 // * all `::` pseduo-element selectors
1369 // * certain pseduo-selectors which don't get a lot of day-to-day use:
1370 // | * `:root`, `:lang()`, `:target`, `:focus`
1371 // * all visual and state selectors:
1372 // | * `:root`, `:active`, `:hover`, `:visisted`, `:link`,
1373 // `:enabled`, `:disabled`
1374 // * `:*-of-type` pseudo selectors
1375 //
1376 // dojo.query and XML Documents:
1377 // -----------------------------
1378 //
1379 // `dojo.query` (as of dojo 1.2) supports searching XML documents
1380 // in a case-sensitive manner. If an HTML document is served with
1381 // a doctype that forces case-sensitivity (e.g., XHTML 1.1
1382 // Strict), dojo.query() will detect this and "do the right
1383 // thing". Case sensitivity is dependent upon the document being
1384 // searched and not the query used. It is therefore possible to
1385 // use case-sensitive queries on strict sub-documents (iframes,
1386 // etc.) or XML documents while still assuming case-insensitivity
1387 // for a host/root document.
1388 //
1389 // Non-selector Queries:
1390 // ---------------------
1391 //
1392 // If something other than a String is passed for the query,
1393 // `dojo.query` will return a new `dojo.NodeList` instance
1394 // constructed from that parameter alone and all further
1395 // processing will stop. This means that if you have a reference
1396 // to a node or NodeList, you can quickly construct a new NodeList
1397 // from the original by calling `dojo.query(node)` or
1398 // `dojo.query(list)`.
1399 //
1400 // query:
1401 // The CSS3 expression to match against. For details on the syntax of
1402 // CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors>
1403 // root:
1404 // A DOMNode (or node id) to scope the search from. Optional.
1405 // returns: dojo.NodeList
1406 // An instance of `dojo.NodeList`. Many methods are available on
1407 // NodeLists for searching, iterating, manipulating, and handling
1408 // events on the matched nodes in the returned list.
1409 // example:
1410 // search the entire document for elements with the class "foo":
1411 // | dojo.query(".foo");
1412 // these elements will match:
1413 // | <span class="foo"></span>
1414 // | <span class="foo bar"></span>
1415 // | <p class="thud foo"></p>
1416 // example:
1417 // search the entire document for elements with the classes "foo" *and* "bar":
1418 // | dojo.query(".foo.bar");
1419 // these elements will match:
1420 // | <span class="foo bar"></span>
1421 // while these will not:
1422 // | <span class="foo"></span>
1423 // | <p class="thud foo"></p>
1424 // example:
1425 // find `<span>` elements which are descendants of paragraphs and
1426 // which have a "highlighted" class:
1427 // | dojo.query("p span.highlighted");
1428 // the innermost span in this fragment matches:
1429 // | <p class="foo">
1430 // | <span>...
1431 // | <span class="highlighted foo bar">...</span>
1432 // | </span>
1433 // | </p>
1434 // example:
1435 // set an "odd" class on all odd table rows inside of the table
1436 // `#tabular_data`, using the `>` (direct child) selector to avoid
1437 // affecting any nested tables:
1438 // | dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd");
1439 // example:
1440 // remove all elements with the class "error" from the document
1441 // and store them in a list:
1442 // | var errors = dojo.query(".error").orphan();
1443 // example:
1444 // add an onclick handler to every submit button in the document
1445 // which causes the form to be sent via Ajax instead:
1446 // | dojo.query("input[type='submit']").onclick(function(e){
1447 // | dojo.stopEvent(e); // prevent sending the form
1448 // | var btn = e.target;
1449 // | dojo.xhrPost({
1450 // | form: btn.form,
1451 // | load: function(data){
1452 // | // replace the form with the response
1453 // | var div = dojo.doc.createElement("div");
1454 // | dojo.place(div, btn.form, "after");
1455 // | div.innerHTML = data;
1456 // | dojo.style(btn.form, "display", "none");
1457 // | }
1458 // | });
1459 // | });
1460
1461 //Set list constructor to desired value. This can change
1462 //between calls, so always re-assign here.
1463 qlc = d._NodeListCtor;
1464
1465 if(!query){
1466 return new qlc();
1467 }
1468
1469 if(query.constructor == qlc){
1470 return query;
1471 }
1472 if(typeof query != "string"){ // inline'd type check
1473 return new qlc(query); // dojo.NodeList
1474 }
1475 if(typeof root == "string"){ // inline'd type check
1476 root = d.byId(root);
1477 if(!root){ return new qlc(); }
1478 }
1479
1480 root = root||getDoc();
1481 var od = root.ownerDocument||root.documentElement;
1482
1483 // throw the big case sensitivity switch
1484
1485 // NOTE:
1486 // Opera in XHTML mode doesn't detect case-sensitivity correctly
1487 // and it's not clear that there's any way to test for it
1488 caseSensitive = (root.contentType && root.contentType=="application/xml") ||
1489 (d.isOpera && (root.doctype || od.toString() == "[object XMLDocument]")) ||
1490 (!!od) &&
1491 (d.isIE ? od.xml : (root.xmlVersion||od.xmlVersion));
1492
1493 // NOTE:
1494 // adding "true" as the 2nd argument to getQueryFunc is useful for
1495 // testing the DOM branch without worrying about the
1496 // behavior/performance of the QSA branch.
1497 var r = getQueryFunc(query)(root);
1498
1499 // FIXME:
1500 // need to investigate this branch WRT #8074 and #8075
1501 if(r && r.nozip && !qlc._wrap){
1502 return r;
1503 }
1504 return _zip(r); // dojo.NodeList
1505 }
1506
1507 // FIXME: need to add infrastructure for post-filtering pseudos, ala :last
1508 d.query.pseudos = pseudos;
1509
1510 // one-off function for filtering a NodeList based on a simple selector
1511 d._filterQueryResult = function(nodeList, simpleFilter){
1512 var tmpNodeList = new d._NodeListCtor();
1513 var filterFunc = getSimpleFilterFunc(getQueryParts(simpleFilter)[0]);
1514 for(var x = 0, te; te = nodeList[x]; x++){
1515 if(filterFunc(te)){ tmpNodeList.push(te); }
1516 }
1517 return tmpNodeList;
1518 }
2f01fe57 1519})(this["queryPortability"]||this["acme"]||dojo);
a089699c
AD
1520
1521/*
1522*/
1523
2f01fe57 1524}