2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('selector-native', function(Y) {
12 * The selector-native module provides support for native querySelector
14 * @submodule selector-native
19 * Provides support for using CSS selectors to query the DOM
25 Y.namespace('Selector'); // allow native module to standalone
27 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
28 OWNER_DOCUMENT = 'ownerDocument';
35 _compare: ('sourceIndex' in Y.config.doc.documentElement) ?
36 function(nodeA, nodeB) {
37 var a = nodeA.sourceIndex,
38 b = nodeB.sourceIndex;
48 } : (Y.config.doc.documentElement[COMPARE_DOCUMENT_POSITION] ?
49 function(nodeA, nodeB) {
50 if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
56 function(nodeA, nodeB) {
57 var rangeA, rangeB, compare;
59 rangeA = nodeA[OWNER_DOCUMENT].createRange();
60 rangeA.setStart(nodeA, 0);
61 rangeB = nodeB[OWNER_DOCUMENT].createRange();
62 rangeB.setStart(nodeB, 0);
63 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
70 _sort: function(nodes) {
72 nodes = Y.Array(nodes, 0, true);
74 nodes.sort(Selector._compare);
81 _deDupe: function(nodes) {
85 for (i = 0; (node = nodes[i++]);) {
87 ret[ret.length] = node;
92 for (i = 0; (node = ret[i++]);) {
94 node.removeAttribute('_found');
101 * Retrieves a set of nodes based on a given CSS selector.
104 * @param {string} selector The CSS Selector to test the node against.
105 * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
106 * @param {Boolean} firstOnly optional Whether or not to return only the first match.
107 * @return {Array} An array of nodes that match the given selector.
110 query: function(selector, root, firstOnly, skipNative) {
111 root = root || Y.config.doc;
113 useNative = (Y.Selector.useNative && Y.config.doc.querySelector && !skipNative),
114 queries = [[selector, root]],
118 fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery;
120 if (selector && fn) {
121 // split group into seperate queries
122 if (!skipNative && // already done if skipping
123 (!useNative || root.tagName)) { // split native when element scoping is needed
124 queries = Selector._splitQueries(selector, root);
127 for (i = 0; (query = queries[i++]);) {
128 result = fn(query[0], query[1], firstOnly);
129 if (!firstOnly) { // coerce DOM Collection to Array
130 result = Y.Array(result, 0, true);
133 ret = ret.concat(result);
137 if (queries.length > 1) { // remove dupes and sort by doc order
138 ret = Selector._sort(Selector._deDupe(ret));
142 return (firstOnly) ? (ret[0] || null) : ret;
146 // allows element scoped queries to begin with combinator
147 // e.g. query('> p', document.body) === query('body > p')
148 _splitQueries: function(selector, node) {
149 var groups = selector.split(','),
155 // enforce for element scoping
157 node.id = node.id || Y.guid();
158 prefix = '[id="' + node.id + '"] ';
161 for (i = 0, len = groups.length; i < len; ++i) {
162 selector = prefix + groups[i];
163 queries.push([selector, node]);
170 _nativeQuery: function(selector, root, one) {
171 if (Y.UA.webkit && selector.indexOf(':checked') > -1 &&
172 (Y.Selector.pseudos && Y.Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
173 return Y.Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
176 return root['querySelector' + (one ? '' : 'All')](selector);
177 } catch(e) { // fallback to brute if available
178 return Y.Selector.query(selector, root, one, true); // redo with skipNative true
182 filter: function(nodes, selector) {
186 if (nodes && selector) {
187 for (i = 0; (node = nodes[i++]);) {
188 if (Y.Selector.test(node, selector)) {
189 ret[ret.length] = node;
198 test: function(node, selector, root) {
200 groups = selector.split(','),
208 if (node && node.tagName) { // only test HTMLElements
210 // we need a root if off-doc
211 if (!root && !Y.DOM.inDoc(node)) {
212 parent = node.parentNode;
215 } else { // only use frag when no parent to query
216 frag = node[OWNER_DOCUMENT].createDocumentFragment();
217 frag.appendChild(node);
222 root = root || node[OWNER_DOCUMENT];
227 for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
228 group += '[id="' + node.id + '"]';
229 items = Y.Selector.query(group, root);
231 for (j = 0; item = items[j++];) {
242 if (useFrag) { // cleanup
243 frag.removeChild(node);
251 * A convenience function to emulate Y.Node's aNode.ancestor(selector).
252 * @param {HTMLElement} element An HTMLElement to start the query from.
253 * @param {String} selector The CSS selector to test the node against.
254 * @return {HTMLElement} The ancestor node matching the selector, or null.
255 * @param {Boolean} testSelf optional Whether or not to include the element in the scan
259 ancestor: function (element, selector, testSelf) {
260 return Y.DOM.ancestor(element, function(n) {
261 return Y.Selector.test(n, selector);
266 Y.mix(Y.Selector, Selector, true);
271 }, '3.3.0' ,{requires:['dom-base']});