# Yahoo! UI Library

### dom 3.0.0

Yahoo! UI Library > dom > selector-native.js (source view)
Search:
Filters
Show Private Show Protected Show Deprecated

    (function(Y) {
    /**
     * 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
     */

    Y.namespace('Selector'); // allow native module to standalone

    var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
        OWNER_DOCUMENT = 'ownerDocument',
        TMP_PREFIX = 'yui-tmp-',
        g_counter = 0;

    var Selector = {
        _foundCache: [],

        useNative: true,

        _compare: ('sourceIndex' in document.documentElement) ?
            function(nodeA, nodeB) {
                var a = nodeA.sourceIndex,
                    b = nodeB.sourceIndex;

                if (a === b) {
                    return 0;
                } else if (a > b) {
                    return 1;
                }

                return -1;

            } : (document.documentElement[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) {
            root = root || Y.config.doc;
            var ret = [],
                useNative = (Y.Selector.useNative && document.querySelector && !skipNative),
                queries = [[selector, root]],
                query,
                result,
                i,
                fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery;

            if (selector && fn) {
                // split group into seperate 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));
                }
            }

            Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
            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 = '#' + 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) {
            try {
                //Y.log('trying native query with: ' + selector, 'info', 'selector-native');
                return root['querySelector' + (one ? '' : 'All')](selector);
            } catch(e) { // fallback to brute if available
                //Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
                return Y.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 (Y.Selector.test(node, selector)) {
                        ret[ret.length] = node;
                    }
                }
            } else {
                Y.log('invalid filter input (nodes: ' + nodes +
                        ', selector: ' + selector + ')', 'warn', 'Selector');
            }

            return ret;
        },

        test: function(node, selector, root) {
            var ret = false,
                groups = selector.split(','),
                item,
                i, group;

            if (node && node.tagName) { // only test HTMLElements
                root = root || node.ownerDocument;

                if (!node.id) {
                    node.id = TMP_PREFIX + g_counter++;
                }
                for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
                    group += '#' + node.id; // add ID for uniqueness
                    item = Y.Selector.query(group, root, true);
                    ret = (item === node);
                    if (ret) {
                        break;
                    }
                }
            }

            return ret;
        }
    };

    Y.mix(Y.Selector, Selector, true);

    })(Y);

Copyright © 2009 Yahoo! Inc. All rights reserved.