2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 YUI.add('selector-css2', function(Y) {
11 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
13 * @submodule selector-css2
18 * Provides helper methods for collecting and filtering DOM elements.
21 var PARENT_NODE = 'parentNode',
23 ATTRIBUTES = 'attributes',
24 COMBINATOR = 'combinator',
27 Selector = Y.Selector,
31 _children: function(node, tag) {
32 var ret = node.children,
38 if (node.children && tag && node.children.tags) {
39 children = node.children.tags(tag);
40 } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
41 childNodes = ret || node.childNodes;
43 for (i = 0; (child = childNodes[i++]);) {
45 if (!tag || tag === child.tagName) {
59 pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\)))*/i
63 * Mapping of shorthand tokens to corresponding attribute selector
68 '\\#(-?[_a-z]+[-\\w]*)': '[id=$1]',
69 '\\.(-?[_a-z]+[-\\w]*)': '[className~=$1]'
73 * List of operators and corresponding boolean functions.
74 * These functions are passed the attribute and the current node's value of the attribute.
79 '': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
81 //'=': '^{val}$', // equality
82 '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
83 '|=': '^{val}-?' // optional hyphen-delimited
87 'first-child': function(node) {
88 return Y.Selector._children(node[PARENT_NODE])[0] === node;
92 _bruteQuery: function(selector, root, firstOnly) {
95 tokens = Selector._tokenize(selector),
96 token = tokens[tokens.length - 1],
97 rootDoc = Y.DOM._getDoc(root),
103 // if we have an initial ID, set to root when in document
104 if (tokens[0] && rootDoc === root &&
105 (id = tokens[0].id) &&
106 rootDoc.getElementById(id)) {
107 root = rootDoc.getElementById(id);
113 className = token.className;
114 tagName = token.tagName || '*';
118 if (rootDoc.getElementById(id)) { // if in document
119 nodes = [rootDoc.getElementById(id)]; // TODO: DOM.byId?
121 // try className if supported
122 } else if (className) {
123 nodes = root.getElementsByClassName(className);
124 } else if (tagName) { // default to tagName
125 nodes = root.getElementsByTagName(tagName || '*');
129 ret = Selector._filterNodes(nodes, tokens, firstOnly);
136 _filterNodes: function(nodes, tokens, firstOnly) {
144 getters = Y.Selector.getters,
150 //FUNCTION = 'function',
156 for (i = 0; (tmpNode = node = nodes[i++]);) {
161 while (tmpNode && tmpNode.tagName) {
166 while ((test = tests[--j])) {
168 if (getters[test[0]]) {
169 value = getters[test[0]](tmpNode, test[0]);
171 value = tmpNode[test[0]];
172 // use getAttribute for non-standard attributes
173 if (value === undefined && tmpNode.getAttribute) {
174 value = tmpNode.getAttribute(test[0]);
178 if ((operator === '=' && value !== test[2]) || // fast path for equality
179 (operator.test && !operator.test(value)) || // regex test
180 (operator.call && !operator(tmpNode, test[0]))) { // function test
182 // skip non element nodes or non-matching tags
183 if ((tmpNode = tmpNode[path])) {
186 (token.tagName && token.tagName !== tmpNode.tagName))
188 tmpNode = tmpNode[path];
196 n--; // move to next token
197 // now that we've passed the test, move up the tree by combinator
198 if (!pass && (combinator = token.combinator)) {
199 path = combinator.axis;
200 tmpNode = tmpNode[path];
202 // skip non element nodes
203 while (tmpNode && !tmpNode.tagName) {
204 tmpNode = tmpNode[path];
207 if (combinator.direct) { // one pass only
211 } else { // success if we made it this far
219 }// while (tmpNode = node = nodes[++i]);
220 node = tmpNode = null;
224 _getRegExp: function(str, flags) {
225 var regexCache = Selector._regexCache;
227 if (!regexCache[str + flags]) {
228 regexCache[str + flags] = new RegExp(str, flags);
230 return regexCache[str + flags];
245 axis: 'previousSibling',
253 re: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
254 fn: function(match, token) {
255 var operator = match[2] || '',
256 operators = Y.Selector.operators,
259 // add prefiltering for ID and CLASS
260 if ((match[1] === 'id' && operator === '=') ||
261 (match[1] === 'className' &&
262 document.getElementsByClassName &&
263 (operator === '~=' || operator === '='))) {
264 token.prefilter = match[1];
265 token[match[1]] = match[3];
269 if (operator in operators) {
270 test = operators[operator];
271 if (typeof test === 'string') {
272 test = Y.Selector._getRegExp(test.replace('{val}', match[3]));
276 if (!token.last || token.prefilter !== match[1]) {
277 return match.slice(1);
284 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
285 fn: function(match, token) {
286 var tag = match[1].toUpperCase();
289 if (tag !== '*' && (!token.last || token.prefilter)) {
290 return [TAG_NAME, '=', tag];
292 if (!token.prefilter) {
293 token.prefilter = 'tagName';
299 re: /^\s*([>+~]|\s)\s*/,
300 fn: function(match, token) {
305 re: /^:([\-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
306 fn: function(match, token) {
307 var test = Selector[PSEUDOS][match[1]];
308 if (test) { // reorder match array
309 return [match[2], test];
310 } else { // selector token not supported (possibly missing CSS3 module)
317 _getToken: function(token) {
329 Break selector into token units per simple selector.
330 Combinator is attached to the previous token.
332 _tokenize: function(selector) {
333 selector = selector || '';
334 selector = Selector._replaceShorthand(Y.Lang.trim(selector));
335 var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
336 query = selector, // original query for debug report
337 tokens = [], // array of tokens
338 found = false, // whether or not any matches were found this pass
339 match, // the regex match
344 Search for selector patterns, store, and strip them from the selector string
345 until no patterns match (invalid selector) or we run out of chars.
347 Multiple attributes and pseudos are allowed, in any order.
349 'form:first-child[type=button]:not(button)[lang|=en]'
353 found = false; // reset after full pass
354 for (i = 0; (parser = Selector._parsers[i++]);) {
355 if ( (match = parser.re.exec(selector)) ) { // note assignment
356 if (parser !== COMBINATOR ) {
357 token.selector = selector;
359 selector = selector.replace(match[0], ''); // strip current match from selector
360 if (!selector.length) {
364 if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
365 match[1] = Selector._attrFilters[match[1]];
368 test = parser.fn(match, token);
369 if (test === false) { // selector not supported
373 token.tests.push(test);
376 if (!selector.length || parser.name === COMBINATOR) {
378 token = Selector._getToken(token);
379 if (parser.name === COMBINATOR) {
380 token.combinator = Y.Selector.combinators[match[1]];
386 } while (found && selector.length);
388 if (!found || selector.length) { // not fully parsed
394 _replaceShorthand: function(selector) {
395 var shorthand = Selector.shorthand,
396 attrs = selector.match(Selector._re.attr), // pull attributes to avoid false pos on "." and "#"
397 pseudos = selector.match(Selector._re.pseudos), // pull attributes to avoid false pos on "." and "#"
401 selector = selector.replace(Selector._re.pseudos, '!!REPLACED_PSEUDO!!');
405 selector = selector.replace(Selector._re.attr, '!!REPLACED_ATTRIBUTE!!');
408 for (re in shorthand) {
409 if (shorthand.hasOwnProperty(re)) {
410 selector = selector.replace(Selector._getRegExp(re, 'gi'), shorthand[re]);
415 for (i = 0, len = attrs.length; i < len; ++i) {
416 selector = selector.replace('!!REPLACED_ATTRIBUTE!!', attrs[i]);
420 for (i = 0, len = pseudos.length; i < len; ++i) {
421 selector = selector.replace('!!REPLACED_PSEUDO!!', pseudos[i]);
428 'class': 'className',
433 href: function(node, attr) {
434 return Y.DOM.getAttribute(node, attr);
439 Y.mix(Y.Selector, SelectorCSS2, true);
440 Y.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
442 // IE wants class with native queries
443 if (Y.Selector.useNative && document.querySelector) {
444 Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
449 }, '3.0.0' ,{requires:['selector-native']});