123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- /*
- Copyright (c) 2009, Yahoo! Inc. All rights reserved.
- Code licensed under the BSD License:
- http://developer.yahoo.net/yui/license.txt
- version: 2.8.0r4
- */
- /**
- * The selector module provides helper methods allowing CSS3 Selectors to be used with DOM elements.
- * @module selector
- * @title Selector Utility
- * @namespace YAHOO.util
- * @requires yahoo, dom
- */
- (function() {
- var Y = YAHOO.util;
- /**
- * Provides helper methods for collecting and filtering DOM elements.
- * @namespace YAHOO.util
- * @class Selector
- * @static
- */
- Y.Selector = {
- _foundCache: [],
- _regexCache: {},
- _re: {
- nth: /^(?:([-]?\d*)(n){1}|(odd|even)$)*([-+]?\d*)$/,
- attr: /(\[.*\])/g,
- urls: /^(?:href|src)/
- },
- /**
- * Default document for use queries
- * @property document
- * @type object
- * @default window.document
- */
- document: window.document,
- /**
- * Mapping of attributes to aliases, normally to work around HTMLAttributes
- * that conflict with JS reserved words.
- * @property attrAliases
- * @type object
- */
- attrAliases: {
- },
- /**
- * Mapping of shorthand tokens to corresponding attribute selector
- * @property shorthand
- * @type object
- */
- shorthand: {
- //'(?:(?:[^\\)\\]\\s*>+~,]+)(?:-?[_a-z]+[-\\w]))+#(-?[_a-z]+[-\\w]*)': '[id=$1]',
- '\\#(-?[_a-z]+[-\\w]*)': '[id=$1]',
- '\\.(-?[_a-z]+[-\\w]*)': '[class~=$1]'
- },
- /**
- * List of operators and corresponding boolean functions.
- * These functions are passed the attribute and the current node's value of the attribute.
- * @property operators
- * @type object
- */
- operators: {
- '=': function(attr, val) { return attr === val; }, // Equality
- '!=': function(attr, val) { return attr !== val; }, // Inequality
- '~=': function(attr, val) { // Match one of space seperated words
- var s = ' ';
- return (s + attr + s).indexOf((s + val + s)) > -1;
- },
- '|=': function(attr, val) { return attr === val || attr.slice(0, val.length + 1) === val + '-'; }, // Matches value followed by optional hyphen
- '^=': function(attr, val) { return attr.indexOf(val) === 0; }, // Match starts with value
- '$=': function(attr, val) { return attr.slice(-val.length) === val; }, // Match ends with value
- '*=': function(attr, val) { return attr.indexOf(val) > -1; }, // Match contains value as substring
- '': function(attr, val) { return attr; } // Just test for existence of attribute
- },
- /**
- * List of pseudo-classes and corresponding boolean functions.
- * These functions are called with the current node, and any value that was parsed with the pseudo regex.
- * @property pseudos
- * @type object
- */
- pseudos: {
- 'root': function(node) {
- return node === node.ownerDocument.documentElement;
- },
- 'nth-child': function(node, val) {
- return Y.Selector._getNth(node, val);
- },
- 'nth-last-child': function(node, val) {
- return Y.Selector._getNth(node, val, null, true);
- },
- 'nth-of-type': function(node, val) {
- return Y.Selector._getNth(node, val, node.tagName);
- },
-
- 'nth-last-of-type': function(node, val) {
- return Y.Selector._getNth(node, val, node.tagName, true);
- },
-
- 'first-child': function(node) {
- return Y.Selector._getChildren(node.parentNode)[0] === node;
- },
- 'last-child': function(node) {
- var children = Y.Selector._getChildren(node.parentNode);
- return children[children.length - 1] === node;
- },
- 'first-of-type': function(node, val) {
- return Y.Selector._getChildren(node.parentNode, node.tagName)[0];
- },
-
- 'last-of-type': function(node, val) {
- var children = Y.Selector._getChildren(node.parentNode, node.tagName);
- return children[children.length - 1];
- },
-
- 'only-child': function(node) {
- var children = Y.Selector._getChildren(node.parentNode);
- return children.length === 1 && children[0] === node;
- },
- 'only-of-type': function(node) {
- return Y.Selector._getChildren(node.parentNode, node.tagName).length === 1;
- },
- 'empty': function(node) {
- return node.childNodes.length === 0;
- },
- 'not': function(node, simple) {
- return !Y.Selector.test(node, simple);
- },
- 'contains': function(node, str) {
- var text = node.innerText || node.textContent || '';
- return text.indexOf(str) > -1;
- },
- 'checked': function(node) {
- return node.checked === true;
- }
- },
- /**
- * Test if the supplied node matches the supplied selector.
- * @method test
- *
- * @param {HTMLElement | String} node An id or node reference to the HTMLElement being tested.
- * @param {string} selector The CSS Selector to test the node against.
- * @return{boolean} Whether or not the node matches the selector.
- * @static
-
- */
- test: function(node, selector) {
- node = Y.Selector.document.getElementById(node) || node;
- if (!node) {
- return false;
- }
- var groups = selector ? selector.split(',') : [];
- if (groups.length) {
- for (var i = 0, len = groups.length; i < len; ++i) {
- if ( Y.Selector._test(node, groups[i]) ) { // passes if ANY group matches
- return true;
- }
- }
- return false;
- }
- return Y.Selector._test(node, selector);
- },
- _test: function(node, selector, token, deDupe) {
- token = token || Y.Selector._tokenize(selector).pop() || {};
- if (!node.tagName ||
- (token.tag !== '*' && node.tagName !== token.tag) ||
- (deDupe && node._found) ) {
- return false;
- }
- if (token.attributes.length) {
- var val,
- ieFlag,
- re_urls = Y.Selector._re.urls;
- if (!node.attributes || !node.attributes.length) {
- return false;
- }
- for (var i = 0, attr; attr = token.attributes[i++];) {
- ieFlag = (re_urls.test(attr[0])) ? 2 : 0;
- val = node.getAttribute(attr[0], ieFlag);
- if (val === null || val === undefined) {
- return false;
- }
- if ( Y.Selector.operators[attr[1]] &&
- !Y.Selector.operators[attr[1]](val, attr[2])) {
- return false;
- }
- }
- }
- if (token.pseudos.length) {
- for (var i = 0, len = token.pseudos.length; i < len; ++i) {
- if (Y.Selector.pseudos[token.pseudos[i][0]] &&
- !Y.Selector.pseudos[token.pseudos[i][0]](node, token.pseudos[i][1])) {
- return false;
- }
- }
- }
- return (token.previous && token.previous.combinator !== ',') ?
- Y.Selector._combinators[token.previous.combinator](node, token) :
- true;
- },
- /**
- * Filters a set of nodes based on a given CSS selector.
- * @method filter
- *
- * @param {array} nodes A set of nodes/ids to filter.
- * @param {string} selector The selector used to test each node.
- * @return{array} An array of nodes from the supplied array that match the given selector.
- * @static
- */
- filter: function(nodes, selector) {
- nodes = nodes || [];
- var node,
- result = [],
- tokens = Y.Selector._tokenize(selector);
- if (!nodes.item) { // if not HTMLCollection, handle arrays of ids and/or nodes
- for (var i = 0, len = nodes.length; i < len; ++i) {
- if (!nodes[i].tagName) { // tagName limits to HTMLElements
- node = Y.Selector.document.getElementById(nodes[i]);
- if (node) { // skip IDs that return null
- nodes[i] = node;
- } else {
- }
- }
- }
- }
- result = Y.Selector._filter(nodes, Y.Selector._tokenize(selector)[0]);
- return result;
- },
- _filter: function(nodes, token, firstOnly, deDupe) {
- var result = firstOnly ? null : [],
- foundCache = Y.Selector._foundCache;
- for (var i = 0, len = nodes.length; i < len; i++) {
- if (! Y.Selector._test(nodes[i], '', token, deDupe)) {
- continue;
- }
- if (firstOnly) {
- return nodes[i];
- }
- if (deDupe) {
- if (nodes[i]._found) {
- continue;
- }
- nodes[i]._found = true;
- foundCache[foundCache.length] = nodes[i];
- }
- result[result.length] = nodes[i];
- }
- return result;
- },
- /**
- * 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 | String} root optional An id or HTMLElement to start the query from. Defaults to Selector.document.
- * @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) {
- var result = Y.Selector._query(selector, root, firstOnly);
- return result;
- },
- _query: function(selector, root, firstOnly, deDupe) {
- var result = (firstOnly) ? null : [],
- node;
- if (!selector) {
- return result;
- }
- var groups = selector.split(','); // TODO: handle comma in attribute/pseudo
- if (groups.length > 1) {
- var found;
- for (var i = 0, len = groups.length; i < len; ++i) {
- found = Y.Selector._query(groups[i], root, firstOnly, true);
- result = firstOnly ? found : result.concat(found);
- }
- Y.Selector._clearFoundCache();
- return result;
- }
- if (root && !root.nodeName) { // assume ID
- root = Y.Selector.document.getElementById(root);
- if (!root) {
- return result;
- }
- }
- root = root || Y.Selector.document;
- if (root.nodeName !== '#document') { // prepend with root selector
- Y.Dom.generateId(root); // TODO: cleanup after?
- selector = root.tagName + '#' + root.id + ' ' + selector;
- node = root;
- root = root.ownerDocument;
- }
- var tokens = Y.Selector._tokenize(selector);
- var idToken = tokens[Y.Selector._getIdTokenIndex(tokens)],
- nodes = [],
- id,
- token = tokens.pop() || {};
-
- if (idToken) {
- id = Y.Selector._getId(idToken.attributes);
- }
- // use id shortcut when possible
- if (id) {
- node = node || Y.Selector.document.getElementById(id);
- if (node && (root.nodeName === '#document' || Y.Dom.isAncestor(root, node))) {
- if ( Y.Selector._test(node, null, idToken) ) {
- if (idToken === token) {
- nodes = [node]; // simple selector
- } else if (idToken.combinator === ' ' || idToken.combinator === '>') {
- root = node; // start from here
- }
- }
- } else {
- return result;
- }
- }
- if (root && !nodes.length) {
- nodes = root.getElementsByTagName(token.tag);
- }
- if (nodes.length) {
- result = Y.Selector._filter(nodes, token, firstOnly, deDupe);
- }
- return result;
- },
- _clearFoundCache: function() {
- var foundCache = Y.Selector._foundCache;
- for (var i = 0, len = foundCache.length; i < len; ++i) {
- try { // IE no like delete
- delete foundCache[i]._found;
- } catch(e) {
- foundCache[i].removeAttribute('_found');
- }
- }
- foundCache = [];
- },
- _getRegExp: function(str, flags) {
- var regexCache = Y.Selector._regexCache;
- flags = flags || '';
- if (!regexCache[str + flags]) {
- regexCache[str + flags] = new RegExp(str, flags);
- }
- return regexCache[str + flags];
- },
- _getChildren: function() {
- if (document.documentElement.children && document.documentElement.children.tags) { // document for capability test
- return function(node, tag) {
- return (tag) ? node.children.tags(tag) : node.children || [];
- };
- } else {
- return function(node, tag) {
- var children = [],
- childNodes = node.childNodes;
- for (var i = 0, len = childNodes.length; i < len; ++i) {
- if (childNodes[i].tagName) {
- if (!tag || childNodes[i].tagName === tag) {
- children.push(childNodes[i]);
- }
- }
- }
- return children;
- };
- }
- }(),
- _combinators: {
- ' ': function(node, token) {
- while ( (node = node.parentNode) ) {
- if (Y.Selector._test(node, '', token.previous)) {
- return true;
- }
- }
- return false;
- },
- '>': function(node, token) {
- return Y.Selector._test(node.parentNode, null, token.previous);
- },
- '+': function(node, token) {
- var sib = node.previousSibling;
- while (sib && sib.nodeType !== 1) {
- sib = sib.previousSibling;
- }
- if (sib && Y.Selector._test(sib, null, token.previous)) {
- return true;
- }
- return false;
- },
- '~': function(node, token) {
- var sib = node.previousSibling;
- while (sib) {
- if (sib.nodeType === 1 && Y.Selector._test(sib, null, token.previous)) {
- return true;
- }
- sib = sib.previousSibling;
- }
- return false;
- }
- },
- /*
- an+b = get every _a_th node starting at the _b_th
- 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
- 1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
- an+0 = get every _a_th element, "0" may be omitted
- */
- _getNth: function(node, expr, tag, reverse) {
- Y.Selector._re.nth.test(expr);
- var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
- n = RegExp.$2, // "n"
- oddeven = RegExp.$3, // "odd" or "even"
- b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
- result = [],
- op;
- var siblings = Y.Selector._getChildren(node.parentNode, tag);
- if (oddeven) {
- a = 2; // always every other
- op = '+';
- n = 'n';
- b = (oddeven === 'odd') ? 1 : 0;
- } else if ( isNaN(a) ) {
- a = (n) ? 1 : 0; // start from the first or no repeat
- }
- if (a === 0) { // just the first
- if (reverse) {
- b = siblings.length - b + 1;
- }
- if (siblings[b - 1] === node) {
- return true;
- } else {
- return false;
- }
- } else if (a < 0) {
- reverse = !!reverse;
- a = Math.abs(a);
- }
- if (!reverse) {
- for (var i = b - 1, len = siblings.length; i < len; i += a) {
- if ( i >= 0 && siblings[i] === node ) {
- return true;
- }
- }
- } else {
- for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
- if ( i < len && siblings[i] === node ) {
- return true;
- }
- }
- }
- return false;
- },
- _getId: function(attr) {
- for (var i = 0, len = attr.length; i < len; ++i) {
- if (attr[i][0] == 'id' && attr[i][1] === '=') {
- return attr[i][2];
- }
- }
- },
- _getIdTokenIndex: function(tokens) {
- for (var i = 0, len = tokens.length; i < len; ++i) {
- if (Y.Selector._getId(tokens[i].attributes)) {
- return i;
- }
- }
- return -1;
- },
- _patterns: {
- tag: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
- attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
- pseudos: /^:([-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
- combinator: /^\s*([>+~]|\s)\s*/
- },
- /**
- Break selector into token units per simple selector.
- Combinator is attached to left-hand selector.
- */
- _tokenize: function(selector) {
- var token = {}, // one token per simple selector (left selector holds combinator)
- tokens = [], // array of tokens
- id, // unique id for the simple selector (if found)
- found = false, // whether or not any matches were found this pass
- patterns = Y.Selector._patterns,
- match; // the regex match
- selector = Y.Selector._replaceShorthand(selector); // convert ID and CLASS shortcuts to attributes
- /*
- Search for selector patterns, store, and strip them from the selector string
- until no patterns match (invalid selector) or we run out of chars.
- Multiple attributes and pseudos are allowed, in any order.
- for example:
- 'form:first-child[type=button]:not(button)[lang|=en]'
- */
- do {
- found = false; // reset after full pass
- for (var re in patterns) {
- if (YAHOO.lang.hasOwnProperty(patterns, re)) {
- if (re != 'tag' && re != 'combinator') { // only one allowed
- token[re] = token[re] || [];
- }
- if ( (match = patterns[re].exec(selector)) ) { // note assignment
- found = true;
- if (re != 'tag' && re != 'combinator') { // only one allowed
- // capture ID for fast path to element
- if (re === 'attributes' && match[1] === 'id') {
- token.id = match[3];
- }
- token[re].push(match.slice(1));
- } else { // single selector (tag, combinator)
- token[re] = match[1];
- }
- selector = selector.replace(match[0], ''); // strip current match from selector
- if (re === 'combinator' || !selector.length) { // next token or done
- token.attributes = Y.Selector._fixAttributes(token.attributes);
- token.pseudos = token.pseudos || [];
- token.tag = token.tag ? token.tag.toUpperCase() : '*';
- tokens.push(token);
- token = { // prep next token
- previous: token
- };
- }
- }
- }
- }
- } while (found);
- return tokens;
- },
- _fixAttributes: function(attr) {
- var aliases = Y.Selector.attrAliases;
- attr = attr || [];
- for (var i = 0, len = attr.length; i < len; ++i) {
- if (aliases[attr[i][0]]) { // convert reserved words, etc
- attr[i][0] = aliases[attr[i][0]];
- }
- if (!attr[i][1]) { // use exists operator
- attr[i][1] = '';
- }
- }
- return attr;
- },
- _replaceShorthand: function(selector) {
- var shorthand = Y.Selector.shorthand;
- //var attrs = selector.match(Y.Selector._patterns.attributes); // pull attributes to avoid false pos on "." and "#"
- var attrs = selector.match(Y.Selector._re.attr); // pull attributes to avoid false pos on "." and "#"
- if (attrs) {
- selector = selector.replace(Y.Selector._re.attr, 'REPLACED_ATTRIBUTE');
- }
- for (var re in shorthand) {
- if (YAHOO.lang.hasOwnProperty(shorthand, re)) {
- selector = selector.replace(Y.Selector._getRegExp(re, 'gi'), shorthand[re]);
- }
- }
- if (attrs) {
- for (var i = 0, len = attrs.length; i < len; ++i) {
- selector = selector.replace('REPLACED_ATTRIBUTE', attrs[i]);
- }
- }
- return selector;
- }
- };
- if (YAHOO.env.ua.ie && YAHOO.env.ua.ie < 8) { // rewrite class for IE < 8
- Y.Selector.attrAliases['class'] = 'className';
- Y.Selector.attrAliases['for'] = 'htmlFor';
- }
- })();
- YAHOO.register("selector", YAHOO.util.Selector, {version: "2.8.0r4", build: "2449"});
|