mirror of
https://github.com/salesagility/SuiteCRM.git
synced 2024-11-22 16:02:36 +00:00
973 lines
31 KiB
JavaScript
Executable File
973 lines
31 KiB
JavaScript
Executable File
/*
|
|
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
|
|
Code licensed under the BSD License:
|
|
http://developer.yahoo.com/yui/license.html
|
|
version: 2.9.1
|
|
*/
|
|
var Y = YAHOO,
|
|
Y_DOM = YAHOO.util.Dom,
|
|
EMPTY_ARRAY = [],
|
|
Y_UA = Y.env.ua,
|
|
Y_Lang = Y.lang,
|
|
Y_DOC = document,
|
|
Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
|
|
|
|
Y_DOM_inDoc = Y_DOM.inDocument,
|
|
Y_mix = Y_Lang.augmentObject,
|
|
Y_guid = Y_DOM.generateId,
|
|
|
|
Y_getDoc = function(element) {
|
|
var doc = Y_DOC;
|
|
if (element) {
|
|
doc = (element.nodeType === 9) ? element : // element === document
|
|
element.ownerDocument || // element === DOM node
|
|
element.document || // element === window
|
|
Y_DOC; // default
|
|
}
|
|
|
|
return doc;
|
|
},
|
|
|
|
Y_Array = function(o, startIdx) {
|
|
var l, a, start = startIdx || 0;
|
|
|
|
// IE errors when trying to slice HTMLElement collections
|
|
try {
|
|
return Array.prototype.slice.call(o, start);
|
|
} catch (e) {
|
|
a = [];
|
|
l = o.length;
|
|
for (; start < l; start++) {
|
|
a.push(o[start]);
|
|
}
|
|
return a;
|
|
}
|
|
},
|
|
|
|
Y_DOM_allById = function(id, root) {
|
|
root = root || Y_DOC;
|
|
var nodes = [],
|
|
ret = [],
|
|
i,
|
|
node;
|
|
|
|
if (root.querySelectorAll) {
|
|
ret = root.querySelectorAll('[id="' + id + '"]');
|
|
} else if (root.all) {
|
|
nodes = root.all(id);
|
|
|
|
if (nodes) {
|
|
// root.all may return HTMLElement or HTMLCollection.
|
|
// some elements are also HTMLCollection (FORM, SELECT).
|
|
if (nodes.nodeName) {
|
|
if (nodes.id === id) { // avoid false positive on name
|
|
ret.push(nodes);
|
|
nodes = EMPTY_ARRAY; // done, no need to filter
|
|
} else { // prep for filtering
|
|
nodes = [nodes];
|
|
}
|
|
}
|
|
|
|
if (nodes.length) {
|
|
// filter out matches on node.name
|
|
// and element.id as reference to element with id === 'id'
|
|
for (i = 0; node = nodes[i++];) {
|
|
if (node.id === id ||
|
|
(node.attributes && node.attributes.id &&
|
|
node.attributes.id.value === id)) {
|
|
ret.push(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
ret = [Y_getDoc(root).getElementById(id)];
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* The selector-native module provides support for native querySelector
|
|
* @module dom
|
|
* @submodule selector-native
|
|
* @for Selector
|
|
*/
|
|
|
|
/**
|
|
* Provides support for using CSS selectors to query the DOM
|
|
* @class Selector
|
|
* @static
|
|
* @for Selector
|
|
*/
|
|
|
|
var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
|
|
OWNER_DOCUMENT = 'ownerDocument',
|
|
|
|
Selector = {
|
|
_foundCache: [],
|
|
|
|
useNative: true,
|
|
|
|
_compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
|
|
function(nodeA, nodeB) {
|
|
var a = nodeA.sourceIndex,
|
|
b = nodeB.sourceIndex;
|
|
|
|
if (a === b) {
|
|
return 0;
|
|
} else if (a > b) {
|
|
return 1;
|
|
}
|
|
|
|
return -1;
|
|
|
|
} : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
|
|
function(nodeA, nodeB) {
|
|
if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
} :
|
|
function(nodeA, nodeB) {
|
|
var rangeA, rangeB, compare;
|
|
if (nodeA && nodeB) {
|
|
rangeA = nodeA[OWNER_DOCUMENT].createRange();
|
|
rangeA.setStart(nodeA, 0);
|
|
rangeB = nodeB[OWNER_DOCUMENT].createRange();
|
|
rangeB.setStart(nodeB, 0);
|
|
compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
|
|
}
|
|
|
|
return compare;
|
|
|
|
}),
|
|
|
|
_sort: function(nodes) {
|
|
if (nodes) {
|
|
nodes = Y_Array(nodes, 0, true);
|
|
if (nodes.sort) {
|
|
nodes.sort(Selector._compare);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
},
|
|
|
|
_deDupe: function(nodes) {
|
|
var ret = [],
|
|
i, node;
|
|
|
|
for (i = 0; (node = nodes[i++]);) {
|
|
if (!node._found) {
|
|
ret[ret.length] = node;
|
|
node._found = true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; (node = ret[i++]);) {
|
|
node._found = null;
|
|
node.removeAttribute('_found');
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* 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} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
|
|
* @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, skipNative) {
|
|
if (root && typeof root == 'string') {
|
|
root = Y_DOM.get(root);
|
|
if (!root) {
|
|
return (firstOnly) ? null : [];
|
|
}
|
|
} else {
|
|
root = root || Y_DOC;
|
|
}
|
|
|
|
var ret = [],
|
|
useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
|
|
queries = [[selector, root]],
|
|
query,
|
|
result,
|
|
i,
|
|
fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
|
|
|
|
if (selector && fn) {
|
|
// split group into separate queries
|
|
if (!skipNative && // already done if skipping
|
|
(!useNative || root.tagName)) { // split native when element scoping is needed
|
|
queries = Selector._splitQueries(selector, root);
|
|
}
|
|
|
|
for (i = 0; (query = queries[i++]);) {
|
|
result = fn(query[0], query[1], firstOnly);
|
|
if (!firstOnly) { // coerce DOM Collection to Array
|
|
result = Y_Array(result, 0, true);
|
|
}
|
|
if (result) {
|
|
ret = ret.concat(result);
|
|
}
|
|
}
|
|
|
|
if (queries.length > 1) { // remove dupes and sort by doc order
|
|
ret = Selector._sort(Selector._deDupe(ret));
|
|
}
|
|
}
|
|
|
|
return (firstOnly) ? (ret[0] || null) : ret;
|
|
|
|
},
|
|
|
|
// allows element scoped queries to begin with combinator
|
|
// e.g. query('> p', document.body) === query('body > p')
|
|
_splitQueries: function(selector, node) {
|
|
var groups = selector.split(','),
|
|
queries = [],
|
|
prefix = '',
|
|
i, len;
|
|
|
|
if (node) {
|
|
// enforce for element scoping
|
|
if (node.tagName) {
|
|
node.id = node.id || Y_guid();
|
|
prefix = '[id="' + node.id + '"] ';
|
|
}
|
|
|
|
for (i = 0, len = groups.length; i < len; ++i) {
|
|
selector = prefix + groups[i];
|
|
queries.push([selector, node]);
|
|
}
|
|
}
|
|
|
|
return queries;
|
|
},
|
|
|
|
_nativeQuery: function(selector, root, one) {
|
|
if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
|
|
(Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
|
|
return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
|
|
}
|
|
try {
|
|
return root['querySelector' + (one ? '' : 'All')](selector);
|
|
} catch(e) { // fallback to brute if available
|
|
return Selector.query(selector, root, one, true); // redo with skipNative true
|
|
}
|
|
},
|
|
|
|
filter: function(nodes, selector) {
|
|
var ret = [],
|
|
i, node;
|
|
|
|
if (nodes && selector) {
|
|
for (i = 0; (node = nodes[i++]);) {
|
|
if (Selector.test(node, selector)) {
|
|
ret[ret.length] = node;
|
|
}
|
|
}
|
|
} else {
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
test: function(node, selector, root) {
|
|
var ret = false,
|
|
groups = selector.split(','),
|
|
useFrag = false,
|
|
parent,
|
|
item,
|
|
items,
|
|
frag,
|
|
i, j, group;
|
|
|
|
if (node && node.tagName) { // only test HTMLElements
|
|
|
|
// we need a root if off-doc
|
|
if (!root && !Y_DOM_inDoc(node)) {
|
|
parent = node.parentNode;
|
|
if (parent) {
|
|
root = parent;
|
|
} else { // only use frag when no parent to query
|
|
frag = node[OWNER_DOCUMENT].createDocumentFragment();
|
|
frag.appendChild(node);
|
|
root = frag;
|
|
useFrag = true;
|
|
}
|
|
}
|
|
root = root || node[OWNER_DOCUMENT];
|
|
|
|
if (!node.id) {
|
|
node.id = Y_guid();
|
|
}
|
|
for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
|
|
group += '[id="' + node.id + '"]';
|
|
items = Selector.query(group, root);
|
|
|
|
for (j = 0; item = items[j++];) {
|
|
if (item === node) {
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
if (ret) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (useFrag) { // cleanup
|
|
frag.removeChild(node);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
};
|
|
|
|
YAHOO.util.Selector = Selector;
|
|
/**
|
|
* The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
|
|
* @module dom
|
|
* @submodule selector-css2
|
|
* @for Selector
|
|
*/
|
|
|
|
/**
|
|
* Provides helper methods for collecting and filtering DOM elements.
|
|
*/
|
|
|
|
var PARENT_NODE = 'parentNode',
|
|
TAG_NAME = 'tagName',
|
|
ATTRIBUTES = 'attributes',
|
|
COMBINATOR = 'combinator',
|
|
PSEUDOS = 'pseudos',
|
|
|
|
SelectorCSS2 = {
|
|
_reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
|
|
SORT_RESULTS: true,
|
|
_children: function(node, tag) {
|
|
var ret = node.children,
|
|
i,
|
|
children = [],
|
|
childNodes,
|
|
child;
|
|
|
|
if (node.children && tag && node.children.tags) {
|
|
children = node.children.tags(tag);
|
|
} else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
|
|
childNodes = ret || node.childNodes;
|
|
ret = [];
|
|
for (i = 0; (child = childNodes[i++]);) {
|
|
if (child.tagName) {
|
|
if (!tag || tag === child.tagName) {
|
|
ret.push(child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret || [];
|
|
},
|
|
|
|
_re: {
|
|
//attr: /(\[.*\])/g,
|
|
attr: /(\[[^\]]*\])/g,
|
|
//esc: /\\[:\[][\w\d\]]*/gi,
|
|
esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
|
|
//pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
|
|
pseudos: /(\([^\)]*\))/g
|
|
},
|
|
|
|
/**
|
|
* Mapping of shorthand tokens to corresponding attribute selector
|
|
* @property shorthand
|
|
* @type object
|
|
*/
|
|
shorthand: {
|
|
//'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
|
|
'\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
|
|
//'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
|
|
//'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
|
|
'\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$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(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
|
|
//'': '.+',
|
|
//'=': '^{val}$', // equality
|
|
'~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
|
|
'|=': '^{val}(?:-|$)' // optional hyphen-delimited
|
|
},
|
|
|
|
pseudos: {
|
|
'first-child': function(node) {
|
|
return Selector._children(node[PARENT_NODE])[0] === node;
|
|
}
|
|
},
|
|
|
|
_bruteQuery: function(selector, root, firstOnly) {
|
|
var ret = [],
|
|
nodes = [],
|
|
tokens = Selector._tokenize(selector),
|
|
token = tokens[tokens.length - 1],
|
|
rootDoc = Y_getDoc(root),
|
|
child,
|
|
id,
|
|
className,
|
|
tagName;
|
|
|
|
|
|
// if we have an initial ID, set to root when in document
|
|
/*
|
|
if (tokens[0] && rootDoc === root &&
|
|
(id = tokens[0].id) &&
|
|
rootDoc.getElementById(id)) {
|
|
root = rootDoc.getElementById(id);
|
|
}
|
|
*/
|
|
|
|
if (token) {
|
|
// prefilter nodes
|
|
id = token.id;
|
|
className = token.className;
|
|
tagName = token.tagName || '*';
|
|
|
|
if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
|
|
// try ID first, unless no root.all && root not in document
|
|
// (root.all works off document, but not getElementById)
|
|
// TODO: move to allById?
|
|
if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
|
|
nodes = Y_DOM_allById(id, root);
|
|
// try className
|
|
} else if (className) {
|
|
nodes = root.getElementsByClassName(className);
|
|
} else { // default to tagName
|
|
nodes = root.getElementsByTagName(tagName);
|
|
}
|
|
|
|
} else { // brute getElementsByTagName('*')
|
|
child = root.firstChild;
|
|
while (child) {
|
|
if (child.tagName) { // only collect HTMLElements
|
|
nodes.push(child);
|
|
}
|
|
child = child.nextSilbing || child.firstChild;
|
|
}
|
|
}
|
|
if (nodes.length) {
|
|
ret = Selector._filterNodes(nodes, tokens, firstOnly);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
_filterNodes: function(nodes, tokens, firstOnly) {
|
|
var i = 0,
|
|
j,
|
|
len = tokens.length,
|
|
n = len - 1,
|
|
result = [],
|
|
node = nodes[0],
|
|
tmpNode = node,
|
|
getters = Selector.getters,
|
|
operator,
|
|
combinator,
|
|
token,
|
|
path,
|
|
pass,
|
|
//FUNCTION = 'function',
|
|
value,
|
|
tests,
|
|
test;
|
|
|
|
//do {
|
|
for (i = 0; (tmpNode = node = nodes[i++]);) {
|
|
n = len - 1;
|
|
path = null;
|
|
|
|
testLoop:
|
|
while (tmpNode && tmpNode.tagName) {
|
|
token = tokens[n];
|
|
tests = token.tests;
|
|
j = tests.length;
|
|
if (j && !pass) {
|
|
while ((test = tests[--j])) {
|
|
operator = test[1];
|
|
if (getters[test[0]]) {
|
|
value = getters[test[0]](tmpNode, test[0]);
|
|
} else {
|
|
value = tmpNode[test[0]];
|
|
// use getAttribute for non-standard attributes
|
|
if (value === undefined && tmpNode.getAttribute) {
|
|
value = tmpNode.getAttribute(test[0]);
|
|
}
|
|
}
|
|
|
|
if ((operator === '=' && value !== test[2]) || // fast path for equality
|
|
(typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
|
|
operator.test && !operator.test(value)) || // regex test
|
|
(!operator.test && // protect against RegExp as function (webkit)
|
|
typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
|
|
|
|
// skip non element nodes or non-matching tags
|
|
if ((tmpNode = tmpNode[path])) {
|
|
while (tmpNode &&
|
|
(!tmpNode.tagName ||
|
|
(token.tagName && token.tagName !== tmpNode.tagName))
|
|
) {
|
|
tmpNode = tmpNode[path];
|
|
}
|
|
}
|
|
continue testLoop;
|
|
}
|
|
}
|
|
}
|
|
|
|
n--; // move to next token
|
|
// now that we've passed the test, move up the tree by combinator
|
|
if (!pass && (combinator = token.combinator)) {
|
|
path = combinator.axis;
|
|
tmpNode = tmpNode[path];
|
|
|
|
// skip non element nodes
|
|
while (tmpNode && !tmpNode.tagName) {
|
|
tmpNode = tmpNode[path];
|
|
}
|
|
|
|
if (combinator.direct) { // one pass only
|
|
path = null;
|
|
}
|
|
|
|
} else { // success if we made it this far
|
|
result.push(node);
|
|
if (firstOnly) {
|
|
return result;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}// while (tmpNode = node = nodes[++i]);
|
|
node = tmpNode = null;
|
|
return result;
|
|
},
|
|
|
|
combinators: {
|
|
' ': {
|
|
axis: 'parentNode'
|
|
},
|
|
|
|
'>': {
|
|
axis: 'parentNode',
|
|
direct: true
|
|
},
|
|
|
|
|
|
'+': {
|
|
axis: 'previousSibling',
|
|
direct: true
|
|
}
|
|
},
|
|
|
|
_parsers: [
|
|
{
|
|
name: ATTRIBUTES,
|
|
//re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
|
|
re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
|
|
fn: function(match, token) {
|
|
var operator = match[2] || '',
|
|
operators = Selector.operators,
|
|
escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
|
|
test;
|
|
|
|
// add prefiltering for ID and CLASS
|
|
if ((match[1] === 'id' && operator === '=') ||
|
|
(match[1] === 'className' &&
|
|
Y_DOCUMENT_ELEMENT.getElementsByClassName &&
|
|
(operator === '~=' || operator === '='))) {
|
|
token.prefilter = match[1];
|
|
|
|
|
|
match[3] = escVal;
|
|
|
|
// escape all but ID for prefilter, which may run through QSA (via Dom.allById)
|
|
token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
|
|
|
|
}
|
|
|
|
// add tests
|
|
if (operator in operators) {
|
|
test = operators[operator];
|
|
if (typeof test === 'string') {
|
|
match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
|
|
test = new RegExp(test.replace('{val}', match[3]));
|
|
}
|
|
match[2] = test;
|
|
}
|
|
if (!token.last || token.prefilter !== match[1]) {
|
|
return match.slice(1);
|
|
}
|
|
}
|
|
|
|
},
|
|
{
|
|
name: TAG_NAME,
|
|
re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
|
|
fn: function(match, token) {
|
|
var tag = match[1].toUpperCase();
|
|
token.tagName = tag;
|
|
|
|
if (tag !== '*' && (!token.last || token.prefilter)) {
|
|
return [TAG_NAME, '=', tag];
|
|
}
|
|
if (!token.prefilter) {
|
|
token.prefilter = 'tagName';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: COMBINATOR,
|
|
re: /^\s*([>+~]|\s)\s*/,
|
|
fn: function(match, token) {
|
|
}
|
|
},
|
|
{
|
|
name: PSEUDOS,
|
|
re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
|
|
fn: function(match, token) {
|
|
var test = Selector[PSEUDOS][match[1]];
|
|
if (test) { // reorder match array and unescape special chars for tests
|
|
if (match[2]) {
|
|
match[2] = match[2].replace(/\\/g, '');
|
|
}
|
|
return [match[2], test];
|
|
} else { // selector token not supported (possibly missing CSS3 module)
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
],
|
|
|
|
_getToken: function(token) {
|
|
return {
|
|
tagName: null,
|
|
id: null,
|
|
className: null,
|
|
attributes: {},
|
|
combinator: null,
|
|
tests: []
|
|
};
|
|
},
|
|
|
|
/**
|
|
Break selector into token units per simple selector.
|
|
Combinator is attached to the previous token.
|
|
*/
|
|
_tokenize: function(selector) {
|
|
selector = selector || '';
|
|
selector = Selector._replaceShorthand(Y_Lang.trim(selector));
|
|
var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
|
|
query = selector, // original query for debug report
|
|
tokens = [], // array of tokens
|
|
found = false, // whether or not any matches were found this pass
|
|
match, // the regex match
|
|
test,
|
|
i, parser;
|
|
|
|
/*
|
|
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]'
|
|
*/
|
|
|
|
outer:
|
|
do {
|
|
found = false; // reset after full pass
|
|
|
|
for (i = 0; (parser = Selector._parsers[i++]);) {
|
|
if ( (match = parser.re.exec(selector)) ) { // note assignment
|
|
if (parser.name !== COMBINATOR ) {
|
|
token.selector = selector;
|
|
}
|
|
selector = selector.replace(match[0], ''); // strip current match from selector
|
|
if (!selector.length) {
|
|
token.last = true;
|
|
}
|
|
|
|
if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
|
|
match[1] = Selector._attrFilters[match[1]];
|
|
}
|
|
|
|
test = parser.fn(match, token);
|
|
if (test === false) { // selector not supported
|
|
found = false;
|
|
break outer;
|
|
} else if (test) {
|
|
token.tests.push(test);
|
|
}
|
|
|
|
if (!selector.length || parser.name === COMBINATOR) {
|
|
tokens.push(token);
|
|
token = Selector._getToken(token);
|
|
if (parser.name === COMBINATOR) {
|
|
token.combinator = Selector.combinators[match[1]];
|
|
}
|
|
}
|
|
found = true;
|
|
|
|
|
|
}
|
|
}
|
|
} while (found && selector.length);
|
|
|
|
if (!found || selector.length) { // not fully parsed
|
|
tokens = [];
|
|
}
|
|
return tokens;
|
|
},
|
|
|
|
_replaceShorthand: function(selector) {
|
|
var shorthand = Selector.shorthand,
|
|
esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
|
|
attrs,
|
|
pseudos,
|
|
re, i, len;
|
|
|
|
if (esc) {
|
|
selector = selector.replace(Selector._re.esc, '\uE000');
|
|
}
|
|
|
|
attrs = selector.match(Selector._re.attr);
|
|
pseudos = selector.match(Selector._re.pseudos);
|
|
|
|
if (attrs) {
|
|
selector = selector.replace(Selector._re.attr, '\uE001');
|
|
}
|
|
|
|
if (pseudos) {
|
|
selector = selector.replace(Selector._re.pseudos, '\uE002');
|
|
}
|
|
|
|
|
|
for (re in shorthand) {
|
|
if (shorthand.hasOwnProperty(re)) {
|
|
selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
|
|
}
|
|
}
|
|
|
|
if (attrs) {
|
|
for (i = 0, len = attrs.length; i < len; ++i) {
|
|
selector = selector.replace(/\uE001/, attrs[i]);
|
|
}
|
|
}
|
|
|
|
if (pseudos) {
|
|
for (i = 0, len = pseudos.length; i < len; ++i) {
|
|
selector = selector.replace(/\uE002/, pseudos[i]);
|
|
}
|
|
}
|
|
|
|
selector = selector.replace(/\[/g, '\uE003');
|
|
selector = selector.replace(/\]/g, '\uE004');
|
|
|
|
selector = selector.replace(/\(/g, '\uE005');
|
|
selector = selector.replace(/\)/g, '\uE006');
|
|
|
|
if (esc) {
|
|
for (i = 0, len = esc.length; i < len; ++i) {
|
|
selector = selector.replace('\uE000', esc[i]);
|
|
}
|
|
}
|
|
|
|
return selector;
|
|
},
|
|
|
|
_attrFilters: {
|
|
'class': 'className',
|
|
'for': 'htmlFor'
|
|
},
|
|
|
|
getters: {
|
|
href: function(node, attr) {
|
|
return Y_DOM.getAttribute(node, attr);
|
|
}
|
|
}
|
|
};
|
|
|
|
Y_mix(Selector, SelectorCSS2, true);
|
|
Selector.getters.src = Selector.getters.rel = Selector.getters.href;
|
|
|
|
// IE wants class with native queries
|
|
if (Selector.useNative && Y_DOC.querySelector) {
|
|
Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
|
|
}
|
|
|
|
/**
|
|
* The selector css3 module provides support for css3 selectors.
|
|
* @module dom
|
|
* @submodule selector-css3
|
|
* @for Selector
|
|
*/
|
|
|
|
/*
|
|
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
|
|
*/
|
|
|
|
Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
|
|
|
|
Selector._getNth = function(node, expr, tag, reverse) {
|
|
Selector._reNth.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 = [],
|
|
siblings = Selector._children(node.parentNode, tag),
|
|
op;
|
|
|
|
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;
|
|
};
|
|
|
|
Y_mix(Selector.pseudos, {
|
|
'root': function(node) {
|
|
return node === node.ownerDocument.documentElement;
|
|
},
|
|
|
|
'nth-child': function(node, expr) {
|
|
return Selector._getNth(node, expr);
|
|
},
|
|
|
|
'nth-last-child': function(node, expr) {
|
|
return Selector._getNth(node, expr, null, true);
|
|
},
|
|
|
|
'nth-of-type': function(node, expr) {
|
|
return Selector._getNth(node, expr, node.tagName);
|
|
},
|
|
|
|
'nth-last-of-type': function(node, expr) {
|
|
return Selector._getNth(node, expr, node.tagName, true);
|
|
},
|
|
|
|
'last-child': function(node) {
|
|
var children = Selector._children(node.parentNode);
|
|
return children[children.length - 1] === node;
|
|
},
|
|
|
|
'first-of-type': function(node) {
|
|
return Selector._children(node.parentNode, node.tagName)[0] === node;
|
|
},
|
|
|
|
'last-of-type': function(node) {
|
|
var children = Selector._children(node.parentNode, node.tagName);
|
|
return children[children.length - 1] === node;
|
|
},
|
|
|
|
'only-child': function(node) {
|
|
var children = Selector._children(node.parentNode);
|
|
return children.length === 1 && children[0] === node;
|
|
},
|
|
|
|
'only-of-type': function(node) {
|
|
var children = Selector._children(node.parentNode, node.tagName);
|
|
return children.length === 1 && children[0] === node;
|
|
},
|
|
|
|
'empty': function(node) {
|
|
return node.childNodes.length === 0;
|
|
},
|
|
|
|
'not': function(node, expr) {
|
|
return !Selector.test(node, expr);
|
|
},
|
|
|
|
'contains': function(node, expr) {
|
|
var text = node.innerText || node.textContent || '';
|
|
return text.indexOf(expr) > -1;
|
|
},
|
|
|
|
'checked': function(node) {
|
|
return (node.checked === true || node.selected === true);
|
|
},
|
|
|
|
enabled: function(node) {
|
|
return (node.disabled !== undefined && !node.disabled);
|
|
},
|
|
|
|
disabled: function(node) {
|
|
return (node.disabled);
|
|
}
|
|
});
|
|
|
|
Y_mix(Selector.operators, {
|
|
'^=': '^{val}', // Match starts with value
|
|
'!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
|
|
'$=': '{val}$', // Match ends with value
|
|
'*=': '{val}' // Match contains value as substring
|
|
});
|
|
|
|
Selector.combinators['~'] = {
|
|
axis: 'previousSibling'
|
|
};
|
|
YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.1", build: "2800"});
|