MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / selector-native / selector-native.js
blobb626cb7cb7b29b7c39619169563c97574a67f3dc
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('selector-native', function(Y) {
9 (function(Y) {
10 /**
11  * The selector-native module provides support for native querySelector
12  * @module dom
13  * @submodule selector-native
14  * @for Selector
15  */
17 /**
18  * Provides support for using CSS selectors to query the DOM 
19  * @class Selector 
20  * @static
21  * @for Selector
22  */
24 Y.namespace('Selector'); // allow native module to standalone
26 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
27     OWNER_DOCUMENT = 'ownerDocument';
29 var Selector = {
30     _types: {
31         esc: {
32             token: '\uE000',
33             re: /\\[:\[\]\(\)#\.\'\>+~"]/gi
34         },
36         attr: {
37             token: '\uE001',
38             re: /(\[[^\]]*\])/g
39         },
41         pseudo: {
42             token: '\uE002',
43             re: /(\([^\)]*\))/g
44         }
45     },
47     useNative: true,
49     _escapeId: function(id) {
50         if (id) {
51             id = id.replace(/([:\[\]\(\)#\.'<>+~"])/g,'\\$1');
52         }
53         return id;
54     },
56     _compare: ('sourceIndex' in Y.config.doc.documentElement) ?
57         function(nodeA, nodeB) {
58             var a = nodeA.sourceIndex,
59                 b = nodeB.sourceIndex;
61             if (a === b) {
62                 return 0;
63             } else if (a > b) {
64                 return 1;
65             }
67             return -1;
69         } : (Y.config.doc.documentElement[COMPARE_DOCUMENT_POSITION] ?
70         function(nodeA, nodeB) {
71             if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
72                 return -1;
73             } else {
74                 return 1;
75             }
76         } :
77         function(nodeA, nodeB) {
78             var rangeA, rangeB, compare;
79             if (nodeA && nodeB) {
80                 rangeA = nodeA[OWNER_DOCUMENT].createRange();
81                 rangeA.setStart(nodeA, 0);
82                 rangeB = nodeB[OWNER_DOCUMENT].createRange();
83                 rangeB.setStart(nodeB, 0);
84                 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
85             }
87             return compare;
88         
89     }),
91     _sort: function(nodes) {
92         if (nodes) {
93             nodes = Y.Array(nodes, 0, true);
94             if (nodes.sort) {
95                 nodes.sort(Selector._compare);
96             }
97         }
99         return nodes;
100     },
102     _deDupe: function(nodes) {
103         var ret = [],
104             i, node;
106         for (i = 0; (node = nodes[i++]);) {
107             if (!node._found) {
108                 ret[ret.length] = node;
109                 node._found = true;
110             }
111         }
113         for (i = 0; (node = ret[i++]);) {
114             node._found = null;
115             node.removeAttribute('_found');
116         }
118         return ret;
119     },
121     /**
122      * Retrieves a set of nodes based on a given CSS selector. 
123      * @method query
124      *
125      * @param {string} selector The CSS Selector to test the node against.
126      * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
127      * @param {Boolean} firstOnly optional Whether or not to return only the first match.
128      * @return {Array} An array of nodes that match the given selector.
129      * @static
130      */
131     query: function(selector, root, firstOnly, skipNative) {
132         root = root || Y.config.doc;
133         var ret = [],
134             useNative = (Y.Selector.useNative && Y.config.doc.querySelector && !skipNative),
135             queries = [[selector, root]],
136             query,
137             result,
138             i,
139             fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery;
141         if (selector && fn) {
142             // split group into seperate queries
143             if (!skipNative && // already done if skipping
144                     (!useNative || root.tagName)) { // split native when element scoping is needed
145                 queries = Selector._splitQueries(selector, root);
146             }
148             for (i = 0; (query = queries[i++]);) {
149                 result = fn(query[0], query[1], firstOnly);
150                 if (!firstOnly) { // coerce DOM Collection to Array
151                     result = Y.Array(result, 0, true);
152                 }
153                 if (result) {
154                     ret = ret.concat(result);
155                 }
156             }
158             if (queries.length > 1) { // remove dupes and sort by doc order 
159                 ret = Selector._sort(Selector._deDupe(ret));
160             }
161         }
163         return (firstOnly) ? (ret[0] || null) : ret;
165     },
167     _replaceSelector: function(selector) {
168         var esc = Y.Selector._parse('esc', selector), // pull escaped colon, brackets, etc. 
169             attrs,
170             pseudos;
172         // first replace escaped chars, which could be present in attrs or pseudos
173         selector = Y.Selector._replace('esc', selector);
175         // then replace pseudos before attrs to avoid replacing :not([foo])
176         pseudos = Y.Selector._parse('pseudo', selector);
177         selector = Selector._replace('pseudo', selector);
179         attrs = Y.Selector._parse('attr', selector);
180         selector = Y.Selector._replace('attr', selector);
182         return {
183             esc: esc,
184             attrs: attrs,
185             pseudos: pseudos,
186             selector: selector
187         }
188     },
190     _restoreSelector: function(replaced) {
191         var selector = replaced.selector;
192         selector = Y.Selector._restore('attr', selector, replaced.attrs);
193         selector = Y.Selector._restore('pseudo', selector, replaced.pseudos);
194         selector = Y.Selector._restore('esc', selector, replaced.esc);
195         return selector;
196     },
198     _replaceCommas: function(selector) {
199         var replaced = Y.Selector._replaceSelector(selector),
200             selector = replaced.selector;
202         if (selector) {
203             selector = selector.replace(/,/g, '\uE007');
204             replaced.selector = selector;
205             selector = Y.Selector._restoreSelector(replaced);
206         }
207         return selector;
208     },
210     // allows element scoped queries to begin with combinator
211     // e.g. query('> p', document.body) === query('body > p')
212     _splitQueries: function(selector, node) {
213         if (selector.indexOf(',') > -1) {
214             selector = Y.Selector._replaceCommas(selector);
215         }
217         var groups = selector.split('\uE007'), // split on replaced comma token
218             queries = [],
219             prefix = '',
220             id,
221             i,
222             len;
224         if (node) {
225             // enforce for element scoping
226             if (node.nodeType === 1) { // Elements only
227                 id = Y.Selector._escapeId(Y.DOM.getId(node));
229                 if (!id) {
230                     id = Y.guid();
231                     Y.DOM.setId(node, id);
232                 }
233             
234                 prefix = '[id="' + id + '"] ';
235             }
237             for (i = 0, len = groups.length; i < len; ++i) {
238                 selector =  prefix + groups[i];
239                 queries.push([selector, node]);
240             }
241         }
243         return queries;
244     },
246     _nativeQuery: function(selector, root, one) {
247         if (Y.UA.webkit && selector.indexOf(':checked') > -1 &&
248                 (Y.Selector.pseudos && Y.Selector.pseudos.checked)) { // webkit (chrome, safari) fails to pick up "selected"  with "checked"
249             return Y.Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
250         }
251         try {
252             return root['querySelector' + (one ? '' : 'All')](selector);
253         } catch(e) { // fallback to brute if available
254             return Y.Selector.query(selector, root, one, true); // redo with skipNative true
255         }
256     },
258     filter: function(nodes, selector) {
259         var ret = [],
260             i, node;
262         if (nodes && selector) {
263             for (i = 0; (node = nodes[i++]);) {
264                 if (Y.Selector.test(node, selector)) {
265                     ret[ret.length] = node;
266                 }
267             }
268         } else {
269         }
271         return ret;
272     },
274     test: function(node, selector, root) {
275         var ret = false,
276             useFrag = false,
277             groups,
278             parent,
279             item,
280             items,
281             frag,
282             id,
283             i, j, group;
285         if (node && node.tagName) { // only test HTMLElements
287             if (typeof selector == 'function') { // test with function
288                 ret = selector.call(node, node);
289             } else { // test with query
290                 // we need a root if off-doc
291                 groups = selector.split(',');
292                 if (!root && !Y.DOM.inDoc(node)) {
293                     parent = node.parentNode;
294                     if (parent) { 
295                         root = parent;
296                     } else { // only use frag when no parent to query
297                         frag = node[OWNER_DOCUMENT].createDocumentFragment();
298                         frag.appendChild(node);
299                         root = frag;
300                         useFrag = true;
301                     }
302                 }
303                 root = root || node[OWNER_DOCUMENT];
305                 id = Y.Selector._escapeId(Y.DOM.getId(node));
306                 if (!id) {
307                     id = Y.guid();
308                     Y.DOM.setId(node, id);
309                 }
311                 for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
312                     group += '[id="' + id + '"]';
313                     items = Y.Selector.query(group, root);
315                     for (j = 0; item = items[j++];) {
316                         if (item === node) {
317                             ret = true;
318                             break;
319                         }
320                     }
321                     if (ret) {
322                         break;
323                     }
324                 }
326                 if (useFrag) { // cleanup
327                     frag.removeChild(node);
328                 }
329             };
330         }
332         return ret;
333     },
335     /**
336      * A convenience function to emulate Y.Node's aNode.ancestor(selector).
337      * @param {HTMLElement} element An HTMLElement to start the query from.
338      * @param {String} selector The CSS selector to test the node against.
339      * @return {HTMLElement} The ancestor node matching the selector, or null.
340      * @param {Boolean} testSelf optional Whether or not to include the element in the scan 
341      * @static
342      * @method ancestor
343      */
344     ancestor: function (element, selector, testSelf) {
345         return Y.DOM.ancestor(element, function(n) {
346             return Y.Selector.test(n, selector);
347         }, testSelf);
348     },
350     _parse: function(name, selector) {
351         return selector.match(Y.Selector._types[name].re);
352     },
354     _replace: function(name, selector) {
355         var o = Y.Selector._types[name];
356         return selector.replace(o.re, o.token);
357     },
359     _restore: function(name, selector, items) {
360         if (items) {
361             var token = Y.Selector._types[name].token,
362                 i, len;
363             for (i = 0, len = items.length; i < len; ++i) {
364                 selector = selector.replace(token, items[i]);
365             }
366         }
367         return selector;
368     }
371 Y.mix(Y.Selector, Selector, true);
373 })(Y);
376 }, '3.5.1' ,{requires:['dom-base']});