76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * @module dom
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * @submodule selector-css2
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * @for Selector
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * Provides helper methods for collecting and filtering DOM elements.
313e502fe352bdaa1e9707617e5fdffd5519bfc0Matt Sweeney _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/,
313e502fe352bdaa1e9707617e5fdffd5519bfc0Matt Sweeney // TODO: better detection, document specific
313e502fe352bdaa1e9707617e5fdffd5519bfc0Matt Sweeney _isXML: (function() {
313e502fe352bdaa1e9707617e5fdffd5519bfc0Matt Sweeney var isXML = (Y.config.doc.createElement('div').tagName !== 'DIV');
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * Mapping of shorthand tokens to corresponding attribute selector
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * @property shorthand
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * @type object
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * List of operators and corresponding boolean functions.
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * These functions are passed the attribute and the current node's value of the attribute.
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * @property operators
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass * @type object
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass '': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
36983c2503289bdffd9962fc19c6ab015c6cd751Matt Sweeney return Y.DOM._children(node[PARENT_NODE])[0] === node;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass _bruteQuery: function(selector, root, firstOnly) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // prefilter nodes
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // try ID first, unless no root.all && root not in document
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // (root.all works off document, but not getElementById)
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (id && (root.all || (root.nodeType === 9 || Y.DOM.inDoc(root)))) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // try className
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass } else if (className) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass } else { // default to tagName
e5e4ce6686f2b73be9df6dee9002b469ba61ae85Matt Sweeney } else { // brute getElementsByTagName()
e5e4ce6686f2b73be9df6dee9002b469ba61ae85Matt Sweeney // only collect HTMLElements
e5e4ce6686f2b73be9df6dee9002b469ba61ae85Matt Sweeney // match tag to supplement missing getElementsByTagName
e5e4ce6686f2b73be9df6dee9002b469ba61ae85Matt Sweeney if (child.tagName && (tagName === '*' || child.tagName === tagName)) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass ret = Selector._filterNodes(nodes, tokens, firstOnly);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass _filterNodes: function(nodes, tokens, firstOnly) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (j && !pass) {
313e502fe352bdaa1e9707617e5fdffd5519bfc0Matt Sweeney if (test[0] === 'tagName' && !Selector._isXML) {
da91bc62b8e0ca859df14ba36dc85c5a1a7ecd71Matt Sweeney if (typeof value != 'string' && value !== undefined && value.toString) {
da91bc62b8e0ca859df14ba36dc85c5a1a7ecd71Matt Sweeney value = value.toString(); // coerce for comparison
da91bc62b8e0ca859df14ba36dc85c5a1a7ecd71Matt Sweeney } else if (value === undefined && tmpNode.getAttribute) {
da91bc62b8e0ca859df14ba36dc85c5a1a7ecd71Matt Sweeney // use getAttribute for non-standard attributes
da91bc62b8e0ca859df14ba36dc85c5a1a7ecd71Matt Sweeney value = tmpNode.getAttribute(test[0], 2); // 2 === force string for IE
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if ((operator === '=' && value !== test[2]) || // fast path for equality
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass operator.test && !operator.test(value)) || // regex test
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass (!operator.test && // protect against RegExp as function (webkit)
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // skip non element nodes or non-matching tags
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass (token.tagName && token.tagName !== tmpNode.tagName))
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass n--; // move to next token
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // now that we've passed the test, move up the tree by combinator
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // skip non element nodes
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass } else { // success if we made it this far
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // add prefiltering for ID and CLASS
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass Y.config.doc.documentElement.getElementsByClassName &&
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass // add tests
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass test = new RegExp(test.replace('{val}', match[3]));
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (!token.last || token.prefilter !== match[1]) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (tag !== '*' && (!token.last || token.prefilter)) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass re: /^\s*([>+~]|\s)\s*/,
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass fn: function(match, token) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass var test = Selector[PSEUDOS][match[1]];
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (test) { // reorder match array and unescape special chars for tests
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (match[2]) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass match[2] = match[2].replace(/\\/g, '');
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass return [match[2], test];
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass } else { // selector token not supported (possibly missing CSS3 module)
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass return false;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass _getToken: function(token) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass tagName: null,
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass className: null,
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass attributes: {},
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass combinator: null,
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass Break selector into token units per simple selector.
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass Combinator is attached to the previous token.
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass _tokenize: function(selector) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass selector = selector || '';
995d931cab307d6df1075144025548f35c5bc337Matt Sweeney selector = Selector._parseSelector(Y.Lang.trim(selector));
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass query = selector, // original query for debug report
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass tokens = [], // array of tokens
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass found = false, // whether or not any matches were found this pass
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass match, // the regex match
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass Search for selector patterns, store, and strip them from the selector string
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass until no patterns match (invalid selector) or we run out of chars.
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass Multiple attributes and pseudos are allowed, in any order.
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass for example:
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass 'form:first-child[type=button]:not(button)[lang|=en]'
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass found = false; // reset after full pass
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass for (i = 0; (parser = Selector._parsers[i++]);) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if ( (match = parser.re.exec(selector)) ) { // note assignment
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (parser.name !== COMBINATOR ) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass token.selector = selector;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass selector = selector.replace(match[0], ''); // strip current match from selector
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (!selector.length) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass token.last = true;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass match[1] = Selector._attrFilters[match[1]];
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass test = parser.fn(match, token);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (test === false) { // selector not supported
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass found = false;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass break outer;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass } else if (test) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass token.tests.push(test);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (!selector.length || parser.name === COMBINATOR) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass tokens.push(token);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass token = Selector._getToken(token);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (parser.name === COMBINATOR) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass token.combinator = Y.Selector.combinators[match[1]];
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass found = true;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass } while (found && selector.length);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass if (!found || selector.length) { // not fully parsed
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass tokens = [];
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass return tokens;
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney _replaceMarkers: function(selector) {
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney selector = selector.replace(/\[/g, '\uE003');
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney selector = selector.replace(/\]/g, '\uE004');
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney selector = selector.replace(/\(/g, '\uE005');
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney selector = selector.replace(/\)/g, '\uE006');
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney return selector;
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney _replaceShorthand: function(selector) {
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney var shorthand = Y.Selector.shorthand,
995d931cab307d6df1075144025548f35c5bc337Matt Sweeney for (re in shorthand) {
995d931cab307d6df1075144025548f35c5bc337Matt Sweeney if (shorthand.hasOwnProperty(re)) {
995d931cab307d6df1075144025548f35c5bc337Matt Sweeney selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
995d931cab307d6df1075144025548f35c5bc337Matt Sweeney return selector;
995d931cab307d6df1075144025548f35c5bc337Matt Sweeney _parseSelector: function(selector) {
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney var replaced = Y.Selector._replaceSelector(selector),
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney selector = replaced.selector;
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney // replace shorthand (".foo, #bar") after pseudos and attrs
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney // to avoid replacing unescaped chars
995d931cab307d6df1075144025548f35c5bc337Matt Sweeney selector = Y.Selector._replaceShorthand(selector);
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney selector = Y.Selector._restore('attr', selector, replaced.attrs);
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney selector = Y.Selector._restore('pseudo', selector, replaced.pseudos);
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney // replace braces and parens before restoring escaped chars
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney // to avoid replacing ecaped markers
1e4ab86a9283948b01948c28fc8782f14a8c2f9aMatt Sweeney selector = Y.Selector._replaceMarkers(selector);
dd02386244f04f36bb138d55c2889aac556d689eMatt Sweeney selector = Y.Selector._restore('esc', selector, replaced.esc);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass return selector;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass _attrFilters: {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass 'class': 'className',
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass 'for': 'htmlFor'
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass href: function(node, attr) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass return Y.DOM.getAttribute(node, attr);
93e7d06d2c6eca3a7b6910ad473b09e9dcc0a15cMatt Sweeney id: function(node, attr) {
93e7d06d2c6eca3a7b6910ad473b09e9dcc0a15cMatt Sweeney return Y.DOM.getId(node);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav GlassY.mix(Y.Selector, SelectorCSS2, true);
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav GlassY.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass// IE wants class with native queries
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glassif (Y.Selector.useNative && Y.config.doc.querySelector) {
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
76ca635d61eb3f9fb7c9d788a44fa8b1690aa138Dav Glass}, '@VERSION@' ,{requires:['selector-native']});