source: Dev/trunk/src/client/dojo/selector/lite.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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