| 1 |
efrain |
1 |
YUI.add('yui2-selector', function(Y) {
|
|
|
2 |
var YAHOO = Y.YUI2;
|
|
|
3 |
/*
|
|
|
4 |
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
|
|
|
5 |
Code licensed under the BSD License:
|
|
|
6 |
http://developer.yahoo.com/yui/license.html
|
|
|
7 |
version: 2.9.0
|
|
|
8 |
*/
|
|
|
9 |
var Y = YAHOO,
|
|
|
10 |
Y_DOM = YAHOO.util.Dom,
|
|
|
11 |
EMPTY_ARRAY = [],
|
|
|
12 |
Y_UA = Y.env.ua,
|
|
|
13 |
Y_Lang = Y.lang,
|
|
|
14 |
Y_DOC = document,
|
|
|
15 |
Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
|
|
|
16 |
|
|
|
17 |
Y_DOM_inDoc = Y_DOM.inDocument,
|
|
|
18 |
Y_mix = Y_Lang.augmentObject,
|
|
|
19 |
Y_guid = Y_DOM.generateId,
|
|
|
20 |
|
|
|
21 |
Y_getDoc = function(element) {
|
|
|
22 |
var doc = Y_DOC;
|
|
|
23 |
if (element) {
|
|
|
24 |
doc = (element.nodeType === 9) ? element : // element === document
|
|
|
25 |
element.ownerDocument || // element === DOM node
|
|
|
26 |
element.document || // element === window
|
|
|
27 |
Y_DOC; // default
|
|
|
28 |
}
|
|
|
29 |
|
|
|
30 |
return doc;
|
|
|
31 |
},
|
|
|
32 |
|
|
|
33 |
Y_Array = function(o, startIdx) {
|
|
|
34 |
var l, a, start = startIdx || 0;
|
|
|
35 |
|
|
|
36 |
// IE errors when trying to slice HTMLElement collections
|
|
|
37 |
try {
|
|
|
38 |
return Array.prototype.slice.call(o, start);
|
|
|
39 |
} catch (e) {
|
|
|
40 |
a = [];
|
|
|
41 |
l = o.length;
|
|
|
42 |
for (; start < l; start++) {
|
|
|
43 |
a.push(o[start]);
|
|
|
44 |
}
|
|
|
45 |
return a;
|
|
|
46 |
}
|
|
|
47 |
},
|
|
|
48 |
|
|
|
49 |
Y_DOM_allById = function(id, root) {
|
|
|
50 |
root = root || Y_DOC;
|
|
|
51 |
var nodes = [],
|
|
|
52 |
ret = [],
|
|
|
53 |
i,
|
|
|
54 |
node;
|
|
|
55 |
|
|
|
56 |
if (root.querySelectorAll) {
|
|
|
57 |
ret = root.querySelectorAll('[id="' + id + '"]');
|
|
|
58 |
} else if (root.all) {
|
|
|
59 |
nodes = root.all(id);
|
|
|
60 |
|
|
|
61 |
if (nodes) {
|
|
|
62 |
// root.all may return HTMLElement or HTMLCollection.
|
|
|
63 |
// some elements are also HTMLCollection (FORM, SELECT).
|
|
|
64 |
if (nodes.nodeName) {
|
|
|
65 |
if (nodes.id === id) { // avoid false positive on name
|
|
|
66 |
ret.push(nodes);
|
|
|
67 |
nodes = EMPTY_ARRAY; // done, no need to filter
|
|
|
68 |
} else { // prep for filtering
|
|
|
69 |
nodes = [nodes];
|
|
|
70 |
}
|
|
|
71 |
}
|
|
|
72 |
|
|
|
73 |
if (nodes.length) {
|
|
|
74 |
// filter out matches on node.name
|
|
|
75 |
// and element.id as reference to element with id === 'id'
|
|
|
76 |
for (i = 0; node = nodes[i++];) {
|
|
|
77 |
if (node.id === id ||
|
|
|
78 |
(node.attributes && node.attributes.id &&
|
|
|
79 |
node.attributes.id.value === id)) {
|
|
|
80 |
ret.push(node);
|
|
|
81 |
}
|
|
|
82 |
}
|
|
|
83 |
}
|
|
|
84 |
}
|
|
|
85 |
} else {
|
|
|
86 |
ret = [Y_getDoc(root).getElementById(id)];
|
|
|
87 |
}
|
|
|
88 |
|
|
|
89 |
return ret;
|
|
|
90 |
};
|
|
|
91 |
|
|
|
92 |
/**
|
|
|
93 |
* The selector-native module provides support for native querySelector
|
|
|
94 |
* @module dom
|
|
|
95 |
* @submodule selector-native
|
|
|
96 |
* @for Selector
|
|
|
97 |
*/
|
|
|
98 |
|
|
|
99 |
/**
|
|
|
100 |
* Provides support for using CSS selectors to query the DOM
|
|
|
101 |
* @class Selector
|
|
|
102 |
* @static
|
|
|
103 |
* @for Selector
|
|
|
104 |
*/
|
|
|
105 |
|
|
|
106 |
var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
|
|
|
107 |
OWNER_DOCUMENT = 'ownerDocument',
|
|
|
108 |
|
|
|
109 |
Selector = {
|
|
|
110 |
_foundCache: [],
|
|
|
111 |
|
|
|
112 |
useNative: true,
|
|
|
113 |
|
|
|
114 |
_compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
|
|
|
115 |
function(nodeA, nodeB) {
|
|
|
116 |
var a = nodeA.sourceIndex,
|
|
|
117 |
b = nodeB.sourceIndex;
|
|
|
118 |
|
|
|
119 |
if (a === b) {
|
|
|
120 |
return 0;
|
|
|
121 |
} else if (a > b) {
|
|
|
122 |
return 1;
|
|
|
123 |
}
|
|
|
124 |
|
|
|
125 |
return -1;
|
|
|
126 |
|
|
|
127 |
} : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
|
|
|
128 |
function(nodeA, nodeB) {
|
|
|
129 |
if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
|
|
|
130 |
return -1;
|
|
|
131 |
} else {
|
|
|
132 |
return 1;
|
|
|
133 |
}
|
|
|
134 |
} :
|
|
|
135 |
function(nodeA, nodeB) {
|
|
|
136 |
var rangeA, rangeB, compare;
|
|
|
137 |
if (nodeA && nodeB) {
|
|
|
138 |
rangeA = nodeA[OWNER_DOCUMENT].createRange();
|
|
|
139 |
rangeA.setStart(nodeA, 0);
|
|
|
140 |
rangeB = nodeB[OWNER_DOCUMENT].createRange();
|
|
|
141 |
rangeB.setStart(nodeB, 0);
|
|
|
142 |
compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
|
|
|
143 |
}
|
|
|
144 |
|
|
|
145 |
return compare;
|
|
|
146 |
|
|
|
147 |
}),
|
|
|
148 |
|
|
|
149 |
_sort: function(nodes) {
|
|
|
150 |
if (nodes) {
|
|
|
151 |
nodes = Y_Array(nodes, 0, true);
|
|
|
152 |
if (nodes.sort) {
|
|
|
153 |
nodes.sort(Selector._compare);
|
|
|
154 |
}
|
|
|
155 |
}
|
|
|
156 |
|
|
|
157 |
return nodes;
|
|
|
158 |
},
|
|
|
159 |
|
|
|
160 |
_deDupe: function(nodes) {
|
|
|
161 |
var ret = [],
|
|
|
162 |
i, node;
|
|
|
163 |
|
|
|
164 |
for (i = 0; (node = nodes[i++]);) {
|
|
|
165 |
if (!node._found) {
|
|
|
166 |
ret[ret.length] = node;
|
|
|
167 |
node._found = true;
|
|
|
168 |
}
|
|
|
169 |
}
|
|
|
170 |
|
|
|
171 |
for (i = 0; (node = ret[i++]);) {
|
|
|
172 |
node._found = null;
|
|
|
173 |
node.removeAttribute('_found');
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
return ret;
|
|
|
177 |
},
|
|
|
178 |
|
|
|
179 |
/**
|
|
|
180 |
* Retrieves a set of nodes based on a given CSS selector.
|
|
|
181 |
* @method query
|
|
|
182 |
*
|
|
|
183 |
* @param {string} selector The CSS Selector to test the node against.
|
|
|
184 |
* @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
|
|
|
185 |
* @param {Boolean} firstOnly optional Whether or not to return only the first match.
|
|
|
186 |
* @return {Array} An array of nodes that match the given selector.
|
|
|
187 |
* @static
|
|
|
188 |
*/
|
|
|
189 |
query: function(selector, root, firstOnly, skipNative) {
|
|
|
190 |
if (root && typeof root == 'string') {
|
|
|
191 |
root = Y_DOM.get(root);
|
|
|
192 |
if (!root) {
|
|
|
193 |
return (firstOnly) ? null : [];
|
|
|
194 |
}
|
|
|
195 |
} else {
|
|
|
196 |
root = root || Y_DOC;
|
|
|
197 |
}
|
|
|
198 |
|
|
|
199 |
var ret = [],
|
|
|
200 |
useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
|
|
|
201 |
queries = [[selector, root]],
|
|
|
202 |
query,
|
|
|
203 |
result,
|
|
|
204 |
i,
|
|
|
205 |
fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
|
|
|
206 |
|
|
|
207 |
if (selector && fn) {
|
|
|
208 |
// split group into seperate queries
|
|
|
209 |
if (!skipNative && // already done if skipping
|
|
|
210 |
(!useNative || root.tagName)) { // split native when element scoping is needed
|
|
|
211 |
queries = Selector._splitQueries(selector, root);
|
|
|
212 |
}
|
|
|
213 |
|
|
|
214 |
for (i = 0; (query = queries[i++]);) {
|
|
|
215 |
result = fn(query[0], query[1], firstOnly);
|
|
|
216 |
if (!firstOnly) { // coerce DOM Collection to Array
|
|
|
217 |
result = Y_Array(result, 0, true);
|
|
|
218 |
}
|
|
|
219 |
if (result) {
|
|
|
220 |
ret = ret.concat(result);
|
|
|
221 |
}
|
|
|
222 |
}
|
|
|
223 |
|
|
|
224 |
if (queries.length > 1) { // remove dupes and sort by doc order
|
|
|
225 |
ret = Selector._sort(Selector._deDupe(ret));
|
|
|
226 |
}
|
|
|
227 |
}
|
|
|
228 |
|
|
|
229 |
YAHOO.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
|
|
|
230 |
return (firstOnly) ? (ret[0] || null) : ret;
|
|
|
231 |
|
|
|
232 |
},
|
|
|
233 |
|
|
|
234 |
// allows element scoped queries to begin with combinator
|
|
|
235 |
// e.g. query('> p', document.body) === query('body > p')
|
|
|
236 |
_splitQueries: function(selector, node) {
|
|
|
237 |
var groups = selector.split(','),
|
|
|
238 |
queries = [],
|
|
|
239 |
prefix = '',
|
|
|
240 |
i, len;
|
|
|
241 |
|
|
|
242 |
if (node) {
|
|
|
243 |
// enforce for element scoping
|
|
|
244 |
if (node.tagName) {
|
|
|
245 |
node.id = node.id || Y_guid();
|
|
|
246 |
prefix = '[id="' + node.id + '"] ';
|
|
|
247 |
}
|
|
|
248 |
|
|
|
249 |
for (i = 0, len = groups.length; i < len; ++i) {
|
|
|
250 |
selector = prefix + groups[i];
|
|
|
251 |
queries.push([selector, node]);
|
|
|
252 |
}
|
|
|
253 |
}
|
|
|
254 |
|
|
|
255 |
return queries;
|
|
|
256 |
},
|
|
|
257 |
|
|
|
258 |
_nativeQuery: function(selector, root, one) {
|
|
|
259 |
if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
|
|
|
260 |
(Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
|
|
|
261 |
return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
|
|
|
262 |
}
|
|
|
263 |
try {
|
|
|
264 |
//YAHOO.log('trying native query with: ' + selector, 'info', 'selector-native');
|
|
|
265 |
return root['querySelector' + (one ? '' : 'All')](selector);
|
|
|
266 |
} catch(e) { // fallback to brute if available
|
|
|
267 |
//YAHOO.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
|
|
|
268 |
return Selector.query(selector, root, one, true); // redo with skipNative true
|
|
|
269 |
}
|
|
|
270 |
},
|
|
|
271 |
|
|
|
272 |
filter: function(nodes, selector) {
|
|
|
273 |
var ret = [],
|
|
|
274 |
i, node;
|
|
|
275 |
|
|
|
276 |
if (nodes && selector) {
|
|
|
277 |
for (i = 0; (node = nodes[i++]);) {
|
|
|
278 |
if (Selector.test(node, selector)) {
|
|
|
279 |
ret[ret.length] = node;
|
|
|
280 |
}
|
|
|
281 |
}
|
|
|
282 |
} else {
|
|
|
283 |
YAHOO.log('invalid filter input (nodes: ' + nodes +
|
|
|
284 |
', selector: ' + selector + ')', 'warn', 'Selector');
|
|
|
285 |
}
|
|
|
286 |
|
|
|
287 |
return ret;
|
|
|
288 |
},
|
|
|
289 |
|
|
|
290 |
test: function(node, selector, root) {
|
|
|
291 |
var ret = false,
|
|
|
292 |
groups = selector.split(','),
|
|
|
293 |
useFrag = false,
|
|
|
294 |
parent,
|
|
|
295 |
item,
|
|
|
296 |
items,
|
|
|
297 |
frag,
|
|
|
298 |
i, j, group;
|
|
|
299 |
|
|
|
300 |
if (node && node.tagName) { // only test HTMLElements
|
|
|
301 |
|
|
|
302 |
// we need a root if off-doc
|
|
|
303 |
if (!root && !Y_DOM_inDoc(node)) {
|
|
|
304 |
parent = node.parentNode;
|
|
|
305 |
if (parent) {
|
|
|
306 |
root = parent;
|
|
|
307 |
} else { // only use frag when no parent to query
|
|
|
308 |
frag = node[OWNER_DOCUMENT].createDocumentFragment();
|
|
|
309 |
frag.appendChild(node);
|
|
|
310 |
root = frag;
|
|
|
311 |
useFrag = true;
|
|
|
312 |
}
|
|
|
313 |
}
|
|
|
314 |
root = root || node[OWNER_DOCUMENT];
|
|
|
315 |
|
|
|
316 |
if (!node.id) {
|
|
|
317 |
node.id = Y_guid();
|
|
|
318 |
}
|
|
|
319 |
for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
|
|
|
320 |
group += '[id="' + node.id + '"]';
|
|
|
321 |
items = Selector.query(group, root);
|
|
|
322 |
|
|
|
323 |
for (j = 0; item = items[j++];) {
|
|
|
324 |
if (item === node) {
|
|
|
325 |
ret = true;
|
|
|
326 |
break;
|
|
|
327 |
}
|
|
|
328 |
}
|
|
|
329 |
if (ret) {
|
|
|
330 |
break;
|
|
|
331 |
}
|
|
|
332 |
}
|
|
|
333 |
|
|
|
334 |
if (useFrag) { // cleanup
|
|
|
335 |
frag.removeChild(node);
|
|
|
336 |
}
|
|
|
337 |
}
|
|
|
338 |
|
|
|
339 |
return ret;
|
|
|
340 |
}
|
|
|
341 |
|
|
|
342 |
};
|
|
|
343 |
|
|
|
344 |
YAHOO.util.Selector = Selector;
|
|
|
345 |
/**
|
|
|
346 |
* The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
|
|
|
347 |
* @module dom
|
|
|
348 |
* @submodule selector-css2
|
|
|
349 |
* @for Selector
|
|
|
350 |
*/
|
|
|
351 |
|
|
|
352 |
/**
|
|
|
353 |
* Provides helper methods for collecting and filtering DOM elements.
|
|
|
354 |
*/
|
|
|
355 |
|
|
|
356 |
var PARENT_NODE = 'parentNode',
|
|
|
357 |
TAG_NAME = 'tagName',
|
|
|
358 |
ATTRIBUTES = 'attributes',
|
|
|
359 |
COMBINATOR = 'combinator',
|
|
|
360 |
PSEUDOS = 'pseudos',
|
|
|
361 |
|
|
|
362 |
SelectorCSS2 = {
|
|
|
363 |
_reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
|
|
|
364 |
SORT_RESULTS: true,
|
|
|
365 |
_children: function(node, tag) {
|
|
|
366 |
var ret = node.children,
|
|
|
367 |
i,
|
|
|
368 |
children = [],
|
|
|
369 |
childNodes,
|
|
|
370 |
child;
|
|
|
371 |
|
|
|
372 |
if (node.children && tag && node.children.tags) {
|
|
|
373 |
children = node.children.tags(tag);
|
|
|
374 |
} else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
|
|
|
375 |
childNodes = ret || node.childNodes;
|
|
|
376 |
ret = [];
|
|
|
377 |
for (i = 0; (child = childNodes[i++]);) {
|
|
|
378 |
if (child.tagName) {
|
|
|
379 |
if (!tag || tag === child.tagName) {
|
|
|
380 |
ret.push(child);
|
|
|
381 |
}
|
|
|
382 |
}
|
|
|
383 |
}
|
|
|
384 |
}
|
|
|
385 |
|
|
|
386 |
return ret || [];
|
|
|
387 |
},
|
|
|
388 |
|
|
|
389 |
_re: {
|
|
|
390 |
//attr: /(\[.*\])/g,
|
|
|
391 |
attr: /(\[[^\]]*\])/g,
|
|
|
392 |
//esc: /\\[:\[][\w\d\]]*/gi,
|
|
|
393 |
esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
|
|
|
394 |
//pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
|
|
|
395 |
pseudos: /(\([^\)]*\))/g
|
|
|
396 |
},
|
|
|
397 |
|
|
|
398 |
/**
|
|
|
399 |
* Mapping of shorthand tokens to corresponding attribute selector
|
|
|
400 |
* @property shorthand
|
|
|
401 |
* @type object
|
|
|
402 |
*/
|
|
|
403 |
shorthand: {
|
|
|
404 |
//'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
|
|
|
405 |
'\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
|
|
|
406 |
//'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
|
|
|
407 |
//'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
|
|
|
408 |
'\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
|
|
|
409 |
},
|
|
|
410 |
|
|
|
411 |
/**
|
|
|
412 |
* List of operators and corresponding boolean functions.
|
|
|
413 |
* These functions are passed the attribute and the current node's value of the attribute.
|
|
|
414 |
* @property operators
|
|
|
415 |
* @type object
|
|
|
416 |
*/
|
|
|
417 |
operators: {
|
|
|
418 |
'': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
|
|
|
419 |
//'': '.+',
|
|
|
420 |
//'=': '^{val}$', // equality
|
|
|
421 |
'~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
|
|
|
422 |
'|=': '^{val}(?:-|$)' // optional hyphen-delimited
|
|
|
423 |
},
|
|
|
424 |
|
|
|
425 |
pseudos: {
|
|
|
426 |
'first-child': function(node) {
|
|
|
427 |
return Selector._children(node[PARENT_NODE])[0] === node;
|
|
|
428 |
}
|
|
|
429 |
},
|
|
|
430 |
|
|
|
431 |
_bruteQuery: function(selector, root, firstOnly) {
|
|
|
432 |
var ret = [],
|
|
|
433 |
nodes = [],
|
|
|
434 |
tokens = Selector._tokenize(selector),
|
|
|
435 |
token = tokens[tokens.length - 1],
|
|
|
436 |
rootDoc = Y_getDoc(root),
|
|
|
437 |
child,
|
|
|
438 |
id,
|
|
|
439 |
className,
|
|
|
440 |
tagName;
|
|
|
441 |
|
|
|
442 |
|
|
|
443 |
// if we have an initial ID, set to root when in document
|
|
|
444 |
/*
|
|
|
445 |
if (tokens[0] && rootDoc === root &&
|
|
|
446 |
(id = tokens[0].id) &&
|
|
|
447 |
rootDoc.getElementById(id)) {
|
|
|
448 |
root = rootDoc.getElementById(id);
|
|
|
449 |
}
|
|
|
450 |
*/
|
|
|
451 |
|
|
|
452 |
if (token) {
|
|
|
453 |
// prefilter nodes
|
|
|
454 |
id = token.id;
|
|
|
455 |
className = token.className;
|
|
|
456 |
tagName = token.tagName || '*';
|
|
|
457 |
|
|
|
458 |
if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
|
|
|
459 |
// try ID first, unless no root.all && root not in document
|
|
|
460 |
// (root.all works off document, but not getElementById)
|
|
|
461 |
// TODO: move to allById?
|
|
|
462 |
if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
|
|
|
463 |
nodes = Y_DOM_allById(id, root);
|
|
|
464 |
// try className
|
|
|
465 |
} else if (className) {
|
|
|
466 |
nodes = root.getElementsByClassName(className);
|
|
|
467 |
} else { // default to tagName
|
|
|
468 |
nodes = root.getElementsByTagName(tagName);
|
|
|
469 |
}
|
|
|
470 |
|
|
|
471 |
} else { // brute getElementsByTagName('*')
|
|
|
472 |
child = root.firstChild;
|
|
|
473 |
while (child) {
|
|
|
474 |
if (child.tagName) { // only collect HTMLElements
|
|
|
475 |
nodes.push(child);
|
|
|
476 |
}
|
|
|
477 |
child = child.nextSilbing || child.firstChild;
|
|
|
478 |
}
|
|
|
479 |
}
|
|
|
480 |
if (nodes.length) {
|
|
|
481 |
ret = Selector._filterNodes(nodes, tokens, firstOnly);
|
|
|
482 |
}
|
|
|
483 |
}
|
|
|
484 |
|
|
|
485 |
return ret;
|
|
|
486 |
},
|
|
|
487 |
|
|
|
488 |
_filterNodes: function(nodes, tokens, firstOnly) {
|
|
|
489 |
var i = 0,
|
|
|
490 |
j,
|
|
|
491 |
len = tokens.length,
|
|
|
492 |
n = len - 1,
|
|
|
493 |
result = [],
|
|
|
494 |
node = nodes[0],
|
|
|
495 |
tmpNode = node,
|
|
|
496 |
getters = Selector.getters,
|
|
|
497 |
operator,
|
|
|
498 |
combinator,
|
|
|
499 |
token,
|
|
|
500 |
path,
|
|
|
501 |
pass,
|
|
|
502 |
//FUNCTION = 'function',
|
|
|
503 |
value,
|
|
|
504 |
tests,
|
|
|
505 |
test;
|
|
|
506 |
|
|
|
507 |
//do {
|
|
|
508 |
for (i = 0; (tmpNode = node = nodes[i++]);) {
|
|
|
509 |
n = len - 1;
|
|
|
510 |
path = null;
|
|
|
511 |
|
|
|
512 |
testLoop:
|
|
|
513 |
while (tmpNode && tmpNode.tagName) {
|
|
|
514 |
token = tokens[n];
|
|
|
515 |
tests = token.tests;
|
|
|
516 |
j = tests.length;
|
|
|
517 |
if (j && !pass) {
|
|
|
518 |
while ((test = tests[--j])) {
|
|
|
519 |
operator = test[1];
|
|
|
520 |
if (getters[test[0]]) {
|
|
|
521 |
value = getters[test[0]](tmpNode, test[0]);
|
|
|
522 |
} else {
|
|
|
523 |
value = tmpNode[test[0]];
|
|
|
524 |
// use getAttribute for non-standard attributes
|
|
|
525 |
if (value === undefined && tmpNode.getAttribute) {
|
|
|
526 |
value = tmpNode.getAttribute(test[0]);
|
|
|
527 |
}
|
|
|
528 |
}
|
|
|
529 |
|
|
|
530 |
if ((operator === '=' && value !== test[2]) || // fast path for equality
|
|
|
531 |
(typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
|
|
|
532 |
operator.test && !operator.test(value)) || // regex test
|
|
|
533 |
(!operator.test && // protect against RegExp as function (webkit)
|
|
|
534 |
typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
|
|
|
535 |
|
|
|
536 |
// skip non element nodes or non-matching tags
|
|
|
537 |
if ((tmpNode = tmpNode[path])) {
|
|
|
538 |
while (tmpNode &&
|
|
|
539 |
(!tmpNode.tagName ||
|
|
|
540 |
(token.tagName && token.tagName !== tmpNode.tagName))
|
|
|
541 |
) {
|
|
|
542 |
tmpNode = tmpNode[path];
|
|
|
543 |
}
|
|
|
544 |
}
|
|
|
545 |
continue testLoop;
|
|
|
546 |
}
|
|
|
547 |
}
|
|
|
548 |
}
|
|
|
549 |
|
|
|
550 |
n--; // move to next token
|
|
|
551 |
// now that we've passed the test, move up the tree by combinator
|
|
|
552 |
if (!pass && (combinator = token.combinator)) {
|
|
|
553 |
path = combinator.axis;
|
|
|
554 |
tmpNode = tmpNode[path];
|
|
|
555 |
|
|
|
556 |
// skip non element nodes
|
|
|
557 |
while (tmpNode && !tmpNode.tagName) {
|
|
|
558 |
tmpNode = tmpNode[path];
|
|
|
559 |
}
|
|
|
560 |
|
|
|
561 |
if (combinator.direct) { // one pass only
|
|
|
562 |
path = null;
|
|
|
563 |
}
|
|
|
564 |
|
|
|
565 |
} else { // success if we made it this far
|
|
|
566 |
result.push(node);
|
|
|
567 |
if (firstOnly) {
|
|
|
568 |
return result;
|
|
|
569 |
}
|
|
|
570 |
break;
|
|
|
571 |
}
|
|
|
572 |
}
|
|
|
573 |
}// while (tmpNode = node = nodes[++i]);
|
|
|
574 |
node = tmpNode = null;
|
|
|
575 |
return result;
|
|
|
576 |
},
|
|
|
577 |
|
|
|
578 |
combinators: {
|
|
|
579 |
' ': {
|
|
|
580 |
axis: 'parentNode'
|
|
|
581 |
},
|
|
|
582 |
|
|
|
583 |
'>': {
|
|
|
584 |
axis: 'parentNode',
|
|
|
585 |
direct: true
|
|
|
586 |
},
|
|
|
587 |
|
|
|
588 |
|
|
|
589 |
'+': {
|
|
|
590 |
axis: 'previousSibling',
|
|
|
591 |
direct: true
|
|
|
592 |
}
|
|
|
593 |
},
|
|
|
594 |
|
|
|
595 |
_parsers: [
|
|
|
596 |
{
|
|
|
597 |
name: ATTRIBUTES,
|
|
|
598 |
//re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
|
|
|
599 |
re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
|
|
|
600 |
fn: function(match, token) {
|
|
|
601 |
var operator = match[2] || '',
|
|
|
602 |
operators = Selector.operators,
|
|
|
603 |
escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
|
|
|
604 |
test;
|
|
|
605 |
|
|
|
606 |
// add prefiltering for ID and CLASS
|
|
|
607 |
if ((match[1] === 'id' && operator === '=') ||
|
|
|
608 |
(match[1] === 'className' &&
|
|
|
609 |
Y_DOCUMENT_ELEMENT.getElementsByClassName &&
|
|
|
610 |
(operator === '~=' || operator === '='))) {
|
|
|
611 |
token.prefilter = match[1];
|
|
|
612 |
|
|
|
613 |
|
|
|
614 |
match[3] = escVal;
|
|
|
615 |
|
|
|
616 |
// escape all but ID for prefilter, which may run through QSA (via Dom.allById)
|
|
|
617 |
token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
|
|
|
618 |
|
|
|
619 |
}
|
|
|
620 |
|
|
|
621 |
// add tests
|
|
|
622 |
if (operator in operators) {
|
|
|
623 |
test = operators[operator];
|
|
|
624 |
if (typeof test === 'string') {
|
|
|
625 |
match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
|
|
|
626 |
test = new RegExp(test.replace('{val}', match[3]));
|
|
|
627 |
}
|
|
|
628 |
match[2] = test;
|
|
|
629 |
}
|
|
|
630 |
if (!token.last || token.prefilter !== match[1]) {
|
|
|
631 |
return match.slice(1);
|
|
|
632 |
}
|
|
|
633 |
}
|
|
|
634 |
|
|
|
635 |
},
|
|
|
636 |
{
|
|
|
637 |
name: TAG_NAME,
|
|
|
638 |
re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
|
|
|
639 |
fn: function(match, token) {
|
|
|
640 |
var tag = match[1].toUpperCase();
|
|
|
641 |
token.tagName = tag;
|
|
|
642 |
|
|
|
643 |
if (tag !== '*' && (!token.last || token.prefilter)) {
|
|
|
644 |
return [TAG_NAME, '=', tag];
|
|
|
645 |
}
|
|
|
646 |
if (!token.prefilter) {
|
|
|
647 |
token.prefilter = 'tagName';
|
|
|
648 |
}
|
|
|
649 |
}
|
|
|
650 |
},
|
|
|
651 |
{
|
|
|
652 |
name: COMBINATOR,
|
|
|
653 |
re: /^\s*([>+~]|\s)\s*/,
|
|
|
654 |
fn: function(match, token) {
|
|
|
655 |
}
|
|
|
656 |
},
|
|
|
657 |
{
|
|
|
658 |
name: PSEUDOS,
|
|
|
659 |
re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
|
|
|
660 |
fn: function(match, token) {
|
|
|
661 |
var test = Selector[PSEUDOS][match[1]];
|
|
|
662 |
if (test) { // reorder match array and unescape special chars for tests
|
|
|
663 |
if (match[2]) {
|
|
|
664 |
match[2] = match[2].replace(/\\/g, '');
|
|
|
665 |
}
|
|
|
666 |
return [match[2], test];
|
|
|
667 |
} else { // selector token not supported (possibly missing CSS3 module)
|
|
|
668 |
return false;
|
|
|
669 |
}
|
|
|
670 |
}
|
|
|
671 |
}
|
|
|
672 |
],
|
|
|
673 |
|
|
|
674 |
_getToken: function(token) {
|
|
|
675 |
return {
|
|
|
676 |
tagName: null,
|
|
|
677 |
id: null,
|
|
|
678 |
className: null,
|
|
|
679 |
attributes: {},
|
|
|
680 |
combinator: null,
|
|
|
681 |
tests: []
|
|
|
682 |
};
|
|
|
683 |
},
|
|
|
684 |
|
|
|
685 |
/**
|
|
|
686 |
Break selector into token units per simple selector.
|
|
|
687 |
Combinator is attached to the previous token.
|
|
|
688 |
*/
|
|
|
689 |
_tokenize: function(selector) {
|
|
|
690 |
selector = selector || '';
|
|
|
691 |
selector = Selector._replaceShorthand(Y_Lang.trim(selector));
|
|
|
692 |
var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
|
|
|
693 |
query = selector, // original query for debug report
|
|
|
694 |
tokens = [], // array of tokens
|
|
|
695 |
found = false, // whether or not any matches were found this pass
|
|
|
696 |
match, // the regex match
|
|
|
697 |
test,
|
|
|
698 |
i, parser;
|
|
|
699 |
|
|
|
700 |
/*
|
|
|
701 |
Search for selector patterns, store, and strip them from the selector string
|
|
|
702 |
until no patterns match (invalid selector) or we run out of chars.
|
|
|
703 |
|
|
|
704 |
Multiple attributes and pseudos are allowed, in any order.
|
|
|
705 |
for example:
|
|
|
706 |
'form:first-child[type=button]:not(button)[lang|=en]'
|
|
|
707 |
*/
|
|
|
708 |
|
|
|
709 |
outer:
|
|
|
710 |
do {
|
|
|
711 |
found = false; // reset after full pass
|
|
|
712 |
|
|
|
713 |
for (i = 0; (parser = Selector._parsers[i++]);) {
|
|
|
714 |
if ( (match = parser.re.exec(selector)) ) { // note assignment
|
|
|
715 |
if (parser.name !== COMBINATOR ) {
|
|
|
716 |
token.selector = selector;
|
|
|
717 |
}
|
|
|
718 |
selector = selector.replace(match[0], ''); // strip current match from selector
|
|
|
719 |
if (!selector.length) {
|
|
|
720 |
token.last = true;
|
|
|
721 |
}
|
|
|
722 |
|
|
|
723 |
if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
|
|
|
724 |
match[1] = Selector._attrFilters[match[1]];
|
|
|
725 |
}
|
|
|
726 |
|
|
|
727 |
test = parser.fn(match, token);
|
|
|
728 |
if (test === false) { // selector not supported
|
|
|
729 |
found = false;
|
|
|
730 |
break outer;
|
|
|
731 |
} else if (test) {
|
|
|
732 |
token.tests.push(test);
|
|
|
733 |
}
|
|
|
734 |
|
|
|
735 |
if (!selector.length || parser.name === COMBINATOR) {
|
|
|
736 |
tokens.push(token);
|
|
|
737 |
token = Selector._getToken(token);
|
|
|
738 |
if (parser.name === COMBINATOR) {
|
|
|
739 |
token.combinator = Selector.combinators[match[1]];
|
|
|
740 |
}
|
|
|
741 |
}
|
|
|
742 |
found = true;
|
|
|
743 |
|
|
|
744 |
|
|
|
745 |
}
|
|
|
746 |
}
|
|
|
747 |
} while (found && selector.length);
|
|
|
748 |
|
|
|
749 |
if (!found || selector.length) { // not fully parsed
|
|
|
750 |
YAHOO.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
|
|
|
751 |
tokens = [];
|
|
|
752 |
}
|
|
|
753 |
return tokens;
|
|
|
754 |
},
|
|
|
755 |
|
|
|
756 |
_replaceShorthand: function(selector) {
|
|
|
757 |
var shorthand = Selector.shorthand,
|
|
|
758 |
esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
|
|
|
759 |
attrs,
|
|
|
760 |
pseudos,
|
|
|
761 |
re, i, len;
|
|
|
762 |
|
|
|
763 |
if (esc) {
|
|
|
764 |
selector = selector.replace(Selector._re.esc, '\uE000');
|
|
|
765 |
}
|
|
|
766 |
|
|
|
767 |
attrs = selector.match(Selector._re.attr);
|
|
|
768 |
pseudos = selector.match(Selector._re.pseudos);
|
|
|
769 |
|
|
|
770 |
if (attrs) {
|
|
|
771 |
selector = selector.replace(Selector._re.attr, '\uE001');
|
|
|
772 |
}
|
|
|
773 |
|
|
|
774 |
if (pseudos) {
|
|
|
775 |
selector = selector.replace(Selector._re.pseudos, '\uE002');
|
|
|
776 |
}
|
|
|
777 |
|
|
|
778 |
|
|
|
779 |
for (re in shorthand) {
|
|
|
780 |
if (shorthand.hasOwnProperty(re)) {
|
|
|
781 |
selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
|
|
|
782 |
}
|
|
|
783 |
}
|
|
|
784 |
|
|
|
785 |
if (attrs) {
|
|
|
786 |
for (i = 0, len = attrs.length; i < len; ++i) {
|
|
|
787 |
selector = selector.replace(/\uE001/, attrs[i]);
|
|
|
788 |
}
|
|
|
789 |
}
|
|
|
790 |
|
|
|
791 |
if (pseudos) {
|
|
|
792 |
for (i = 0, len = pseudos.length; i < len; ++i) {
|
|
|
793 |
selector = selector.replace(/\uE002/, pseudos[i]);
|
|
|
794 |
}
|
|
|
795 |
}
|
|
|
796 |
|
|
|
797 |
selector = selector.replace(/\[/g, '\uE003');
|
|
|
798 |
selector = selector.replace(/\]/g, '\uE004');
|
|
|
799 |
|
|
|
800 |
selector = selector.replace(/\(/g, '\uE005');
|
|
|
801 |
selector = selector.replace(/\)/g, '\uE006');
|
|
|
802 |
|
|
|
803 |
if (esc) {
|
|
|
804 |
for (i = 0, len = esc.length; i < len; ++i) {
|
|
|
805 |
selector = selector.replace('\uE000', esc[i]);
|
|
|
806 |
}
|
|
|
807 |
}
|
|
|
808 |
|
|
|
809 |
return selector;
|
|
|
810 |
},
|
|
|
811 |
|
|
|
812 |
_attrFilters: {
|
|
|
813 |
'class': 'className',
|
|
|
814 |
'for': 'htmlFor'
|
|
|
815 |
},
|
|
|
816 |
|
|
|
817 |
getters: {
|
|
|
818 |
href: function(node, attr) {
|
|
|
819 |
return Y_DOM.getAttribute(node, attr);
|
|
|
820 |
}
|
|
|
821 |
}
|
|
|
822 |
};
|
|
|
823 |
|
|
|
824 |
Y_mix(Selector, SelectorCSS2, true);
|
|
|
825 |
Selector.getters.src = Selector.getters.rel = Selector.getters.href;
|
|
|
826 |
|
|
|
827 |
// IE wants class with native queries
|
|
|
828 |
if (Selector.useNative && Y_DOC.querySelector) {
|
|
|
829 |
Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
|
|
|
830 |
}
|
|
|
831 |
|
|
|
832 |
/**
|
|
|
833 |
* The selector css3 module provides support for css3 selectors.
|
|
|
834 |
* @module dom
|
|
|
835 |
* @submodule selector-css3
|
|
|
836 |
* @for Selector
|
|
|
837 |
*/
|
|
|
838 |
|
|
|
839 |
/*
|
|
|
840 |
an+b = get every _a_th node starting at the _b_th
|
|
|
841 |
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
|
|
|
842 |
1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
|
|
|
843 |
an+0 = get every _a_th element, "0" may be omitted
|
|
|
844 |
*/
|
|
|
845 |
|
|
|
846 |
Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
|
|
|
847 |
|
|
|
848 |
Selector._getNth = function(node, expr, tag, reverse) {
|
|
|
849 |
Selector._reNth.test(expr);
|
|
|
850 |
var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
|
|
|
851 |
n = RegExp.$2, // "n"
|
|
|
852 |
oddeven = RegExp.$3, // "odd" or "even"
|
|
|
853 |
b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
|
|
|
854 |
result = [],
|
|
|
855 |
siblings = Selector._children(node.parentNode, tag),
|
|
|
856 |
op;
|
|
|
857 |
|
|
|
858 |
if (oddeven) {
|
|
|
859 |
a = 2; // always every other
|
|
|
860 |
op = '+';
|
|
|
861 |
n = 'n';
|
|
|
862 |
b = (oddeven === 'odd') ? 1 : 0;
|
|
|
863 |
} else if ( isNaN(a) ) {
|
|
|
864 |
a = (n) ? 1 : 0; // start from the first or no repeat
|
|
|
865 |
}
|
|
|
866 |
|
|
|
867 |
if (a === 0) { // just the first
|
|
|
868 |
if (reverse) {
|
|
|
869 |
b = siblings.length - b + 1;
|
|
|
870 |
}
|
|
|
871 |
|
|
|
872 |
if (siblings[b - 1] === node) {
|
|
|
873 |
return true;
|
|
|
874 |
} else {
|
|
|
875 |
return false;
|
|
|
876 |
}
|
|
|
877 |
|
|
|
878 |
} else if (a < 0) {
|
|
|
879 |
reverse = !!reverse;
|
|
|
880 |
a = Math.abs(a);
|
|
|
881 |
}
|
|
|
882 |
|
|
|
883 |
if (!reverse) {
|
|
|
884 |
for (var i = b - 1, len = siblings.length; i < len; i += a) {
|
|
|
885 |
if ( i >= 0 && siblings[i] === node ) {
|
|
|
886 |
return true;
|
|
|
887 |
}
|
|
|
888 |
}
|
|
|
889 |
} else {
|
|
|
890 |
for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
|
|
|
891 |
if ( i < len && siblings[i] === node ) {
|
|
|
892 |
return true;
|
|
|
893 |
}
|
|
|
894 |
}
|
|
|
895 |
}
|
|
|
896 |
return false;
|
|
|
897 |
};
|
|
|
898 |
|
|
|
899 |
Y_mix(Selector.pseudos, {
|
|
|
900 |
'root': function(node) {
|
|
|
901 |
return node === node.ownerDocument.documentElement;
|
|
|
902 |
},
|
|
|
903 |
|
|
|
904 |
'nth-child': function(node, expr) {
|
|
|
905 |
return Selector._getNth(node, expr);
|
|
|
906 |
},
|
|
|
907 |
|
|
|
908 |
'nth-last-child': function(node, expr) {
|
|
|
909 |
return Selector._getNth(node, expr, null, true);
|
|
|
910 |
},
|
|
|
911 |
|
|
|
912 |
'nth-of-type': function(node, expr) {
|
|
|
913 |
return Selector._getNth(node, expr, node.tagName);
|
|
|
914 |
},
|
|
|
915 |
|
|
|
916 |
'nth-last-of-type': function(node, expr) {
|
|
|
917 |
return Selector._getNth(node, expr, node.tagName, true);
|
|
|
918 |
},
|
|
|
919 |
|
|
|
920 |
'last-child': function(node) {
|
|
|
921 |
var children = Selector._children(node.parentNode);
|
|
|
922 |
return children[children.length - 1] === node;
|
|
|
923 |
},
|
|
|
924 |
|
|
|
925 |
'first-of-type': function(node) {
|
|
|
926 |
return Selector._children(node.parentNode, node.tagName)[0] === node;
|
|
|
927 |
},
|
|
|
928 |
|
|
|
929 |
'last-of-type': function(node) {
|
|
|
930 |
var children = Selector._children(node.parentNode, node.tagName);
|
|
|
931 |
return children[children.length - 1] === node;
|
|
|
932 |
},
|
|
|
933 |
|
|
|
934 |
'only-child': function(node) {
|
|
|
935 |
var children = Selector._children(node.parentNode);
|
|
|
936 |
return children.length === 1 && children[0] === node;
|
|
|
937 |
},
|
|
|
938 |
|
|
|
939 |
'only-of-type': function(node) {
|
|
|
940 |
var children = Selector._children(node.parentNode, node.tagName);
|
|
|
941 |
return children.length === 1 && children[0] === node;
|
|
|
942 |
},
|
|
|
943 |
|
|
|
944 |
'empty': function(node) {
|
|
|
945 |
return node.childNodes.length === 0;
|
|
|
946 |
},
|
|
|
947 |
|
|
|
948 |
'not': function(node, expr) {
|
|
|
949 |
return !Selector.test(node, expr);
|
|
|
950 |
},
|
|
|
951 |
|
|
|
952 |
'contains': function(node, expr) {
|
|
|
953 |
var text = node.innerText || node.textContent || '';
|
|
|
954 |
return text.indexOf(expr) > -1;
|
|
|
955 |
},
|
|
|
956 |
|
|
|
957 |
'checked': function(node) {
|
|
|
958 |
return (node.checked === true || node.selected === true);
|
|
|
959 |
},
|
|
|
960 |
|
|
|
961 |
enabled: function(node) {
|
|
|
962 |
return (node.disabled !== undefined && !node.disabled);
|
|
|
963 |
},
|
|
|
964 |
|
|
|
965 |
disabled: function(node) {
|
|
|
966 |
return (node.disabled);
|
|
|
967 |
}
|
|
|
968 |
});
|
|
|
969 |
|
|
|
970 |
Y_mix(Selector.operators, {
|
|
|
971 |
'^=': '^{val}', // Match starts with value
|
|
|
972 |
'!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
|
|
|
973 |
'$=': '{val}$', // Match ends with value
|
|
|
974 |
'*=': '{val}' // Match contains value as substring
|
|
|
975 |
});
|
|
|
976 |
|
|
|
977 |
Selector.combinators['~'] = {
|
|
|
978 |
axis: 'previousSibling'
|
|
|
979 |
};
|
|
|
980 |
YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});
|
|
|
981 |
|
|
|
982 |
}, '2.9.0' ,{"requires": ["yui2-yahoo", "yui2-dom"]});
|