/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.3.0 build: 3167 */ YUI.add('selector-native', function(Y) { (function(Y) { /** * The selector-native module provides support for native querySelector * @module dom * @submodule selector-native * @for Selector */ /** * Provides support for using CSS selectors to query the DOM * @class Selector * @static * @for Selector */ Y.namespace('Selector'); // allow native module to standalone var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition', OWNER_DOCUMENT = 'ownerDocument'; var Selector = { _foundCache: [], useNative: true, _compare: ('sourceIndex' in Y.config.doc.documentElement) ? function(nodeA, nodeB) { var a = nodeA.sourceIndex, b = nodeB.sourceIndex; if (a === b) { return 0; } else if (a > b) { return 1; } return -1; } : (Y.config.doc.documentElement[COMPARE_DOCUMENT_POSITION] ? function(nodeA, nodeB) { if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) { return -1; } else { return 1; } } : function(nodeA, nodeB) { var rangeA, rangeB, compare; if (nodeA && nodeB) { rangeA = nodeA[OWNER_DOCUMENT].createRange(); rangeA.setStart(nodeA, 0); rangeB = nodeB[OWNER_DOCUMENT].createRange(); rangeB.setStart(nodeB, 0); compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END } return compare; }), _sort: function(nodes) { if (nodes) { nodes = Y.Array(nodes, 0, true); if (nodes.sort) { nodes.sort(Selector._compare); } } return nodes; }, _deDupe: function(nodes) { var ret = [], i, node; for (i = 0; (node = nodes[i++]);) { if (!node._found) { ret[ret.length] = node; node._found = true; } } for (i = 0; (node = ret[i++]);) { node._found = null; node.removeAttribute('_found'); } return ret; }, /** * Retrieves a set of nodes based on a given CSS selector. * @method query * * @param {string} selector The CSS Selector to test the node against. * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc * @param {Boolean} firstOnly optional Whether or not to return only the first match. * @return {Array} An array of nodes that match the given selector. * @static */ query: function(selector, root, firstOnly, skipNative) { root = root || Y.config.doc; var ret = [], useNative = (Y.Selector.useNative && Y.config.doc.querySelector && !skipNative), queries = [[selector, root]], query, result, i, fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery; if (selector && fn) { // split group into seperate queries if (!skipNative && // already done if skipping (!useNative || root.tagName)) { // split native when element scoping is needed queries = Selector._splitQueries(selector, root); } for (i = 0; (query = queries[i++]);) { result = fn(query[0], query[1], firstOnly); if (!firstOnly) { // coerce DOM Collection to Array result = Y.Array(result, 0, true); } if (result) { ret = ret.concat(result); } } if (queries.length > 1) { // remove dupes and sort by doc order ret = Selector._sort(Selector._deDupe(ret)); } } return (firstOnly) ? (ret[0] || null) : ret; }, // allows element scoped queries to begin with combinator // e.g. query('> p', document.body) === query('body > p') _splitQueries: function(selector, node) { var groups = selector.split(','), queries = [], prefix = '', i, len; if (node) { // enforce for element scoping if (node.tagName) { node.id = node.id || Y.guid(); prefix = '[id="' + node.id + '"] '; } for (i = 0, len = groups.length; i < len; ++i) { selector = prefix + groups[i]; queries.push([selector, node]); } } return queries; }, _nativeQuery: function(selector, root, one) { if (Y.UA.webkit && selector.indexOf(':checked') > -1 && (Y.Selector.pseudos && Y.Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected" return Y.Selector.query(selector, root, one, true); // redo with skipNative true to try brute query } try { return root['querySelector' + (one ? '' : 'All')](selector); } catch(e) { // fallback to brute if available return Y.Selector.query(selector, root, one, true); // redo with skipNative true } }, filter: function(nodes, selector) { var ret = [], i, node; if (nodes && selector) { for (i = 0; (node = nodes[i++]);) { if (Y.Selector.test(node, selector)) { ret[ret.length] = node; } } } else { } return ret; }, test: function(node, selector, root) { var ret = false, groups = selector.split(','), useFrag = false, parent, item, items, frag, i, j, group; if (node && node.tagName) { // only test HTMLElements // we need a root if off-doc if (!root && !Y.DOM.inDoc(node)) { parent = node.parentNode; if (parent) { root = parent; } else { // only use frag when no parent to query frag = node[OWNER_DOCUMENT].createDocumentFragment(); frag.appendChild(node); root = frag; useFrag = true; } } root = root || node[OWNER_DOCUMENT]; if (!node.id) { node.id = Y.guid(); } for (i = 0; (group = groups[i++]);) { // TODO: off-dom test group += '[id="' + node.id + '"]'; items = Y.Selector.query(group, root); for (j = 0; item = items[j++];) { if (item === node) { ret = true; break; } } if (ret) { break; } } if (useFrag) { // cleanup frag.removeChild(node); } } return ret; }, /** * A convenience function to emulate Y.Node's aNode.ancestor(selector). * @param {HTMLElement} element An HTMLElement to start the query from. * @param {String} selector The CSS selector to test the node against. * @return {HTMLElement} The ancestor node matching the selector, or null. * @param {Boolean} testSelf optional Whether or not to include the element in the scan * @static * @method ancestor */ ancestor: function (element, selector, testSelf) { return Y.DOM.ancestor(element, function(n) { return Y.Selector.test(n, selector); }, testSelf); } }; Y.mix(Y.Selector, Selector, true); })(Y); }, '3.3.0' ,{requires:['dom-base']});