source: Dev/branches/rest-dojo-ui/client/dojo/selector/lite.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 8.2 KB
Line 
1define(["../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
6var testDiv = document.createElement("div");
7var matchesSelector = testDiv.matchesSelector || testDiv.webkitMatchesSelector || testDiv.mozMatchesSelector || testDiv.msMatchesSelector || testDiv.oMatchesSelector; // IE9, WebKit, Firefox have this, but not Opera yet
8var querySelectorAll = testDiv.querySelectorAll;
9has.add("dom-matches-selector", !!matchesSelector);
10has.add("dom-qsa", !!querySelectorAll);
11
12// this is a simple query engine. It has handles basic selectors, and for simple
13// common selectors is extremely fast
14var 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};
89var 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
97        if(relativeHierarchySelector && !hasParent){
98                return [];
99        }
100        if(!old){
101                context.setAttribute("id", nid);
102        }else{
103                nid = nid.replace(/'/g, "\\$&");
104        }
105        if(relativeHierarchySelector && hasParent){
106                context = context.parentNode;
107        }
108
109        try {
110                return method.call(context, "[id='" + nid + "'] " + query );
111        } finally {
112                if ( !old ) {
113                        oldContext.removeAttribute( "id" );
114                }
115        }
116};
117
118if(!has("dom-matches-selector")){
119        var jsMatchesSelector = (function(){
120                // a JS implementation of CSS selector matching, first we start with the various handlers
121                var caseFix = testDiv.tagName == "div" ? "toLowerCase" : "toUpperCase";
122                function tag(tagName){
123                        tagName = tagName[caseFix]();
124                        return function(node){
125                                return node.tagName == tagName;
126                        }
127                }
128                function className(className){
129                        var classNameSpaced = ' ' + className + ' ';
130                        return function(node){
131                                return node.className.indexOf(className) > -1 && (' ' + node.className + ' ').indexOf(classNameSpaced) > -1;
132                        }
133                }
134                var attrComparators = {
135                        "^=": function(attrValue, value){
136                                return attrValue.indexOf(value) == 0;
137                        },
138                        "*=": function(attrValue, value){
139                                return attrValue.indexOf(value) > -1;
140                        },
141                        "$=": function(attrValue, value){
142                                return attrValue.substring(attrValue.length - value.length, attrValue.length) == value;
143                        },
144                        "~=": function(attrValue, value){
145                                return (' ' + attrValue + ' ').indexOf(' ' + value + ' ') > -1;
146                        },
147                        "|=": function(attrValue, value){
148                                return (attrValue + '-').indexOf(value + '-') == 0;
149                        },
150                        "=": function(attrValue, value){
151                                return attrValue == value;
152                        },
153                        "": function(attrValue, value){
154                                return true;
155                        }
156                };
157                function attr(name, value, type){
158                        if(value.match(/['"]/)){
159                                // it is quoted, do an eval to parse the string (CSS and JS parsing are close enough)
160                                value = eval(value);
161                        }
162                        var comparator = attrComparators[type || ""];
163                        return function(node){
164                                var attrValue = node.getAttribute(name);
165                                return attrValue && comparator(attrValue, value);
166                        }
167                }
168                function ancestor(matcher){
169                        return function(node, root){
170                                while((node = node.parentNode) != root){
171                                        if(matcher(node, root)){
172                                                return true;
173                                        }
174                                }
175                        };
176                }
177                function parent(matcher){
178                        return function(node, root){
179                                node = node.parentNode;
180                                return matcher ?
181                                        node != root && matcher(node, root)
182                                        : node == root;
183                        };
184                }
185                var cache = {};
186                function and(matcher, next){
187                        return matcher ?
188                                function(node, root){
189                                        return next(node) && matcher(node, root);
190                                }
191                                : next;
192                }
193                return function(node, selector, root){
194                        // this returns true or false based on if the node matches the selector (optionally within the given root)
195                        var matcher = cache[selector]; // check to see if we have created a matcher function for the given selector
196                        if(!matcher){
197                                // create a matcher function for the given selector
198                                // parse the selectors
199                                if(selector.replace(/(?:\s*([> ])\s*)|(\.)?([\w-]+)|\[([\w-]+)\s*(.?=)?\s*([^\]]*)\]/g, function(t, combinator, type, value, attrName, attrType, attrValue){
200                                        if(value){
201                                                if(type == "."){
202                                                        matcher = and(matcher, className(value));
203                                                }
204                                                else{
205                                                        matcher = and(matcher, tag(value));
206                                                }
207                                        }
208                                        else if(combinator){
209                                                matcher = (combinator == " " ? ancestor : parent)(matcher);
210                                        }
211                                        else if(attrName){
212                                                matcher = and(matcher, attr(attrName, attrValue, attrType));
213                                        }
214                                        return "";
215                                })){
216                                        throw new Error("Syntax error in query");
217                                }
218                                if(!matcher){
219                                        return true;
220                                }
221                                cache[selector] = matcher;
222                        }
223                        // now run the matcher function on the node
224                        return matcher(node, root);
225                };
226        })();
227}
228if(!has("dom-qsa")){
229        var combine = function(selector, root){
230                // combined queries
231                selector = selector.split(/\s*,\s*/);
232                var indexed = [];
233                // add all results and keep unique ones, this only runs in IE, so we take advantage
234                // of known IE features, particularly sourceIndex which is unique and allows us to
235                // order the results
236                for(var i = 0; i < selector.length; i++){
237                        var results = liteEngine(selector[i], root);
238                        for(var j = 0, l = results.length; j < l; j++){
239                                var node = results[j];
240                                indexed[node.sourceIndex] = node;
241                        }
242                }
243                // now convert from a sparse array to a dense array
244                var totalResults = [];
245                for(i in indexed){
246                        totalResults.push(indexed[i]);
247                }
248                return totalResults;
249        };
250}
251
252liteEngine.match = matchesSelector ? function(node, selector, root){
253        if(root){
254                // doesn't support three args, use rooted id trick
255                return useRoot(root, selector, function(query){
256                        return matchesSelector.call(node, query);
257                });
258        }
259        // we have a native matchesSelector, use that
260        return matchesSelector.call(node, selector);
261} : jsMatchesSelector; // otherwise use the JS matches impl
262
263return liteEngine;
264});
Note: See TracBrowser for help on using the repository browser.