3
Copyright 2011 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('selector-native', function(Y) {
11
* The selector-native module provides support for native querySelector
13
* @submodule selector-native
18
* Provides support for using CSS selectors to query the DOM
24
Y.namespace('Selector'); // allow native module to standalone
26
var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
27
OWNER_DOCUMENT = 'ownerDocument';
34
_compare: ('sourceIndex' in Y.config.doc.documentElement) ?
35
function(nodeA, nodeB) {
36
var a = nodeA.sourceIndex,
37
b = nodeB.sourceIndex;
47
} : (Y.config.doc.documentElement[COMPARE_DOCUMENT_POSITION] ?
48
function(nodeA, nodeB) {
49
if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
55
function(nodeA, nodeB) {
56
var rangeA, rangeB, compare;
58
rangeA = nodeA[OWNER_DOCUMENT].createRange();
59
rangeA.setStart(nodeA, 0);
60
rangeB = nodeB[OWNER_DOCUMENT].createRange();
61
rangeB.setStart(nodeB, 0);
62
compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
69
_sort: function(nodes) {
71
nodes = Y.Array(nodes, 0, true);
73
nodes.sort(Selector._compare);
80
_deDupe: function(nodes) {
84
for (i = 0; (node = nodes[i++]);) {
86
ret[ret.length] = node;
91
for (i = 0; (node = ret[i++]);) {
93
node.removeAttribute('_found');
100
* Retrieves a set of nodes based on a given CSS selector.
103
* @param {string} selector The CSS Selector to test the node against.
104
* @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
105
* @param {Boolean} firstOnly optional Whether or not to return only the first match.
106
* @return {Array} An array of nodes that match the given selector.
109
query: function(selector, root, firstOnly, skipNative) {
110
root = root || Y.config.doc;
112
useNative = (Y.Selector.useNative && Y.config.doc.querySelector && !skipNative),
113
queries = [[selector, root]],
117
fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery;
119
if (selector && fn) {
120
// split group into seperate queries
121
if (!skipNative && // already done if skipping
122
(!useNative || root.tagName)) { // split native when element scoping is needed
123
queries = Selector._splitQueries(selector, root);
126
for (i = 0; (query = queries[i++]);) {
127
result = fn(query[0], query[1], firstOnly);
128
if (!firstOnly) { // coerce DOM Collection to Array
129
result = Y.Array(result, 0, true);
132
ret = ret.concat(result);
136
if (queries.length > 1) { // remove dupes and sort by doc order
137
ret = Selector._sort(Selector._deDupe(ret));
141
Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
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 pick up "selected" with "checked"
173
return Y.Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
176
//Y.log('trying native query with: ' + selector, 'info', 'selector-native');
177
return root['querySelector' + (one ? '' : 'All')](selector);
178
} catch(e) { // fallback to brute if available
179
//Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
180
return Y.Selector.query(selector, root, one, true); // redo with skipNative true
184
filter: function(nodes, selector) {
188
if (nodes && selector) {
189
for (i = 0; (node = nodes[i++]);) {
190
if (Y.Selector.test(node, selector)) {
191
ret[ret.length] = node;
195
Y.log('invalid filter input (nodes: ' + nodes +
196
', selector: ' + selector + ')', 'warn', 'Selector');
202
test: function(node, selector, root) {
212
if (node && node.tagName) { // only test HTMLElements
214
if (typeof selector == 'function') { // test with function
215
ret = selector.call(node, node);
216
} else { // test with query
217
// we need a root if off-doc
218
groups = selector.split(',');
219
if (!root && !Y.DOM.inDoc(node)) {
220
parent = node.parentNode;
223
} else { // only use frag when no parent to query
224
frag = node[OWNER_DOCUMENT].createDocumentFragment();
225
frag.appendChild(node);
230
root = root || node[OWNER_DOCUMENT];
235
for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
236
group += '[id="' + node.id + '"]';
237
items = Y.Selector.query(group, root);
239
for (j = 0; item = items[j++];) {
250
if (useFrag) { // cleanup
251
frag.removeChild(node);
260
* A convenience function to emulate Y.Node's aNode.ancestor(selector).
261
* @param {HTMLElement} element An HTMLElement to start the query from.
262
* @param {String} selector The CSS selector to test the node against.
263
* @return {HTMLElement} The ancestor node matching the selector, or null.
264
* @param {Boolean} testSelf optional Whether or not to include the element in the scan
268
ancestor: function (element, selector, testSelf) {
269
return Y.DOM.ancestor(element, function(n) {
270
return Y.Selector.test(n, selector);
275
Y.mix(Y.Selector, Selector, true);
280
}, '3.4.1' ,{requires:['dom-base']});