lite.js 9.2 KB

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