PageSourceSearch

http://cityparrots.org/universal/yui/selector/selector.js?CE=75

js cityparrots.org collected 2026-10-02 12:24:48 UTC 21,705 bytes, 644 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.