diff options
Diffstat (limited to 'includes/js/dojo/_base/query.js')
-rw-r--r-- | includes/js/dojo/_base/query.js | 1191 |
1 files changed, 0 insertions, 1191 deletions
diff --git a/includes/js/dojo/_base/query.js b/includes/js/dojo/_base/query.js deleted file mode 100644 index 8743df8..0000000 --- a/includes/js/dojo/_base/query.js +++ /dev/null @@ -1,1191 +0,0 @@ -if(!dojo._hasResource["dojo._base.query"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo._base.query"] = true; -dojo.provide("dojo._base.query"); -dojo.require("dojo._base.NodeList"); - -/* - dojo.query() architectural overview: - - dojo.query is a relatively full-featured CSS3 query library. It is - designed to take any valid CSS3 selector and return the nodes matching - the selector. To do this quickly, it processes queries in several - steps, applying caching where profitable. - - The steps (roughly in reverse order of the way they appear in the code): - 1.) check to see if we already have a "query dispatcher" - - if so, use that with the given parameterization. Skip to step 4. - 2.) attempt to determine which branch to dispatch the query to: - - JS (optimized DOM iteration) - - xpath (for browsers that support it and where it's fast) - - native (not available in any browser yet) - 3.) tokenize and convert to executable "query dispatcher" - - this is where the lion's share of the complexity in the - system lies. In the DOM version, the query dispatcher is - assembled as a chain of "yes/no" test functions pertaining to - a section of a simple query statement (".blah:nth-child(odd)" - but not "div div", which is 2 simple statements). Individual - statement dispatchers are cached (to prevent re-definition) - as are entire dispatch chains (to make re-execution of the - same query fast) - - in the xpath path, tokenization yeilds a concatenation of - parameterized xpath selectors. As with the DOM version, both - simple selector blocks and overall evaluators are cached to - prevent re-defintion - 4.) the resulting query dispatcher is called in the passed scope (by default the top-level document) - - for DOM queries, this results in a recursive, top-down - evaluation of nodes based on each simple query section - - xpath queries can, thankfully, be executed in one shot - 5.) matched nodes are pruned to ensure they are unique -*/ - -;(function(){ - // define everything in a closure for compressability reasons. "d" is an - // alias to "dojo" since it's so frequently used. This seems a - // transformation that the build system could perform on a per-file basis. - - //////////////////////////////////////////////////////////////////////// - // Utility code - //////////////////////////////////////////////////////////////////////// - - var d = dojo; - var childNodesName = dojo.isIE ? "children" : "childNodes"; - var caseSensitive = false; - - var getQueryParts = function(query){ - // summary: state machine for query tokenization - if(">~+".indexOf(query.charAt(query.length-1)) >= 0){ - query += " *" - } - query += " "; // ensure that we terminate the state machine - - var ts = function(s, e){ - return d.trim(query.slice(s, e)); - } - - // the overall data graph of the full query, as represented by queryPart objects - var qparts = []; - // state keeping vars - var inBrackets = -1; - var inParens = -1; - var inMatchFor = -1; - var inPseudo = -1; - var inClass = -1; - var inId = -1; - var inTag = -1; - var lc = ""; // the last character - var cc = ""; // the current character - var pStart; - // iteration vars - var x = 0; // index in the query - var ql = query.length; - var currentPart = null; // data structure representing the entire clause - var _cp = null; // the current pseudo or attr matcher - - var endTag = function(){ - if(inTag >= 0){ - var tv = (inTag == x) ? null : ts(inTag, x).toLowerCase(); - currentPart[ (">~+".indexOf(tv) < 0) ? "tag" : "oper" ] = tv; - inTag = -1; - } - } - - var endId = function(){ - if(inId >= 0){ - currentPart.id = ts(inId, x).replace(/\\/g, ""); - inId = -1; - } - } - - var endClass = function(){ - if(inClass >= 0){ - currentPart.classes.push(ts(inClass+1, x).replace(/\\/g, "")); - inClass = -1; - } - } - - var endAll = function(){ - endId(); endTag(); endClass(); - } - - for(; lc=cc, cc=query.charAt(x),x<ql; x++){ - if(lc == "\\"){ continue; } - if(!currentPart){ - // NOTE: I hate all this alloc, but it's shorter than writing tons of if's - pStart = x; - currentPart = { - query: null, - pseudos: [], - attrs: [], - classes: [], - tag: null, - oper: null, - id: null - }; - inTag = x; - } - - if(inBrackets >= 0){ - // look for a the close first - if(cc == "]"){ - if(!_cp.attr){ - _cp.attr = ts(inBrackets+1, x); - }else{ - _cp.matchFor = ts((inMatchFor||inBrackets+1), x); - } - var cmf = _cp.matchFor; - if(cmf){ - if( (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){ - _cp.matchFor = cmf.substring(1, cmf.length-1); - } - } - currentPart.attrs.push(_cp); - _cp = null; // necessaray? - inBrackets = inMatchFor = -1; - }else if(cc == "="){ - var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : ""; - _cp.type = addToCc+cc; - _cp.attr = ts(inBrackets+1, x-addToCc.length); - inMatchFor = x+1; - } - // now look for other clause parts - }else if(inParens >= 0){ - if(cc == ")"){ - if(inPseudo >= 0){ - _cp.value = ts(inParens+1, x); - } - inPseudo = inParens = -1; - } - }else if(cc == "#"){ - endAll(); - inId = x+1; - }else if(cc == "."){ - endAll(); - inClass = x; - }else if(cc == ":"){ - endAll(); - inPseudo = x; - }else if(cc == "["){ - endAll(); - inBrackets = x; - _cp = { - /*===== - attr: null, type: null, matchFor: null - =====*/ - }; - }else if(cc == "("){ - if(inPseudo >= 0){ - _cp = { - name: ts(inPseudo+1, x), - value: null - } - currentPart.pseudos.push(_cp); - } - inParens = x; - }else if(cc == " " && lc != cc){ - // note that we expect the string to be " " terminated - endAll(); - if(inPseudo >= 0){ - currentPart.pseudos.push({ name: ts(inPseudo+1, x) }); - } - currentPart.hasLoops = ( - currentPart.pseudos.length || - currentPart.attrs.length || - currentPart.classes.length ); - currentPart.query = ts(pStart, x); - currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*"); - qparts.push(currentPart); - currentPart = null; - } - } - return qparts; - }; - - - //////////////////////////////////////////////////////////////////////// - // XPath query code - //////////////////////////////////////////////////////////////////////// - - // this array is a lookup used to generate an attribute matching function. - // There is a similar lookup/generator list for the DOM branch with similar - // calling semantics. - var xPathAttrs = { - "*=": function(attr, value){ - return "[contains(@"+attr+", '"+ value +"')]"; - }, - "^=": function(attr, value){ - return "[starts-with(@"+attr+", '"+ value +"')]"; - }, - "$=": function(attr, value){ - return "[substring(@"+attr+", string-length(@"+attr+")-"+(value.length-1)+")='"+value+"']"; - }, - "~=": function(attr, value){ - return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]"; - }, - "|=": function(attr, value){ - return "[contains(concat(' ',@"+attr+",' '), ' "+ value +"-')]"; - }, - "=": function(attr, value){ - return "[@"+attr+"='"+ value +"']"; - } - }; - - // takes a list of attribute searches, the overall query, a function to - // generate a default matcher, and a closure-bound method for providing a - // matching function that generates whatever type of yes/no distinguisher - // the query method needs. The method is a bit tortured and hard to read - // because it needs to be used in both the XPath and DOM branches. - var handleAttrs = function( attrList, - query, - getDefault, - handleMatch){ - d.forEach(query.attrs, function(attr){ - var matcher; - // type, attr, matchFor - if(attr.type && attrList[attr.type]){ - matcher = attrList[attr.type](attr.attr, attr.matchFor); - }else if(attr.attr.length){ - matcher = getDefault(attr.attr); - } - if(matcher){ handleMatch(matcher); } - }); - } - - var buildPath = function(query){ - var xpath = "."; - var qparts = getQueryParts(d.trim(query)); - while(qparts.length){ - var tqp = qparts.shift(); - var prefix; - var postfix = ""; - if(tqp.oper == ">"){ - prefix = "/"; - // prefix = "/child::*"; - tqp = qparts.shift(); - }else if(tqp.oper == "~"){ - prefix = "/following-sibling::"; // get element following siblings - tqp = qparts.shift(); - }else if(tqp.oper == "+"){ - // FIXME: - // fails when selecting subsequent siblings by node type - // because the position() checks the position in the list - // of matching elements and not the localized siblings - prefix = "/following-sibling::"; - postfix = "[position()=1]"; - tqp = qparts.shift(); - }else{ - prefix = "//"; - // prefix = "/descendant::*" - } - - // get the tag name (if any) - - xpath += prefix + tqp.tag + postfix; - - // check to see if it's got an id. Needs to come first in xpath. - if(tqp.id){ - xpath += "[@id='"+tqp.id+"'][1]"; - } - - d.forEach(tqp.classes, function(cn){ - var cnl = cn.length; - var padding = " "; - if(cn.charAt(cnl-1) == "*"){ - padding = ""; cn = cn.substr(0, cnl-1); - } - xpath += - "[contains(concat(' ',@class,' '), ' "+ - cn + padding + "')]"; - }); - - handleAttrs(xPathAttrs, tqp, - function(condition){ - return "[@"+condition+"]"; - }, - function(matcher){ - xpath += matcher; - } - ); - - // FIXME: need to implement pseudo-class checks!! - }; - return xpath; - }; - - var _xpathFuncCache = {}; - var getXPathFunc = function(path){ - if(_xpathFuncCache[path]){ - return _xpathFuncCache[path]; - } - - var doc = d.doc; - // don't need to memoize. The closure scope handles it for us. - var xpath = buildPath(path); - - var tf = function(parent){ - // XPath query strings are memoized. - var ret = []; - var xpathResult; - try{ - xpathResult = doc.evaluate(xpath, parent, null, - // XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); - XPathResult.ANY_TYPE, null); - }catch(e){ - console.debug("failure in exprssion:", xpath, "under:", parent); - console.debug(e); - } - var result = xpathResult.iterateNext(); - while(result){ - ret.push(result); - result = xpathResult.iterateNext(); - } - return ret; - } - return _xpathFuncCache[path] = tf; - }; - - /* - d.xPathMatch = function(query){ - // XPath based DOM query system. Handles a small subset of CSS - // selectors, subset is identical to the non-XPath version of this - // function. - - return getXPathFunc(query)(); - } - */ - - //////////////////////////////////////////////////////////////////////// - // DOM query code - //////////////////////////////////////////////////////////////////////// - - var _filtersCache = {}; - var _simpleFiltersCache = {}; - - // the basic building block of the yes/no chaining system. agree(f1, f2) - // generates a new function which returns the boolean results of both of - // the passed functions to a single logical-anded result. - var agree = function(first, second){ - if(!first){ return second; } - if(!second){ return first; } - - return function(){ - return first.apply(window, arguments) && second.apply(window, arguments); - } - } - - var _childElements = function(root){ - var ret = []; - var te, x=0, tret = root[childNodesName]; - while(te=tret[x++]){ - if(te.nodeType == 1){ ret.push(te); } - } - return ret; - } - - var _nextSiblings = function(root, single){ - var ret = []; - var te = root; - while(te = te.nextSibling){ - if(te.nodeType == 1){ - ret.push(te); - if(single){ break; } - } - } - return ret; - } - - var _filterDown = function(element, queryParts, matchArr, idx){ - // NOTE: - // in the fast path! this function is called recursively and for - // every run of a query. - var nidx = idx+1; - var isFinal = (queryParts.length == nidx); - var tqp = queryParts[idx]; - - // see if we can constrain our next level to direct children - if(tqp.oper){ - var ecn = (tqp.oper == ">") ? - _childElements(element) : - _nextSiblings(element, (tqp.oper == "+")); - - if(!ecn || !ecn.length){ - return; - } - nidx++; - isFinal = (queryParts.length == nidx); - // kinda janky, too much array alloc - var tf = getFilterFunc(queryParts[idx+1]); - // for(var x=ecn.length-1, te; x>=0, te=ecn[x]; x--){ - for(var x=0, ecnl=ecn.length, te; x<ecnl, te=ecn[x]; x++){ - if(tf(te)){ - if(isFinal){ - matchArr.push(te); - }else{ - _filterDown(te, queryParts, matchArr, nidx); - } - } - /* - if(x==0){ - break; - } - */ - } - } - - // otherwise, keep going down, unless we'er at the end - var candidates = getElementsFunc(tqp)(element); - if(isFinal){ - while(candidates.length){ - matchArr.push(candidates.shift()); - } - /* - candidates.unshift(0, matchArr.length-1); - matchArr.splice.apply(matchArr, candidates); - */ - }else{ - // if we're not yet at the bottom, keep going! - while(candidates.length){ - _filterDown(candidates.shift(), queryParts, matchArr, nidx); - } - } - } - - var filterDown = function(elements, queryParts){ - var ret = []; - - // for every root, get the elements that match the descendant selector - // for(var x=elements.length-1, te; x>=0, te=elements[x]; x--){ - var x = elements.length - 1, te; - while(te = elements[x--]){ - _filterDown(te, queryParts, ret, 0); - } - return ret; - } - - var getFilterFunc = function(q){ - // note: query can't have spaces! - if(_filtersCache[q.query]){ - return _filtersCache[q.query]; - } - var ff = null; - - // does it have a tagName component? - if(q.tag){ - if(q.tag == "*"){ - ff = agree(ff, - function(elem){ - return (elem.nodeType == 1); - } - ); - }else{ - // tag name match - ff = agree(ff, - function(elem){ - return ( - (elem.nodeType == 1) && - (q.tag == elem.tagName.toLowerCase()) - ); - // return isTn; - } - ); - } - } - - // does the node have an ID? - if(q.id){ - ff = agree(ff, - function(elem){ - return ( - (elem.nodeType == 1) && - (elem.id == q.id) - ); - } - ); - } - - if(q.hasLoops){ - // if we have other query param parts, make sure we add them to the - // filter chain - ff = agree(ff, getSimpleFilterFunc(q)); - } - - return _filtersCache[q.query] = ff; - } - - var getNodeIndex = function(node){ - // NOTE: - // we could have a more accurate caching mechanism by invalidating - // caches after the query has finished, but I think that'd lead to - // significantly more cache churn than the cache would provide - // value for in the common case. Generally, we're more - // conservative (and therefore, more accurate) than jQuery and - // DomQuery WRT node node indexes, but there may be corner cases - // in which we fall down. How much we care about them is TBD. - - var pn = node.parentNode; - var pnc = pn.childNodes; - - // check to see if we can trust the cache. If not, re-key the whole - // thing and return our node match from that. - - var nidx = -1; - var child = pn.firstChild; - if(!child){ - return nidx; - } - - var ci = node["__cachedIndex"]; - var cl = pn["__cachedLength"]; - - // only handle cache building if we've gone out of sync - if(((typeof cl == "number")&&(cl != pnc.length))||(typeof ci != "number")){ - // rip though the whole set, building cache indexes as we go - pn["__cachedLength"] = pnc.length; - var idx = 1; - do{ - // we only assign indexes for nodes with nodeType == 1, as per: - // http://www.w3.org/TR/css3-selectors/#nth-child-pseudo - // only elements are counted in the search order, and they - // begin at 1 for the first child's index - - if(child === node){ - nidx = idx; - } - if(child.nodeType == 1){ - child["__cachedIndex"] = idx; - idx++; - } - child = child.nextSibling; - }while(child); - }else{ - // NOTE: - // could be incorrect in some cases (node swaps involving the - // passed node, etc.), but we ignore those due to the relative - // unlikelihood of that occuring - nidx = ci; - } - return nidx; - } - - var firedCount = 0; - - var blank = ""; - var _getAttr = function(elem, attr){ - if(attr == "class"){ - return elem.className || blank; - } - if(attr == "for"){ - return elem.htmlFor || blank; - } - return elem.getAttribute(attr, 2) || blank; - } - - var attrs = { - "*=": function(attr, value){ - return function(elem){ - // E[foo*="bar"] - // an E element whose "foo" attribute value contains - // the substring "bar" - return (_getAttr(elem, attr).indexOf(value)>=0); - } - }, - "^=": function(attr, value){ - // E[foo^="bar"] - // an E element whose "foo" attribute value begins exactly - // with the string "bar" - return function(elem){ - return (_getAttr(elem, attr).indexOf(value)==0); - } - }, - "$=": function(attr, value){ - // E[foo$="bar"] - // an E element whose "foo" attribute value ends exactly - // with the string "bar" - var tval = " "+value; - return function(elem){ - var ea = " "+_getAttr(elem, attr); - return (ea.lastIndexOf(value)==(ea.length-value.length)); - } - }, - "~=": function(attr, value){ - // E[foo~="bar"] - // an E element whose "foo" attribute value is a list of - // space-separated values, one of which is exactly equal - // to "bar" - - // return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]"; - var tval = " "+value+" "; - return function(elem){ - var ea = " "+_getAttr(elem, attr)+" "; - return (ea.indexOf(tval)>=0); - } - }, - "|=": function(attr, value){ - // E[hreflang|="en"] - // an E element whose "hreflang" attribute has a - // hyphen-separated list of values beginning (from the - // left) with "en" - var valueDash = " "+value+"-"; - return function(elem){ - var ea = " "+(elem.getAttribute(attr, 2) || ""); - return ( - (ea == value) || - (ea.indexOf(valueDash)==0) - ); - } - }, - "=": function(attr, value){ - return function(elem){ - return (_getAttr(elem, attr) == value); - } - } - }; - - var pseudos = { - "first-child": function(name, condition){ - return function(elem){ - if(elem.nodeType != 1){ return false; } - // check to see if any of the previous siblings are elements - var fc = elem.previousSibling; - while(fc && (fc.nodeType != 1)){ - fc = fc.previousSibling; - } - return (!fc); - } - }, - "last-child": function(name, condition){ - return function(elem){ - if(elem.nodeType != 1){ return false; } - // check to see if any of the next siblings are elements - var nc = elem.nextSibling; - while(nc && (nc.nodeType != 1)){ - nc = nc.nextSibling; - } - return (!nc); - } - }, - "empty": function(name, condition){ - return function(elem){ - // DomQuery and jQuery get this wrong, oddly enough. - // The CSS 3 selectors spec is pretty explicit about - // it, too. - var cn = elem.childNodes; - var cnl = elem.childNodes.length; - // if(!cnl){ return true; } - for(var x=cnl-1; x >= 0; x--){ - var nt = cn[x].nodeType; - if((nt == 1)||(nt == 3)){ return false; } - } - return true; - } - }, - "contains": function(name, condition){ - return function(elem){ - // FIXME: I dislike this version of "contains", as - // whimsical attribute could set it off. An inner-text - // based version might be more accurate, but since - // jQuery and DomQuery also potentially get this wrong, - // I'm leaving it for now. - return (elem.innerHTML.indexOf(condition) >= 0); - } - }, - "not": function(name, condition){ - var ntf = getFilterFunc(getQueryParts(condition)[0]); - return function(elem){ - return (!ntf(elem)); - } - }, - "nth-child": function(name, condition){ - var pi = parseInt; - if(condition == "odd"){ - return function(elem){ - return ( - ((getNodeIndex(elem)) % 2) == 1 - ); - } - }else if((condition == "2n")|| - (condition == "even")){ - return function(elem){ - return ((getNodeIndex(elem) % 2) == 0); - } - }else if(condition.indexOf("0n+") == 0){ - var ncount = pi(condition.substr(3)); - return function(elem){ - return (elem.parentNode[childNodesName][ncount-1] === elem); - } - }else if( (condition.indexOf("n+") > 0) && - (condition.length > 3) ){ - var tparts = condition.split("n+", 2); - var pred = pi(tparts[0]); - var idx = pi(tparts[1]); - return function(elem){ - return ((getNodeIndex(elem) % pred) == idx); - } - }else if(condition.indexOf("n") == -1){ - var ncount = pi(condition); - return function(elem){ - return (getNodeIndex(elem) == ncount); - } - } - } - }; - - var defaultGetter = (d.isIE) ? function(cond){ - var clc = cond.toLowerCase(); - return function(elem){ - return elem[cond]||elem[clc]; - } - } : function(cond){ - return function(elem){ - return (elem && elem.getAttribute && elem.hasAttribute(cond)); - } - }; - - var getSimpleFilterFunc = function(query){ - - var fcHit = (_simpleFiltersCache[query.query]||_filtersCache[query.query]); - if(fcHit){ return fcHit; } - - var ff = null; - - // the only case where we'll need the tag name is if we came from an ID query - if(query.id){ // do we have an ID component? - if(query.tag != "*"){ - ff = agree(ff, function(elem){ - return (elem.tagName.toLowerCase() == query.tag); - }); - } - } - - // if there's a class in our query, generate a match function for it - d.forEach(query.classes, function(cname, idx, arr){ - // get the class name - var isWildcard = cname.charAt(cname.length-1) == "*"; - if(isWildcard){ - cname = cname.substr(0, cname.length-1); - } - // I dislike the regex thing, even if memozied in a cache, but it's VERY short - var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)"); - ff = agree(ff, function(elem){ - return re.test(elem.className); - }); - ff.count = idx; - }); - - d.forEach(query.pseudos, function(pseudo){ - if(pseudos[pseudo.name]){ - ff = agree(ff, pseudos[pseudo.name](pseudo.name, pseudo.value)); - } - }); - - handleAttrs(attrs, query, defaultGetter, - function(tmatcher){ ff = agree(ff, tmatcher); } - ); - if(!ff){ - ff = function(){ return true; }; - } - return _simpleFiltersCache[query.query] = ff; - } - - var _getElementsFuncCache = { }; - - var getElementsFunc = function(query, root){ - var fHit = _getElementsFuncCache[query.query]; - if(fHit){ return fHit; } - - // NOTE: this function is in the fast path! not memoized!!! - - // the query doesn't contain any spaces, so there's only so many - // things it could be - - if(query.id && !query.hasLoops && !query.tag){ - // ID-only query. Easy. - return _getElementsFuncCache[query.query] = function(root){ - // FIXME: if root != document, check for parenting! - return [ d.byId(query.id) ]; - } - } - - var filterFunc = getSimpleFilterFunc(query); - - var retFunc; - if(query.tag && query.id && !query.hasLoops){ - // we got a filtered ID search (e.g., "h4#thinger") - retFunc = function(root){ - var te = d.byId(query.id); - if(filterFunc(te)){ - return [ te ]; - } - } - }else{ - var tret; - - if(!query.hasLoops){ - // it's just a plain-ol elements-by-tag-name query from the root - retFunc = function(root){ - var ret = []; - var te, x=0, tret = root.getElementsByTagName(query.tag); - while(te=tret[x++]){ - ret.push(te); - } - return ret; - } - }else{ - retFunc = function(root){ - var ret = []; - var te, x=0, tret = root.getElementsByTagName(query.tag); - while(te=tret[x++]){ - if(filterFunc(te)){ - ret.push(te); - } - } - return ret; - } - } - } - return _getElementsFuncCache[query.query] = retFunc; - } - - var _partsCache = {}; - - //////////////////////////////////////////////////////////////////////// - // the query runner - //////////////////////////////////////////////////////////////////////// - - // this is the second level of spliting, from full-length queries (e.g., - // "div.foo .bar") into simple query expressions (e.g., ["div.foo", - // ".bar"]) - var _queryFuncCache = { - "*": d.isIE ? - function(root){ - return root.all; - } : - function(root){ - return root.getElementsByTagName("*"); - }, - "~": _nextSiblings, - "+": function(root){ return _nextSiblings(root, true); }, - ">": _childElements - }; - - var getStepQueryFunc = function(query){ - // if it's trivial, get a fast-path dispatcher - var qparts = getQueryParts(d.trim(query)); - // if(query[query.length-1] == ">"){ query += " *"; } - if(qparts.length == 1){ - var tt = getElementsFunc(qparts[0]); - tt.nozip = true; - return tt; - } - - // otherwise, break it up and return a runner that iterates over the parts recursively - var sqf = function(root){ - var localQueryParts = qparts.slice(0); // clone the src arr - var candidates; - if(localQueryParts[0].oper == ">"){ // FIXME: what if it's + or ~? - candidates = [ root ]; - // root = document; - }else{ - candidates = getElementsFunc(localQueryParts.shift())(root); - } - return filterDown(candidates, localQueryParts); - } - return sqf; - } - - // a specialized method that implements our primoridal "query optimizer". - // This allows us to dispatch queries to the fastest subsystem we can get. - var _getQueryFunc = ( - // NOTE: - // XPath on the Webkit nighlies is slower than it's DOM iteration - // for most test cases - // FIXME: - // we should try to capture some runtime speed data for each query - // function to determine on the fly if we should stick w/ the - // potentially optimized variant or if we should try something - // new. - (document["evaluate"] && !d.isSafari) ? - function(query){ - // has xpath support that's faster than DOM - var qparts = query.split(" "); - // can we handle it? - if( (document["evaluate"])&& - (query.indexOf(":") == -1)&& - (query.indexOf("+") == -1) // skip direct sibling matches. See line ~344 - ){ - // dojo.debug(query); - // should we handle it? - - // kind of a lame heuristic, but it works - if( - // a "div div div" style query - ((qparts.length > 2)&&(query.indexOf(">") == -1))|| - // or something else with moderate complexity. kinda janky - (qparts.length > 3)|| - (query.indexOf("[")>=0)|| - // or if it's a ".thinger" query - ((1 == qparts.length)&&(0 <= query.indexOf("."))) - - ){ - // use get and cache a xpath runner for this selector - return getXPathFunc(query); - } - } - - // fallthrough - return getStepQueryFunc(query); - } : getStepQueryFunc - ); - // uncomment to disable XPath for testing and tuning the DOM path - // _getQueryFunc = getStepQueryFunc; - - // FIXME: we've got problems w/ the NodeList query()/filter() functions if we go XPath for everything - - // uncomment to disable DOM queries for testing and tuning XPath - // _getQueryFunc = getXPathFunc; - - // this is the primary caching for full-query results. The query dispatcher - // functions are generated here and then pickled for hash lookup in the - // future - var getQueryFunc = function(query){ - // return a cached version if one is available - var qcz = query.charAt(0); - if(d.doc["querySelectorAll"] && - ( (!d.isSafari) || (d.isSafari > 3.1) ) && // see #5832 - // as per CSS 3, we can't currently start w/ combinator: - // http://www.w3.org/TR/css3-selectors/#w3cselgrammar - (">+~".indexOf(qcz) == -1) - ){ - return function(root){ - var r = root.querySelectorAll(query); - r.nozip = true; // skip expensive duplication checks and just wrap in a NodeList - return r; - }; - } - if(_queryFuncCache[query]){ return _queryFuncCache[query]; } - if(0 > query.indexOf(",")){ - // if it's not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher - return _queryFuncCache[query] = _getQueryFunc(query); - }else{ - // if it's a complex query, break it up into it's constituent parts - // and return a dispatcher that will merge the parts when run - - // var parts = query.split(", "); - var parts = query.split(/\s*,\s*/); - var tf = function(root){ - var pindex = 0; // avoid array alloc for every invocation - var ret = []; - var tp; - while(tp = parts[pindex++]){ - ret = ret.concat(_getQueryFunc(tp, tp.indexOf(" "))(root)); - } - return ret; - } - // ...cache and return - return _queryFuncCache[query] = tf; - } - } - - // FIXME: - // Dean's Base2 uses a system whereby queries themselves note if - // they'll need duplicate filtering. We need to get on that plan!! - - // attempt to efficiently determine if an item in a list is a dupe, - // returning a list of "uniques", hopefully in doucment order - var _zipIdx = 0; - var _zip = function(arr){ - if(arr && arr.nozip){ return d.NodeList._wrap(arr); } - var ret = new d.NodeList(); - if(!arr){ return ret; } - if(arr[0]){ - ret.push(arr[0]); - } - if(arr.length < 2){ return ret; } - _zipIdx++; - arr[0]["_zipIdx"] = _zipIdx; - for(var x=1, te; te = arr[x]; x++){ - if(arr[x]["_zipIdx"] != _zipIdx){ - ret.push(te); - } - te["_zipIdx"] = _zipIdx; - } - // FIXME: should we consider stripping these properties? - return ret; - } - - // the main executor - d.query = function(/*String*/ query, /*String|DOMNode?*/ root){ - // summary: - // Returns nodes which match the given CSS3 selector, searching the - // entire document by default but optionally taking a node to scope - // the search by. Returns an instance of dojo.NodeList. - // description: - // dojo.query() is the swiss army knife of DOM node manipulation in - // Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's - // "$" function, dojo.query provides robust, high-performance - // CSS-based node selector support with the option of scoping searches - // to a particular sub-tree of a document. - // - // Supported Selectors: - // -------------------- - // - // dojo.query() supports a rich set of CSS3 selectors, including: - // - // * class selectors (e.g., `.foo`) - // * node type selectors like `span` - // * ` ` descendant selectors - // * `>` child element selectors - // * `#foo` style ID selectors - // * `*` universal selector - // * `~`, the immediately preceeded-by sibling selector - // * `+`, the preceeded-by sibling selector - // * attribute queries: - // | * `[foo]` attribute presence selector - // | * `[foo='bar']` attribute value exact match - // | * `[foo~='bar']` attribute value list item match - // | * `[foo^='bar']` attribute start match - // | * `[foo$='bar']` attribute end match - // | * `[foo*='bar']` attribute substring match - // * `:first-child`, `:last-child` positional selectors - // * `:empty` content emtpy selector - // * `:empty` content emtpy selector - // * `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations - // * `:nth-child(even)`, `:nth-child(odd)` positional selectors - // * `:not(...)` negation pseudo selectors - // - // Any legal combination of these selectors will work with - // `dojo.query()`, including compound selectors ("," delimited). - // Very complex and useful searches can be constructed with this - // palette of selectors and when combined with functions for - // maniplation presented by dojo.NodeList, many types of DOM - // manipulation operations become very straightforward. - // - // Unsupported Selectors: - // ---------------------- - // - // While dojo.query handles many CSS3 selectors, some fall outside of - // what's resaonable for a programmatic node querying engine to - // handle. Currently unsupported selectors include: - // - // * namespace-differentiated selectors of any form - // * all `::` pseduo-element selectors - // * certain pseduo-selectors which don't get a lot of day-to-day use: - // | * `:root`, `:lang()`, `:target`, `:focus` - // * all visual and state selectors: - // | * `:root`, `:active`, `:hover`, `:visisted`, `:link`, - // `:enabled`, `:disabled`, `:checked` - // * `:*-of-type` pseudo selectors - // - // dojo.query and XML Documents: - // ----------------------------- - // - // `dojo.query` currently only supports searching XML documents - // whose tags and attributes are 100% lower-case. This is a known - // limitation and will [be addressed soon](http://trac.dojotoolkit.org/ticket/3866) - // Non-selector Queries: - // --------------------- - // - // If something other than a String is passed for the query, - // `dojo.query` will return a new `dojo.NodeList` constructed from - // that parameter alone and all further processing will stop. This - // means that if you have a reference to a node or NodeList, you - // can quickly construct a new NodeList from the original by - // calling `dojo.query(node)` or `dojo.query(list)`. - // - // query: - // The CSS3 expression to match against. For details on the syntax of - // CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors> - // root: - // A DOMNode (or node id) to scope the search from. Optional. - // returns: dojo.NodeList - // An instance of `dojo.NodeList`. Many methods are available on - // NodeLists for searching, iterating, manipulating, and handling - // events on the matched nodes in the returned list. - // example: - // search the entire document for elements with the class "foo": - // | dojo.query(".foo"); - // these elements will match: - // | <span class="foo"></span> - // | <span class="foo bar"></span> - // | <p class="thud foo"></p> - // example: - // search the entire document for elements with the classes "foo" *and* "bar": - // | dojo.query(".foo.bar"); - // these elements will match: - // | <span class="foo bar"></span> - // while these will not: - // | <span class="foo"></span> - // | <p class="thud foo"></p> - // example: - // find `<span>` elements which are descendants of paragraphs and - // which have a "highlighted" class: - // | dojo.query("p span.highlighted"); - // the innermost span in this fragment matches: - // | <p class="foo"> - // | <span>... - // | <span class="highlighted foo bar">...</span> - // | </span> - // | </p> - // example: - // set an "odd" class on all odd table rows inside of the table - // `#tabular_data`, using the `>` (direct child) selector to avoid - // affecting any nested tables: - // | dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd"); - // example: - // remove all elements with the class "error" from the document - // and store them in a list: - // | var errors = dojo.query(".error").orphan(); - // example: - // add an onclick handler to every submit button in the document - // which causes the form to be sent via Ajax instead: - // | dojo.query("input[type='submit']").onclick(function(e){ - // | dojo.stopEvent(e); // prevent sending the form - // | var btn = e.target; - // | dojo.xhrPost({ - // | form: btn.form, - // | load: function(data){ - // | // replace the form with the response - // | var div = dojo.doc.createElement("div"); - // | dojo.place(div, btn.form, "after"); - // | div.innerHTML = data; - // | dojo.style(btn.form, "display", "none"); - // | } - // | }); - // | }); - - - // NOTE: elementsById is not currently supported - // NOTE: ignores xpath-ish queries for now - - if(query.constructor == d.NodeList){ - return query; - } - if(!d.isString(query)){ - return new d.NodeList(query); // dojo.NodeList - } - if(d.isString(root)){ - root = d.byId(root); - } - - return _zip(getQueryFunc(query)(root||d.doc)); // dojo.NodeList - } - - /* - // exposing this was a mistake - d.query.attrs = attrs; - */ - // exposing this because new pseudo matches are only executed through the - // DOM query path (never through the xpath optimizing branch) - d.query.pseudos = pseudos; - - // one-off function for filtering a NodeList based on a simple selector - d._filterQueryResult = function(nodeList, simpleFilter){ - var tnl = new d.NodeList(); - var ff = (simpleFilter) ? getFilterFunc(getQueryParts(simpleFilter)[0]) : function(){ return true; }; - for(var x=0, te; te = nodeList[x]; x++){ - if(ff(te)){ tnl.push(te); } - } - return tnl; - } -})(); - -} |