acme.js 49 KB

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