1/* 2Copyright (c) 2009, Yahoo! Inc. All rights reserved. 3Code licensed under the BSD License: 4http://developer.yahoo.net/yui/license.txt 5version: 2.8.0r4 6*/ 7/** 8 * The selector module provides helper methods allowing CSS3 Selectors to be used with DOM elements. 9 * @module selector 10 * @title Selector Utility 11 * @namespace YAHOO.util 12 * @requires yahoo, dom 13 */ 14 15(function() { 16var Y = YAHOO.util; 17 18/** 19 * Provides helper methods for collecting and filtering DOM elements. 20 * @namespace YAHOO.util 21 * @class Selector 22 * @static 23 */ 24 25Y.Selector = { 26 _foundCache: [], 27 _regexCache: {}, 28 29 _re: { 30 nth: /^(?:([-]?\d*)(n){1}|(odd|even)$)*([-+]?\d*)$/, 31 attr: /(\[.*\])/g, 32 urls: /^(?:href|src)/ 33 }, 34 35 /** 36 * Default document for use queries 37 * @property document 38 * @type object 39 * @default window.document 40 */ 41 document: window.document, 42 /** 43 * Mapping of attributes to aliases, normally to work around HTMLAttributes 44 * that conflict with JS reserved words. 45 * @property attrAliases 46 * @type object 47 */ 48 attrAliases: { 49 }, 50 51 /** 52 * Mapping of shorthand tokens to corresponding attribute selector 53 * @property shorthand 54 * @type object 55 */ 56 shorthand: { 57 //'(?:(?:[^\\)\\]\\s*>+~,]+)(?:-?[_a-z]+[-\\w]))+#(-?[_a-z]+[-\\w]*)': '[id=$1]', 58 '\\#(-?[_a-z]+[-\\w]*)': '[id=$1]', 59 '\\.(-?[_a-z]+[-\\w]*)': '[class~=$1]' 60 }, 61 62 /** 63 * List of operators and corresponding boolean functions. 64 * These functions are passed the attribute and the current node's value of the attribute. 65 * @property operators 66 * @type object 67 */ 68 operators: { 69 '=': function(attr, val) { return attr === val; }, // Equality 70 '!=': function(attr, val) { return attr !== val; }, // Inequality 71 '~=': function(attr, val) { // Match one of space seperated words 72 var s = ' '; 73 return (s + attr + s).indexOf((s + val + s)) > -1; 74 }, 75 '|=': function(attr, val) { return attr === val || attr.slice(0, val.length + 1) === val + '-'; }, // Matches value followed by optional hyphen 76 '^=': function(attr, val) { return attr.indexOf(val) === 0; }, // Match starts with value 77 '$=': function(attr, val) { return attr.slice(-val.length) === val; }, // Match ends with value 78 '*=': function(attr, val) { return attr.indexOf(val) > -1; }, // Match contains value as substring 79 '': function(attr, val) { return attr; } // Just test for existence of attribute 80 }, 81 82 /** 83 * List of pseudo-classes and corresponding boolean functions. 84 * These functions are called with the current node, and any value that was parsed with the pseudo regex. 85 * @property pseudos 86 * @type object 87 */ 88 pseudos: { 89 'root': function(node) { 90 return node === node.ownerDocument.documentElement; 91 }, 92 93 'nth-child': function(node, val) { 94 return Y.Selector._getNth(node, val); 95 }, 96 97 'nth-last-child': function(node, val) { 98 return Y.Selector._getNth(node, val, null, true); 99 }, 100 101 'nth-of-type': function(node, val) { 102 return Y.Selector._getNth(node, val, node.tagName); 103 }, 104 105 'nth-last-of-type': function(node, val) { 106 return Y.Selector._getNth(node, val, node.tagName, true); 107 }, 108 109 'first-child': function(node) { 110 return Y.Selector._getChildren(node.parentNode)[0] === node; 111 }, 112 113 'last-child': function(node) { 114 var children = Y.Selector._getChildren(node.parentNode); 115 return children[children.length - 1] === node; 116 }, 117 118 'first-of-type': function(node, val) { 119 return Y.Selector._getChildren(node.parentNode, node.tagName)[0]; 120 }, 121 122 'last-of-type': function(node, val) { 123 var children = Y.Selector._getChildren(node.parentNode, node.tagName); 124 return children[children.length - 1]; 125 }, 126 127 'only-child': function(node) { 128 var children = Y.Selector._getChildren(node.parentNode); 129 return children.length === 1 && children[0] === node; 130 }, 131 132 'only-of-type': function(node) { 133 return Y.Selector._getChildren(node.parentNode, node.tagName).length === 1; 134 }, 135 136 'empty': function(node) { 137 return node.childNodes.length === 0; 138 }, 139 140 'not': function(node, simple) { 141 return !Y.Selector.test(node, simple); 142 }, 143 144 'contains': function(node, str) { 145 var text = node.innerText || node.textContent || ''; 146 return text.indexOf(str) > -1; 147 }, 148 'checked': function(node) { 149 return node.checked === true; 150 } 151 }, 152 153 /**
154 * Test if the supplied node matches the supplied selector. 155 * @method test 156 * 157 * @param {HTMLElement | String} node An id or node reference to the HTMLElement being tested. 158 * @param {string} selector The CSS Selector to test the node against. 159 * @return{boolean} Whether or not the node matches the selector. 160 * @static 161 162 */ 163 test: function(node, selector) { 164 node = Y.Selector.document.getElementById(node) || node; 165 166 if (!node) { 167 return false; 168 } 169 170 var groups = selector ? selector.split(',') : []; 171 if (groups.length) { 172 for (var i = 0, len = groups.length; i < len; ++i) { 173 if ( Y.Selector._test(node, groups[i]) ) { // passes if ANY group matches 174 return true; 175 } 176 } 177 return false; 178 } 179 return Y.Selector._test(node, selector); 180 }, 181 182 _test: function(node, selector, token, deDupe) { 183 token = token || Y.Selector._tokenize(selector).pop() || {}; 184 185 if (!node.tagName || 186 (token.tag !== '*' && node.tagName !== token.tag) || 187 (deDupe && node._found) ) { 188 return false; 189 } 190 191 if (token.attributes.length) { 192 var val, 193 ieFlag, 194 re_urls = Y.Selector._re.urls; 195 196 if (!node.attributes || !node.attributes.length) { 197 return false; 198 } 199 for (var i = 0, attr; attr = token.attributes[i++];) { 200 ieFlag = (re_urls.test(attr[0])) ? 2 : 0; 201 val = node.getAttribute(attr[0], ieFlag); 202 if (val === null || val === undefined) { 203 return false; 204 } 205 if ( Y.Selector.operators[attr[1]] && 206 !Y.Selector.operators[attr[1]](val, attr[2])) { 207 return false; 208 } 209 } 210 } 211 212 if (token.pseudos.length) { 213 for (var i = 0, len = token.pseudos.length; i < len; ++i) { 214 if (Y.Selector.pseudos[token.pseudos[i][0]] && 215 !Y.Selector.pseudos[token.pseudos[i][0]](node, token.pseudos[i][1])) { 216 return false; 217 } 218 } 219 } 220 221 return (token.previous && token.previous.combinator !== ',') ? 222 Y.Selector._combinators[token.previous.combinator](node, token) : 223 true; 224 }, 225 226 /** 227 * Filters a set of nodes based on a given CSS selector. 228 * @method filter 229 * 230 * @param {array} nodes A set of nodes/ids to filter. 231 * @param {string} selector The selector used to test each node. 232 * @return{array} An array of nodes from the supplied array that match the given selector. 233 * @static 234 */ 235 filter: function(nodes, selector) { 236 nodes = nodes || []; 237 238 var node, 239 result = [], 240 tokens = Y.Selector._tokenize(selector); 241 242 if (!nodes.item) { // if not HTMLCollection, handle arrays of ids and/or nodes 243 for (var i = 0, len = nodes.length; i < len; ++i) { 244 if (!nodes[i].tagName) { // tagName limits to HTMLElements 245 node = Y.Selector.document.getElementById(nodes[i]); 246 if (node) { // skip IDs that return null 247 nodes[i] = node; 248 } else { 249 } 250 } 251 } 252 } 253 result = Y.Selector._filter(nodes, Y.Selector._tokenize(selector)[0]); 254 return result; 255 }, 256 257 _filter: function(nodes, token, firstOnly, deDupe) { 258 var result = firstOnly ? null : [], 259 foundCache = Y.Selector._foundCache; 260 261 for (var i = 0, len = nodes.length; i < len; i++) { 262 if (! Y.Selector._test(nodes[i], '', token, deDupe)) { 263 continue; 264 } 265 266 if (firstOnly) { 267 return nodes[i]; 268 } 269 if (deDupe) { 270 if (nodes[i]._found) { 271 continue; 272 } 273 nodes[i]._found = true; 274 foundCache[foundCache.length] = nodes[i]; 275 } 276 277 result[result.length] = nodes[i]; 278 } 279 280 return result; 281 }, 282 283 /** 284 * Retrieves a set of nodes based on a given CSS selector. 285 * @method query 286 * 287 * @param {string} selector The CSS Selector to test the node against. 288 * @param {HTMLElement | String} root optional An id or HTMLElement to start the query from. Defaults to Selector.document. 289 * @param {Boolean} firstOnly optional Whether or not to return only the first match. 290 * @return {Array} An array of nodes that match the given selector. 291 * @static 292 */ 293 query: function(selector, root, firstOnly) { 294 var result = Y.Selector._query(selector, root, firstOnly); 295 return result; 296 }, 297 298 299 _query: function(selector, root, firstOnly, deDupe) { 300 var result = (firstOnly) ? null : [], 301 node; 302 303 if (!selector) { 304 return result; 305 } 306 307 var groups = selector.split(','); // TODO: handle comma in attribute/pseudo 308 309 if (groups.length > 1) { 310 var found; 311 for (var i = 0, len = groups.length; i < len; ++i) { 312 found = Y.Selector._query(groups[i], root, firstOnly, true); 313 result = firstOnly ? found : result.concat(found); 314 } 315 Y.Selector._clearFoundCache(); 316 return result; 317 } 318 319 if (root && !root.nodeName) { // assume ID 320 root = Y.Selector.document.getElementById(root); 321 if (!root) { 322 return result; 323 } 324 } 325 326 root = root || Y.Selector.document; 327 328 if (root.nodeName !== '#document') { // prepend with root selector 329 Y.Dom.generateId(root); // TODO: cleanup after?
330 selector = root.tagName + '#' + root.id + ' ' + selector; 331 node = root; 332 root = root.ownerDocument; 333 } 334 335 var tokens = Y.Selector._tokenize(selector); 336 var idToken = tokens[Y.Selector._getIdTokenIndex(tokens)], 337 nodes = [], 338 id, 339 token = tokens.pop() || {}; 340 341 if (idToken) { 342 id = Y.Selector._getId(idToken.attributes); 343 } 344 345 // use id shortcut when possible 346 if (id) { 347 node = node || Y.Selector.document.getElementById(id); 348 349 if (node && (root.nodeName === '#document' || Y.Dom.isAncestor(root, node))) { 350 if ( Y.Selector._test(node, null, idToken) ) { 351 if (idToken === token) { 352 nodes = [node]; // simple selector 353 } else if (idToken.combinator === ' ' || idToken.combinator === '>') { 354 root = node; // start from here 355 } 356 } 357 } else { 358 return result; 359 } 360 } 361 362 if (root && !nodes.length) { 363 nodes = root.getElementsByTagName(token.tag); 364 } 365 366 if (nodes.length) { 367 result = Y.Selector._filter(nodes, token, firstOnly, deDupe); 368 } 369 370 return result; 371 }, 372 373 374 _clearFoundCache: function() { 375 var foundCache = Y.Selector._foundCache; 376 for (var i = 0, len = foundCache.length; i < len; ++i) { 377 try { // IE no like delete 378 delete foundCache[i]._found; 379 } catch(e) { 380 foundCache[i].removeAttribute('_found'); 381 } 382 } 383 foundCache = []; 384 }, 385 386 387 _getRegExp: function(str, flags) { 388 var regexCache = Y.Selector._regexCache; 389 flags = flags || ''; 390 if (!regexCache[str + flags]) { 391 regexCache[str + flags] = new RegExp(str, flags); 392 } 393 return regexCache[str + flags]; 394 }, 395 396 _getChildren: function() { 397 if (document.documentElement.children && document.documentElement.children.tags) { // document for capability test 398 return function(node, tag) { 399 return (tag) ? node.children.tags(tag) : node.children || []; 400 }; 401 } else { 402 return function(node, tag) { 403 var children = [], 404 childNodes = node.childNodes; 405 406 for (var i = 0, len = childNodes.length; i < len; ++i) { 407 if (childNodes[i].tagName) { 408 if (!tag || childNodes[i].tagName === tag) { 409 children.push(childNodes[i]); 410 } 411 } 412 } 413 return children; 414 }; 415 } 416 }(), 417 418 _combinators: { 419 ' ': function(node, token) { 420 while ( (node = node.parentNode) ) { 421 if (Y.Selector._test(node, '', token.previous)) { 422 return true; 423 } 424 } 425 return false; 426 }, 427 428 '>': function(node, token) { 429 return Y.Selector._test(node.parentNode, null, token.previous); 430 }, 431 432 '+': function(node, token) { 433 var sib = node.previousSibling; 434 while (sib && sib.nodeType !== 1) { 435 sib = sib.previousSibling; 436 } 437 438 if (sib && Y.Selector._test(sib, null, token.previous)) { 439 return true; 440 } 441 return false; 442 }, 443 444 '~': function(node, token) { 445 var sib = node.previousSibling; 446 while (sib) { 447 if (sib.nodeType === 1 && Y.Selector._test(sib, null, token.previous)) { 448 return true; 449 } 450 sib = sib.previousSibling; 451 } 452 453 return false; 454 } 455 }, 456 457 458 /* 459 an+b = get every _a_th node starting at the _b_th 460 0n+b = no repeat ("0" and "n" may both be omitted (together) , e.g. "0n+1" or "1", not "0+1"), return only the _b_th element 461 1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n") 462 an+0 = get every _a_th element, "0" may be omitted 463 */ 464 _getNth: function(node, expr, tag, reverse) { 465 Y.Selector._re.nth.test(expr); 466 var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_) 467 n = RegExp.$2, // "n" 468 oddeven = RegExp.$3, // "odd" or "even" 469 b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_ 470 result = [], 471 op; 472 473 var siblings = Y.Selector._getChildren(node.parentNode, tag); 474 475 if (oddeven) { 476 a = 2; // always every other 477 op = '+'; 478 n = 'n'; 479 b = (oddeven === 'odd') ? 1 : 0; 480 } else if ( isNaN(a) ) { 481 a = (n) ? 1 : 0; // start from the first or no repeat 482 } 483 484 if (a === 0) { // just the first 485 if (reverse) { 486 b = siblings.length - b + 1; 487 } 488 489 if (siblings[b - 1] === node) { 490 return true; 491 } else { 492 return false;
493 } 494 495 } else if (a < 0) { 496 reverse = !!reverse; 497 a = Math.abs(a); 498 } 499 500 if (!reverse) { 501 for (var i = b - 1, len = siblings.length; i < len; i += a) { 502 if ( i >= 0 && siblings[i] === node ) { 503 return true; 504 } 505 } 506 } else { 507 for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) { 508 if ( i < len && siblings[i] === node ) { 509 return true; 510 } 511 } 512 } 513 return false; 514 }, 515 516 _getId: function(attr) { 517 for (var i = 0, len = attr.length; i < len; ++i) { 518 if (attr[i][0] == 'id' && attr[i][1] === '=') { 519 return attr[i][2]; 520 } 521 } 522 }, 523 524 _getIdTokenIndex: function(tokens) { 525 for (var i = 0, len = tokens.length; i < len; ++i) { 526 if (Y.Selector._getId(tokens[i].attributes)) { 527 return i; 528 } 529 } 530 return -1; 531 }, 532 533 _patterns: { 534 tag: /^((?:-?[_a-z]+[\w-]*)|\*)/i, 535 attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i, 536 pseudos: /^:([-\w]+)(?:\(['"]?(.+)['"]?\))*/i, 537 combinator: /^\s*([>+~]|\s)\s*/ 538 }, 539 540 /** 541 Break selector into token units per simple selector. 542 Combinator is attached to left-hand selector. 543 */ 544 _tokenize: function(selector) { 545 var token = {}, // one token per simple selector (left selector holds combinator) 546 tokens = [], // array of tokens 547 id, // unique id for the simple selector (if found) 548 found = false, // whether or not any matches were found this pass 549 patterns = Y.Selector._patterns, 550 match; // the regex match 551 552 selector = Y.Selector._replaceShorthand(selector); // convert ID and CLASS shortcuts to attributes 553 554 /* 555 Search for selector patterns, store, and strip them from the selector string 556 until no patterns match (invalid selector) or we run out of chars. 557 558 Multiple attributes and pseudos are allowed, in any order. 559 for example:
560 'form:first-child[type=button]:not(button)[lang|=en]' 561 */ 562 do { 563 found = false; // reset after full pass 564 for (var re in patterns) { 565 if (YAHOO.lang.hasOwnProperty(patterns, re)) { 566 if (re != 'tag' && re != 'combinator') { // only one allowed 567 token[re] = token[re] || []; 568 } 569 if ( (match = patterns[re].exec(selector)) ) { // note assignment 570 found = true; 571 if (re != 'tag' && re != 'combinator') { // only one allowed 572 // capture ID for fast path to element 573 if (re === 'attributes' && match[1] === 'id') { 574 token.id = match[3]; 575 } 576 577 token[re].push(match.slice(1)); 578 } else { // single selector (tag, combinator) 579 token[re] = match[1]; 580 } 581 selector = selector.replace(match[0], ''); // strip current match from selector 582 if (re === 'combinator' || !selector.length) { // next token or done 583 token.attributes = Y.Selector._fixAttributes(token.attributes); 584 token.pseudos = token.pseudos || []; 585 token.tag = token.tag ? token.tag.toUpperCase() : '*'; 586 tokens.push(token); 587 588 token = { // prep next token 589 previous: token 590 }; 591 } 592 } 593 } 594 } 595 } while (found); 596 597 return tokens; 598 }, 599 600 601 _fixAttributes: function(attr) { 602 var aliases = Y.Selector.attrAliases; 603 attr = attr || []; 604 for (var i = 0, len = attr.length; i < len; ++i) { 605 if (aliases[attr[i][0]]) { // convert reserved words, etc 606 attr[i][0] = aliases[attr[i][0]]; 607 } 608 if (!attr[i][1]) { // use exists operator 609 attr[i][1] = ''; 610 } 611 } 612 return attr; 613 }, 614 615 _replaceShorthand: function(selector) { 616 var shorthand = Y.Selector.shorthand; 617 618 //var attrs = selector.match(Y.Selector._patterns.attributes); // pull attributes to avoid false pos on "." and "#" 619 var attrs = selector.match(Y.Selector._re.attr); // pull attributes to avoid false pos on "." and "#" 620 if (attrs) { 621 selector = selector.replace(Y.Selector._re.attr, 'REPLACED_ATTRIBUTE'); 622 } 623 for (var re in shorthand) { 624 if (YAHOO.lang.hasOwnProperty(shorthand, re)) { 625 selector = selector.replace(Y.Selector._getRegExp(re, 'gi'), shorthand[re]); 626 } 627 } 628 629 if (attrs) { 630 for (var i = 0, len = attrs.length; i < len; ++i) { 631 selector = selector.replace('REPLACED_ATTRIBUTE', attrs[i]); 632 } 633 } 634 return selector; 635 } 636}; 637 638if (YAHOO.env.ua.ie && YAHOO.env.ua.ie < 8) { // rewrite class for IE < 8 639 Y.Selector.attrAliases['class'] = 'className'; 640 Y.Selector.attrAliases['for'] = 'htmlFor'; 641} 642 643})(); 644YAHOO.register("selector", YAHOO.util.Selector, {version: "2.8.0r4", build: "2449"});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.