lite.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. define("dojo/selector/lite", ["../has", "../_base/kernel"], function(has, dojo){
  2. "use strict";
  3. // summary:
  4. // A small lightweight query selector engine that implements CSS2.1 selectors
  5. // minus pseudo-classes and the sibling combinator, plus CSS3 attribute selectors
  6. var testDiv = document.createElement("div");
  7. var matchesSelector = testDiv.matchesSelector || testDiv.webkitMatchesSelector || testDiv.mozMatchesSelector || testDiv.msMatchesSelector || testDiv.oMatchesSelector; // IE9, WebKit, Firefox have this, but not Opera yet
  8. var querySelectorAll = testDiv.querySelectorAll;
  9. var unionSplit = /([^\s,](?:"(?:\\.|[^"])+"|'(?:\\.|[^'])+'|[^,])*)/g;
  10. has.add("dom-matches-selector", !!matchesSelector);
  11. has.add("dom-qsa", !!querySelectorAll);
  12. // this is a simple query engine. It has handles basic selectors, and for simple
  13. // common selectors is extremely fast
  14. var liteEngine = function(selector, root){
  15. if(combine && selector.indexOf(',') > -1){
  16. return combine(selector, root);
  17. }
  18. var match = (querySelectorAll ?
  19. /^([\w]*)#([\w\-]+$)|^(\.)([\w\-\*]+$)|^(\w+$)/ : // this one only matches on simple queries where we can beat qSA with specific methods
  20. /^([\w]*)#([\w\-]+)(?:\s+(.*))?$|(?:^|(>|.+\s+))([\w\-\*]+)(\S*$)/) // this one matches parts of the query that we can use to speed up manual filtering
  21. .exec(selector);
  22. root = root || document;
  23. if(match){
  24. // fast path regardless of whether or not querySelectorAll exists
  25. if(match[2]){
  26. // an #id
  27. // use dojo.byId if available as it fixes the id retrieval in IE
  28. var found = dojo.byId ? dojo.byId(match[2]) : document.getElementById(match[2]);
  29. if(!found || (match[1] && match[1] != found.tagName.toLowerCase())){
  30. // if there is a tag qualifer and it doesn't match, no matches
  31. return [];
  32. }
  33. if(root != document){
  34. // there is a root element, make sure we are a child of it
  35. var parent = found;
  36. while(parent != root){
  37. parent = parent.parentNode;
  38. if(!parent){
  39. return [];
  40. }
  41. }
  42. }
  43. return match[3] ?
  44. liteEngine(match[3], found)
  45. : [found];
  46. }
  47. if(match[3] && root.getElementsByClassName){
  48. // a .class
  49. return root.getElementsByClassName(match[4]);
  50. }
  51. var found;
  52. if(match[5]){
  53. // a tag
  54. found = root.getElementsByTagName(match[5]);
  55. if(match[4] || match[6]){
  56. selector = (match[4] || "") + match[6];
  57. }else{
  58. // that was the entirety of the query, return results
  59. return found;
  60. }
  61. }
  62. }
  63. if(querySelectorAll){
  64. // qSA works strangely on Element-rooted queries
  65. // We can work around this by specifying an extra ID on the root
  66. // and working up from there (Thanks to Andrew Dupont for the technique)
  67. // IE 8 doesn't work on object elements
  68. if (root.nodeType === 1 && root.nodeName.toLowerCase() !== "object"){
  69. return useRoot(root, selector, root.querySelectorAll);
  70. }else{
  71. // we can use the native qSA
  72. return root.querySelectorAll(selector);
  73. }
  74. }else if(!found){
  75. // search all children and then filter
  76. found = root.getElementsByTagName("*");
  77. }
  78. // now we filter the nodes that were found using the matchesSelector
  79. var results = [];
  80. for(var i = 0, l = found.length; i < l; i++){
  81. var node = found[i];
  82. if(node.nodeType == 1 && jsMatchesSelector(node, selector, root)){
  83. // keep the nodes that match the selector
  84. results.push(node);
  85. }
  86. }
  87. return results;
  88. };
  89. var useRoot = function(context, query, method){
  90. // this function creates a temporary id so we can do rooted qSA queries, this is taken from sizzle
  91. var oldContext = context,
  92. old = context.getAttribute("id"),
  93. nid = old || "__dojo__",
  94. hasParent = context.parentNode,
  95. relativeHierarchySelector = /^\s*[+~]/.test(query);
  96. if(relativeHierarchySelector && !hasParent){
  97. return [];
  98. }
  99. if(!old){
  100. context.setAttribute("id", nid);
  101. }else{
  102. nid = nid.replace(/'/g, "\\$&");
  103. }
  104. if(relativeHierarchySelector && hasParent){
  105. context = context.parentNode;
  106. }
  107. var selectors = query.match(unionSplit);
  108. for(var i = 0; i < selectors.length; i++){
  109. selectors[i] = "[id='" + nid + "'] " + selectors[i];
  110. }
  111. query = selectors.join(",");
  112. try{
  113. return method.call(context, query);
  114. }finally{
  115. if(!old){
  116. oldContext.removeAttribute("id");
  117. }
  118. }
  119. };
  120. if(!has("dom-matches-selector")){
  121. var jsMatchesSelector = (function(){
  122. // a JS implementation of CSS selector matching, first we start with the various handlers
  123. var caseFix = testDiv.tagName == "div" ? "toLowerCase" : "toUpperCase";
  124. var selectorTypes = {
  125. "": function(tagName){
  126. tagName = tagName[caseFix]();
  127. return function(node){
  128. return node.tagName == tagName;
  129. };
  130. },
  131. ".": function(className){
  132. var classNameSpaced = ' ' + className + ' ';
  133. return function(node){
  134. return node.className.indexOf(className) > -1 && (' ' + node.className + ' ').indexOf(classNameSpaced) > -1;
  135. };
  136. },
  137. "#": function(id){
  138. return function(node){
  139. return node.id == id;
  140. };
  141. }
  142. };
  143. var attrComparators = {
  144. "^=": function(attrValue, value){
  145. return attrValue.indexOf(value) == 0;
  146. },
  147. "*=": function(attrValue, value){
  148. return attrValue.indexOf(value) > -1;
  149. },
  150. "$=": function(attrValue, value){
  151. return attrValue.substring(attrValue.length - value.length, attrValue.length) == value;
  152. },
  153. "~=": function(attrValue, value){
  154. return (' ' + attrValue + ' ').indexOf(' ' + value + ' ') > -1;
  155. },
  156. "|=": function(attrValue, value){
  157. return (attrValue + '-').indexOf(value + '-') == 0;
  158. },
  159. "=": function(attrValue, value){
  160. return attrValue == value;
  161. },
  162. "": function(attrValue, value){
  163. return true;
  164. }
  165. };
  166. function attr(name, value, type){
  167. var firstChar = value.charAt(0);
  168. if(firstChar == '"' || firstChar == "'"){
  169. // it is quoted, remove the quotes
  170. value = value.slice(1, -1);
  171. }
  172. value = value.replace(/\\/g,'');
  173. var comparator = attrComparators[type || ""];
  174. return function(node){
  175. var attrValue = node.getAttribute(name);
  176. return attrValue && comparator(attrValue, value);
  177. }
  178. }
  179. function ancestor(matcher){
  180. return function(node, root){
  181. while((node = node.parentNode) != root){
  182. if(matcher(node, root)){
  183. return true;
  184. }
  185. }
  186. };
  187. }
  188. function parent(matcher){
  189. return function(node, root){
  190. node = node.parentNode;
  191. return matcher ?
  192. node != root && matcher(node, root)
  193. : node == root;
  194. };
  195. }
  196. var cache = {};
  197. function and(matcher, next){
  198. return matcher ?
  199. function(node, root){
  200. return next(node) && matcher(node, root);
  201. }
  202. : next;
  203. }
  204. return function(node, selector, root){
  205. // this returns true or false based on if the node matches the selector (optionally within the given root)
  206. var matcher = cache[selector]; // check to see if we have created a matcher function for the given selector
  207. if(!matcher){
  208. // create a matcher function for the given selector
  209. // parse the selectors
  210. if(selector.replace(/(?:\s*([> ])\s*)|(#|\.)?((?:\\.|[\w-])+)|\[\s*([\w-]+)\s*(.?=)?\s*("(?:\\.|[^"])+"|'(?:\\.|[^'])+'|(?:\\.|[^\]])*)\s*\]/g, function(t, combinator, type, value, attrName, attrType, attrValue){
  211. if(value){
  212. matcher = and(matcher, selectorTypes[type || ""](value.replace(/\\/g, '')));
  213. }
  214. else if(combinator){
  215. matcher = (combinator == " " ? ancestor : parent)(matcher);
  216. }
  217. else if(attrName){
  218. matcher = and(matcher, attr(attrName, attrValue, attrType));
  219. }
  220. return "";
  221. })){
  222. throw new Error("Syntax error in query");
  223. }
  224. if(!matcher){
  225. return true;
  226. }
  227. cache[selector] = matcher;
  228. }
  229. // now run the matcher function on the node
  230. return matcher(node, root);
  231. };
  232. })();
  233. }
  234. if(!has("dom-qsa")){
  235. var combine = function(selector, root){
  236. // combined queries
  237. var selectors = selector.match(unionSplit);
  238. var indexed = [];
  239. // add all results and keep unique ones, this only runs in IE, so we take advantage
  240. // of known IE features, particularly sourceIndex which is unique and allows us to
  241. // order the results
  242. for(var i = 0; i < selectors.length; i++){
  243. selector = new String(selectors[i].replace(/\s*$/,''));
  244. selector.indexOf = escape; // keep it from recursively entering combine
  245. var results = liteEngine(selector, root);
  246. for(var j = 0, l = results.length; j < l; j++){
  247. var node = results[j];
  248. indexed[node.sourceIndex] = node;
  249. }
  250. }
  251. // now convert from a sparse array to a dense array
  252. var totalResults = [];
  253. for(i in indexed){
  254. totalResults.push(indexed[i]);
  255. }
  256. return totalResults;
  257. };
  258. }
  259. liteEngine.match = matchesSelector ? function(node, selector, root){
  260. if(root){
  261. // doesn't support three args, use rooted id trick
  262. return useRoot(root, selector, function(query){
  263. return matchesSelector.call(node, query);
  264. });
  265. }
  266. // we have a native matchesSelector, use that
  267. return matchesSelector.call(node, selector);
  268. } : jsMatchesSelector; // otherwise use the JS matches impl
  269. return liteEngine;
  270. });