3
Copyright 2011 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('selector-css2', function(Y) {
10
* The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
12
* @submodule selector-css2
17
* Provides helper methods for collecting and filtering DOM elements.
20
var PARENT_NODE = 'parentNode',
22
ATTRIBUTES = 'attributes',
23
COMBINATOR = 'combinator',
26
Selector = Y.Selector,
29
_reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
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) {
56
attr: /(\[[^\]]*\])/g,
57
esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
58
pseudos: /(\([^\)]*\))/g
62
* Mapping of shorthand tokens to corresponding attribute selector
67
'\\#(-?[_a-z0-9]+[-\\w\\uE000]*)': '[id=$1]',
68
'\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
72
* List of operators and corresponding boolean functions.
73
* These functions are passed the attribute and the current node's value of the attribute.
78
'': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
80
//'=': '^{val}$', // equality
81
'~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
82
'|=': '^{val}-?' // optional hyphen-delimited
86
'first-child': function(node) {
87
return Y.Selector._children(node[PARENT_NODE])[0] === node;
91
_bruteQuery: function(selector, root, firstOnly) {
94
tokens = Selector._tokenize(selector),
95
token = tokens[tokens.length - 1],
96
rootDoc = Y.DOM._getDoc(root),
103
// if we have an initial ID, set to root when in document
105
if (tokens[0] && rootDoc === root &&
106
(id = tokens[0].id) &&
107
rootDoc.getElementById(id)) {
108
root = rootDoc.getElementById(id);
115
className = token.className;
116
tagName = token.tagName || '*';
118
if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
119
// try ID first, unless no root.all && root not in document
120
// (root.all works off document, but not getElementById)
121
// TODO: move to allById?
122
if (id && (root.all || (root.nodeType === 9 || Y.DOM.inDoc(root)))) {
123
nodes = Y.DOM.allById(id, root);
125
} else if (className) {
126
nodes = root.getElementsByClassName(className);
127
} else { // default to tagName
128
nodes = root.getElementsByTagName(tagName);
131
} else { // brute getElementsByTagName('*')
132
child = root.firstChild;
134
if (child.tagName) { // only collect HTMLElements
137
child = child.nextSilbing || child.firstChild;
141
ret = Selector._filterNodes(nodes, tokens, firstOnly);
148
_filterNodes: function(nodes, tokens, firstOnly) {
156
getters = Y.Selector.getters,
162
//FUNCTION = 'function',
168
for (i = 0; (tmpNode = node = nodes[i++]);) {
173
while (tmpNode && tmpNode.tagName) {
178
while ((test = tests[--j])) {
180
if (getters[test[0]]) {
181
value = getters[test[0]](tmpNode, test[0]);
183
value = tmpNode[test[0]];
184
// use getAttribute for non-standard attributes
185
if (value === undefined && tmpNode.getAttribute) {
186
value = tmpNode.getAttribute(test[0]);
190
if ((operator === '=' && value !== test[2]) || // fast path for equality
191
(typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
192
operator.test && !operator.test(value)) || // regex test
193
(!operator.test && // protect against RegExp as function (webkit)
194
typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
196
// skip non element nodes or non-matching tags
197
if ((tmpNode = tmpNode[path])) {
200
(token.tagName && token.tagName !== tmpNode.tagName))
202
tmpNode = tmpNode[path];
210
n--; // move to next token
211
// now that we've passed the test, move up the tree by combinator
212
if (!pass && (combinator = token.combinator)) {
213
path = combinator.axis;
214
tmpNode = tmpNode[path];
216
// skip non element nodes
217
while (tmpNode && !tmpNode.tagName) {
218
tmpNode = tmpNode[path];
221
if (combinator.direct) { // one pass only
225
} else { // success if we made it this far
233
}// while (tmpNode = node = nodes[++i]);
234
node = tmpNode = null;
250
axis: 'previousSibling',
258
re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
259
fn: function(match, token) {
260
var operator = match[2] || '',
261
operators = Selector.operators,
262
escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
265
// add prefiltering for ID and CLASS
266
if ((match[1] === 'id' && operator === '=') ||
267
(match[1] === 'className' &&
268
Y.config.doc.documentElement.getElementsByClassName &&
269
(operator === '~=' || operator === '='))) {
270
token.prefilter = match[1];
275
// escape all but ID for prefilter, which may run through QSA (via Dom.allById)
276
token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
281
if (operator in operators) {
282
test = operators[operator];
283
if (typeof test === 'string') {
284
match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
285
test = new RegExp(test.replace('{val}', match[3]));
289
if (!token.last || token.prefilter !== match[1]) {
290
return match.slice(1);
296
re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
297
fn: function(match, token) {
298
var tag = match[1].toUpperCase();
301
if (tag !== '*' && (!token.last || token.prefilter)) {
302
return [TAG_NAME, '=', tag];
304
if (!token.prefilter) {
305
token.prefilter = 'tagName';
311
re: /^\s*([>+~]|\s)\s*/,
312
fn: function(match, token) {
317
re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
318
fn: function(match, token) {
319
var test = Selector[PSEUDOS][match[1]];
320
if (test) { // reorder match array and unescape special chars for tests
322
match[2] = match[2].replace(/\\/g, '');
324
return [match[2], test];
325
} else { // selector token not supported (possibly missing CSS3 module)
332
_getToken: function(token) {
344
Break selector into token units per simple selector.
345
Combinator is attached to the previous token.
347
_tokenize: function(selector) {
348
selector = selector || '';
349
selector = Selector._replaceShorthand(Y.Lang.trim(selector));
350
var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
351
query = selector, // original query for debug report
352
tokens = [], // array of tokens
353
found = false, // whether or not any matches were found this pass
354
match, // the regex match
359
Search for selector patterns, store, and strip them from the selector string
360
until no patterns match (invalid selector) or we run out of chars.
362
Multiple attributes and pseudos are allowed, in any order.
364
'form:first-child[type=button]:not(button)[lang|=en]'
368
found = false; // reset after full pass
369
for (i = 0; (parser = Selector._parsers[i++]);) {
370
if ( (match = parser.re.exec(selector)) ) { // note assignment
371
if (parser.name !== COMBINATOR ) {
372
token.selector = selector;
374
selector = selector.replace(match[0], ''); // strip current match from selector
375
if (!selector.length) {
379
if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
380
match[1] = Selector._attrFilters[match[1]];
383
test = parser.fn(match, token);
384
if (test === false) { // selector not supported
388
token.tests.push(test);
391
if (!selector.length || parser.name === COMBINATOR) {
393
token = Selector._getToken(token);
394
if (parser.name === COMBINATOR) {
395
token.combinator = Y.Selector.combinators[match[1]];
401
} while (found && selector.length);
403
if (!found || selector.length) { // not fully parsed
404
Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
410
_replaceShorthand: function(selector) {
411
var shorthand = Selector.shorthand,
412
esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
418
selector = selector.replace(Selector._re.esc, '\uE000');
421
attrs = selector.match(Selector._re.attr);
422
pseudos = selector.match(Selector._re.pseudos);
425
selector = selector.replace(Selector._re.attr, '\uE001');
429
selector = selector.replace(Selector._re.pseudos, '\uE002');
433
for (re in shorthand) {
434
if (shorthand.hasOwnProperty(re)) {
435
selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
440
for (i = 0, len = attrs.length; i < len; ++i) {
441
selector = selector.replace(/\uE001/, attrs[i]);
446
for (i = 0, len = pseudos.length; i < len; ++i) {
447
selector = selector.replace(/\uE002/, pseudos[i]);
451
selector = selector.replace(/\[/g, '\uE003');
452
selector = selector.replace(/\]/g, '\uE004');
454
selector = selector.replace(/\(/g, '\uE005');
455
selector = selector.replace(/\)/g, '\uE006');
458
for (i = 0, len = esc.length; i < len; ++i) {
459
selector = selector.replace('\uE000', esc[i]);
467
'class': 'className',
472
href: function(node, attr) {
473
return Y.DOM.getAttribute(node, attr);
478
Y.mix(Y.Selector, SelectorCSS2, true);
479
Y.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
481
// IE wants class with native queries
482
if (Y.Selector.useNative && Y.config.doc.querySelector) {
483
Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
488
}, '3.4.1' ,{requires:['selector-native']});