3
Copyright 2012 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';
33
re: /\\[:\[\]\(\)#\.\'\>+~"]/gi
49
_escapeId: function(id) {
51
id = id.replace(/([:\[\]\(\)#\.'<>+~"])/g,'\\$1');
56
_compare: ('sourceIndex' in Y.config.doc.documentElement) ?
57
function(nodeA, nodeB) {
58
var a = nodeA.sourceIndex,
59
b = nodeB.sourceIndex;
69
} : (Y.config.doc.documentElement[COMPARE_DOCUMENT_POSITION] ?
70
function(nodeA, nodeB) {
71
if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
77
function(nodeA, nodeB) {
78
var rangeA, rangeB, compare;
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
91
_sort: function(nodes) {
93
nodes = Y.Array(nodes, 0, true);
95
nodes.sort(Selector._compare);
102
_deDupe: function(nodes) {
106
for (i = 0; (node = nodes[i++]);) {
108
ret[ret.length] = node;
113
for (i = 0; (node = ret[i++]);) {
115
node.removeAttribute('_found');
122
* Retrieves a set of nodes based on a given CSS selector.
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.
131
query: function(selector, root, firstOnly, skipNative) {
132
root = root || Y.config.doc;
134
useNative = (Y.Selector.useNative && Y.config.doc.querySelector && !skipNative),
135
queries = [[selector, root]],
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);
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);
154
ret = ret.concat(result);
158
if (queries.length > 1) { // remove dupes and sort by doc order
159
ret = Selector._sort(Selector._deDupe(ret));
163
return (firstOnly) ? (ret[0] || null) : ret;
167
_replaceSelector: function(selector) {
168
var esc = Y.Selector._parse('esc', selector), // pull escaped colon, brackets, etc.
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);
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);
198
_replaceCommas: function(selector) {
199
var replaced = Y.Selector._replaceSelector(selector),
200
selector = replaced.selector;
203
selector = selector.replace(/,/g, '\uE007');
204
replaced.selector = selector;
205
selector = Y.Selector._restoreSelector(replaced);
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);
217
var groups = selector.split('\uE007'), // split on replaced comma token
225
// enforce for element scoping
226
if (node.nodeType === 1) { // Elements only
227
id = Y.Selector._escapeId(Y.DOM.getId(node));
231
Y.DOM.setId(node, id);
234
prefix = '[id="' + id + '"] ';
237
for (i = 0, len = groups.length; i < len; ++i) {
238
selector = prefix + groups[i];
239
queries.push([selector, node]);
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
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
258
filter: function(nodes, selector) {
262
if (nodes && selector) {
263
for (i = 0; (node = nodes[i++]);) {
264
if (Y.Selector.test(node, selector)) {
265
ret[ret.length] = node;
274
test: function(node, selector, root) {
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;
296
} else { // only use frag when no parent to query
297
frag = node[OWNER_DOCUMENT].createDocumentFragment();
298
frag.appendChild(node);
303
root = root || node[OWNER_DOCUMENT];
305
id = Y.Selector._escapeId(Y.DOM.getId(node));
308
Y.DOM.setId(node, id);
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++];) {
326
if (useFrag) { // cleanup
327
frag.removeChild(node);
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
344
ancestor: function (element, selector, testSelf) {
345
return Y.DOM.ancestor(element, function(n) {
346
return Y.Selector.test(n, selector);
350
_parse: function(name, selector) {
351
return selector.match(Y.Selector._types[name].re);
354
_replace: function(name, selector) {
355
var o = Y.Selector._types[name];
356
return selector.replace(o.re, o.token);
359
_restore: function(name, selector, items) {
361
var token = Y.Selector._types[name].token,
363
for (i = 0, len = items.length; i < len; ++i) {
364
selector = selector.replace(token, items[i]);
371
Y.mix(Y.Selector, Selector, true);
376
}, '3.5.1' ,{requires:['dom-base']});