]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blob - wradmin/static/yui/selector/selector.js
Rename public directory to static.
[philipp/winterrodeln/wradmin.git] / wradmin / static / yui / selector / selector.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.7.0
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() {
16 var 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
25 Y.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 = arguments.callee(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 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                 if (node._children) {
404                     return node._children;
405                 }
406                 var children = [],
407                     childNodes = node.childNodes;
408
409                 for (var i = 0, len = childNodes.length; i < len; ++i) {
410                     if (childNodes[i].tagName) {
411                         if (!tag || childNodes[i].tagName === tag) {
412                             children[children.length] = childNodes[i];
413                         }
414                     }
415                 }
416                 node._children = children;
417                 return children;
418             };
419         }
420     }(),
421
422     _combinators: {
423         ' ': function(node, token) {
424             while ( (node = node.parentNode) ) {
425                 if (Y.Selector._test(node, '', token.previous)) {
426                     return true;
427                 }
428             }  
429             return false;
430         },
431
432         '>': function(node, token) {
433             return Y.Selector._test(node.parentNode, null, token.previous);
434         },
435
436         '+': function(node, token) {
437             var sib = node.previousSibling;
438             while (sib && sib.nodeType !== 1) {
439                 sib = sib.previousSibling;
440             }
441
442             if (sib && Y.Selector._test(sib, null, token.previous)) {
443                 return true; 
444             }
445             return false;
446         },
447
448         '~': function(node, token) {
449             var sib = node.previousSibling;
450             while (sib) {
451                 if (sib.nodeType === 1 && Y.Selector._test(sib, null, token.previous)) {
452                     return true;
453                 }
454                 sib = sib.previousSibling;
455             }
456
457             return false;
458         }
459     },
460
461
462     /*
463         an+b = get every _a_th node starting at the _b_th
464         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
465         1n+b =  get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
466         an+0 = get every _a_th element, "0" may be omitted 
467     */
468     _getNth: function(node, expr, tag, reverse) {
469         Y.Selector._re.nth.test(expr);
470         var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
471             n = RegExp.$2, // "n"
472             oddeven = RegExp.$3, // "odd" or "even"
473             b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
474             result = [],
475             op;
476
477         var siblings = Y.Selector._getChildren(node.parentNode, tag);
478
479         if (oddeven) {
480             a = 2; // always every other
481             op = '+';
482             n = 'n';
483             b = (oddeven === 'odd') ? 1 : 0;
484         } else if ( isNaN(a) ) {
485             a = (n) ? 1 : 0; // start from the first or no repeat
486         }
487
488         if (a === 0) { // just the first
489             if (reverse) {
490                 b = siblings.length - b + 1; 
491             }
492
493             if (siblings[b - 1] === node) {
494                 return true;
495             } else {
496                 return false;
497             }
498
499         } else if (a < 0) {
500             reverse = !!reverse;
501             a = Math.abs(a);
502         }
503
504         if (!reverse) {
505             for (var i = b - 1, len = siblings.length; i < len; i += a) {
506                 if ( i >= 0 && siblings[i] === node ) {
507                     return true;
508                 }
509             }
510         } else {
511             for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
512                 if ( i < len && siblings[i] === node ) {
513                     return true;
514                 }
515             }
516         }
517         return false;
518     },
519
520     _getId: function(attr) {
521         for (var i = 0, len = attr.length; i < len; ++i) {
522             if (attr[i][0] == 'id' && attr[i][1] === '=') {
523                 return attr[i][2];
524             }
525         }
526     },
527
528     _getIdTokenIndex: function(tokens) {
529         for (var i = 0, len = tokens.length; i < len; ++i) {
530             if (Y.Selector._getId(tokens[i].attributes)) {
531                 return i;
532             }
533         }
534         return -1;
535     },
536
537     _patterns: {
538         tag: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
539         attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
540         pseudos: /^:([-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
541         combinator: /^\s*([>+~]|\s)\s*/
542     },
543
544     /**
545         Break selector into token units per simple selector.
546         Combinator is attached to left-hand selector.
547      */
548     _tokenize: function(selector) {
549         var token = {},     // one token per simple selector (left selector holds combinator)
550             tokens = [],    // array of tokens
551             id,             // unique id for the simple selector (if found)
552             found = false,  // whether or not any matches were found this pass
553             patterns = Y.Selector._patterns,
554             match;          // the regex match
555
556         selector = Y.Selector._replaceShorthand(selector); // convert ID and CLASS shortcuts to attributes
557
558         /*
559             Search for selector patterns, store, and strip them from the selector string
560             until no patterns match (invalid selector) or we run out of chars.
561
562             Multiple attributes and pseudos are allowed, in any order.
563             for example:
564                 'form:first-child[type=button]:not(button)[lang|=en]'
565         */
566         do {
567             found = false; // reset after full pass
568             for (var re in patterns) {
569                 if (YAHOO.lang.hasOwnProperty(patterns, re)) {
570                     if (re != 'tag' && re != 'combinator') { // only one allowed
571                         token[re] = token[re] || [];
572                     }
573                     if ( (match = patterns[re].exec(selector)) ) { // note assignment
574                         found = true;
575                         if (re != 'tag' && re != 'combinator') { // only one allowed
576                             // capture ID for fast path to element
577                             if (re === 'attributes' && match[1] === 'id') {
578                                 token.id = match[3];
579                             }
580
581                             token[re].push(match.slice(1));
582                         } else { // single selector (tag, combinator)
583                             token[re] = match[1];
584                         }
585                         selector = selector.replace(match[0], ''); // strip current match from selector
586                         if (re === 'combinator' || !selector.length) { // next token or done
587                             token.attributes = Y.Selector._fixAttributes(token.attributes);
588                             token.pseudos = token.pseudos || [];
589                             token.tag = token.tag ? token.tag.toUpperCase() : '*';
590                             tokens.push(token);
591
592                             token = { // prep next token
593                                 previous: token
594                             };
595                         }
596                     }
597                 }
598             }
599         } while (found);
600
601         return tokens;
602     },
603
604
605     _fixAttributes: function(attr) {
606         var aliases = Y.Selector.attrAliases;
607         attr = attr || [];
608         for (var i = 0, len = attr.length; i < len; ++i) {
609             if (aliases[attr[i][0]]) { // convert reserved words, etc
610                 attr[i][0] = aliases[attr[i][0]];
611             }
612             if (!attr[i][1]) { // use exists operator
613                 attr[i][1] = '';
614             }
615         }
616         return attr;
617     },
618
619     _replaceShorthand: function(selector) {
620         var shorthand = Y.Selector.shorthand;
621
622         //var attrs = selector.match(Y.Selector._patterns.attributes); // pull attributes to avoid false pos on "." and "#"
623         var attrs = selector.match(Y.Selector._re.attr); // pull attributes to avoid false pos on "." and "#"
624         if (attrs) {
625             selector = selector.replace(Y.Selector._re.attr, 'REPLACED_ATTRIBUTE');
626         }
627         for (var re in shorthand) {
628             if (YAHOO.lang.hasOwnProperty(shorthand, re)) {
629                 selector = selector.replace(Y.Selector._getRegExp(re, 'gi'), shorthand[re]);
630             }
631         }
632
633         if (attrs) {
634             for (var i = 0, len = attrs.length; i < len; ++i) {
635                 selector = selector.replace('REPLACED_ATTRIBUTE', attrs[i]);
636             }
637         }
638         return selector;
639     }
640 };
641
642 if (YAHOO.env.ua.ie && YAHOO.env.ua.ie < 8) { // rewrite class for IE < 8
643     Y.Selector.attrAliases['class'] = 'className';
644     Y.Selector.attrAliases['for'] = 'htmlFor';
645 }
646
647 })();
648 YAHOO.register("selector", YAHOO.util.Selector, {version: "2.7.0", build: "1799"});