~bac/juju-gui/trunkcopy

« back to all changes in this revision

Viewing changes to lib/yui/build/selector-native/selector-native.js

  • Committer: kapil.foss at gmail
  • Date: 2012-07-13 18:45:59 UTC
  • Revision ID: kapil.foss@gmail.com-20120713184559-2xl7be17egsrz0c9
reshape

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
YUI 3.5.1 (build 22)
3
 
Copyright 2012 Yahoo! Inc. All rights reserved.
4
 
Licensed under the BSD License.
5
 
http://yuilibrary.com/license/
6
 
*/
7
 
YUI.add('selector-native', function(Y) {
8
 
 
9
 
(function(Y) {
10
 
/**
11
 
 * The selector-native module provides support for native querySelector
12
 
 * @module dom
13
 
 * @submodule selector-native
14
 
 * @for Selector
15
 
 */
16
 
 
17
 
/**
18
 
 * Provides support for using CSS selectors to query the DOM 
19
 
 * @class Selector 
20
 
 * @static
21
 
 * @for Selector
22
 
 */
23
 
 
24
 
Y.namespace('Selector'); // allow native module to standalone
25
 
 
26
 
var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
27
 
    OWNER_DOCUMENT = 'ownerDocument';
28
 
 
29
 
var Selector = {
30
 
    _types: {
31
 
        esc: {
32
 
            token: '\uE000',
33
 
            re: /\\[:\[\]\(\)#\.\'\>+~"]/gi
34
 
        },
35
 
 
36
 
        attr: {
37
 
            token: '\uE001',
38
 
            re: /(\[[^\]]*\])/g
39
 
        },
40
 
 
41
 
        pseudo: {
42
 
            token: '\uE002',
43
 
            re: /(\([^\)]*\))/g
44
 
        }
45
 
    },
46
 
 
47
 
    useNative: true,
48
 
 
49
 
    _escapeId: function(id) {
50
 
        if (id) {
51
 
            id = id.replace(/([:\[\]\(\)#\.'<>+~"])/g,'\\$1');
52
 
        }
53
 
        return id;
54
 
    },
55
 
 
56
 
    _compare: ('sourceIndex' in Y.config.doc.documentElement) ?
57
 
        function(nodeA, nodeB) {
58
 
            var a = nodeA.sourceIndex,
59
 
                b = nodeB.sourceIndex;
60
 
 
61
 
            if (a === b) {
62
 
                return 0;
63
 
            } else if (a > b) {
64
 
                return 1;
65
 
            }
66
 
 
67
 
            return -1;
68
 
 
69
 
        } : (Y.config.doc.documentElement[COMPARE_DOCUMENT_POSITION] ?
70
 
        function(nodeA, nodeB) {
71
 
            if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
72
 
                return -1;
73
 
            } else {
74
 
                return 1;
75
 
            }
76
 
        } :
77
 
        function(nodeA, nodeB) {
78
 
            var rangeA, rangeB, compare;
79
 
            if (nodeA && nodeB) {
80
 
                rangeA = nodeA[OWNER_DOCUMENT].createRange();
81
 
                rangeA.setStart(nodeA, 0);
82
 
                rangeB = nodeB[OWNER_DOCUMENT].createRange();
83
 
                rangeB.setStart(nodeB, 0);
84
 
                compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
85
 
            }
86
 
 
87
 
            return compare;
88
 
        
89
 
    }),
90
 
 
91
 
    _sort: function(nodes) {
92
 
        if (nodes) {
93
 
            nodes = Y.Array(nodes, 0, true);
94
 
            if (nodes.sort) {
95
 
                nodes.sort(Selector._compare);
96
 
            }
97
 
        }
98
 
 
99
 
        return nodes;
100
 
    },
101
 
 
102
 
    _deDupe: function(nodes) {
103
 
        var ret = [],
104
 
            i, node;
105
 
 
106
 
        for (i = 0; (node = nodes[i++]);) {
107
 
            if (!node._found) {
108
 
                ret[ret.length] = node;
109
 
                node._found = true;
110
 
            }
111
 
        }
112
 
 
113
 
        for (i = 0; (node = ret[i++]);) {
114
 
            node._found = null;
115
 
            node.removeAttribute('_found');
116
 
        }
117
 
 
118
 
        return ret;
119
 
    },
120
 
 
121
 
    /**
122
 
     * Retrieves a set of nodes based on a given CSS selector. 
123
 
     * @method query
124
 
     *
125
 
     * @param {string} selector The CSS Selector to test the node against.
126
 
     * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
127
 
     * @param {Boolean} firstOnly optional Whether or not to return only the first match.
128
 
     * @return {Array} An array of nodes that match the given selector.
129
 
     * @static
130
 
     */
131
 
    query: function(selector, root, firstOnly, skipNative) {
132
 
        root = root || Y.config.doc;
133
 
        var ret = [],
134
 
            useNative = (Y.Selector.useNative && Y.config.doc.querySelector && !skipNative),
135
 
            queries = [[selector, root]],
136
 
            query,
137
 
            result,
138
 
            i,
139
 
            fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery;
140
 
 
141
 
        if (selector && fn) {
142
 
            // split group into seperate queries
143
 
            if (!skipNative && // already done if skipping
144
 
                    (!useNative || root.tagName)) { // split native when element scoping is needed
145
 
                queries = Selector._splitQueries(selector, root);
146
 
            }
147
 
 
148
 
            for (i = 0; (query = queries[i++]);) {
149
 
                result = fn(query[0], query[1], firstOnly);
150
 
                if (!firstOnly) { // coerce DOM Collection to Array
151
 
                    result = Y.Array(result, 0, true);
152
 
                }
153
 
                if (result) {
154
 
                    ret = ret.concat(result);
155
 
                }
156
 
            }
157
 
 
158
 
            if (queries.length > 1) { // remove dupes and sort by doc order 
159
 
                ret = Selector._sort(Selector._deDupe(ret));
160
 
            }
161
 
        }
162
 
 
163
 
        return (firstOnly) ? (ret[0] || null) : ret;
164
 
 
165
 
    },
166
 
 
167
 
    _replaceSelector: function(selector) {
168
 
        var esc = Y.Selector._parse('esc', selector), // pull escaped colon, brackets, etc. 
169
 
            attrs,
170
 
            pseudos;
171
 
 
172
 
        // first replace escaped chars, which could be present in attrs or pseudos
173
 
        selector = Y.Selector._replace('esc', selector);
174
 
 
175
 
        // then replace pseudos before attrs to avoid replacing :not([foo])
176
 
        pseudos = Y.Selector._parse('pseudo', selector);
177
 
        selector = Selector._replace('pseudo', selector);
178
 
 
179
 
        attrs = Y.Selector._parse('attr', selector);
180
 
        selector = Y.Selector._replace('attr', selector);
181
 
 
182
 
        return {
183
 
            esc: esc,
184
 
            attrs: attrs,
185
 
            pseudos: pseudos,
186
 
            selector: selector
187
 
        }
188
 
    },
189
 
 
190
 
    _restoreSelector: function(replaced) {
191
 
        var selector = replaced.selector;
192
 
        selector = Y.Selector._restore('attr', selector, replaced.attrs);
193
 
        selector = Y.Selector._restore('pseudo', selector, replaced.pseudos);
194
 
        selector = Y.Selector._restore('esc', selector, replaced.esc);
195
 
        return selector;
196
 
    },
197
 
 
198
 
    _replaceCommas: function(selector) {
199
 
        var replaced = Y.Selector._replaceSelector(selector),
200
 
            selector = replaced.selector;
201
 
 
202
 
        if (selector) {
203
 
            selector = selector.replace(/,/g, '\uE007');
204
 
            replaced.selector = selector;
205
 
            selector = Y.Selector._restoreSelector(replaced);
206
 
        }
207
 
        return selector;
208
 
    },
209
 
 
210
 
    // allows element scoped queries to begin with combinator
211
 
    // e.g. query('> p', document.body) === query('body > p')
212
 
    _splitQueries: function(selector, node) {
213
 
        if (selector.indexOf(',') > -1) {
214
 
            selector = Y.Selector._replaceCommas(selector);
215
 
        }
216
 
 
217
 
        var groups = selector.split('\uE007'), // split on replaced comma token
218
 
            queries = [],
219
 
            prefix = '',
220
 
            id,
221
 
            i,
222
 
            len;
223
 
 
224
 
        if (node) {
225
 
            // enforce for element scoping
226
 
            if (node.nodeType === 1) { // Elements only
227
 
                id = Y.Selector._escapeId(Y.DOM.getId(node));
228
 
 
229
 
                if (!id) {
230
 
                    id = Y.guid();
231
 
                    Y.DOM.setId(node, id);
232
 
                }
233
 
            
234
 
                prefix = '[id="' + id + '"] ';
235
 
            }
236
 
 
237
 
            for (i = 0, len = groups.length; i < len; ++i) {
238
 
                selector =  prefix + groups[i];
239
 
                queries.push([selector, node]);
240
 
            }
241
 
        }
242
 
 
243
 
        return queries;
244
 
    },
245
 
 
246
 
    _nativeQuery: function(selector, root, one) {
247
 
        if (Y.UA.webkit && selector.indexOf(':checked') > -1 &&
248
 
                (Y.Selector.pseudos && Y.Selector.pseudos.checked)) { // webkit (chrome, safari) fails to pick up "selected"  with "checked"
249
 
            return Y.Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
250
 
        }
251
 
        try {
252
 
            return root['querySelector' + (one ? '' : 'All')](selector);
253
 
        } catch(e) { // fallback to brute if available
254
 
            return Y.Selector.query(selector, root, one, true); // redo with skipNative true
255
 
        }
256
 
    },
257
 
 
258
 
    filter: function(nodes, selector) {
259
 
        var ret = [],
260
 
            i, node;
261
 
 
262
 
        if (nodes && selector) {
263
 
            for (i = 0; (node = nodes[i++]);) {
264
 
                if (Y.Selector.test(node, selector)) {
265
 
                    ret[ret.length] = node;
266
 
                }
267
 
            }
268
 
        } else {
269
 
        }
270
 
 
271
 
        return ret;
272
 
    },
273
 
 
274
 
    test: function(node, selector, root) {
275
 
        var ret = false,
276
 
            useFrag = false,
277
 
            groups,
278
 
            parent,
279
 
            item,
280
 
            items,
281
 
            frag,
282
 
            id,
283
 
            i, j, group;
284
 
 
285
 
        if (node && node.tagName) { // only test HTMLElements
286
 
 
287
 
            if (typeof selector == 'function') { // test with function
288
 
                ret = selector.call(node, node);
289
 
            } else { // test with query
290
 
                // we need a root if off-doc
291
 
                groups = selector.split(',');
292
 
                if (!root && !Y.DOM.inDoc(node)) {
293
 
                    parent = node.parentNode;
294
 
                    if (parent) { 
295
 
                        root = parent;
296
 
                    } else { // only use frag when no parent to query
297
 
                        frag = node[OWNER_DOCUMENT].createDocumentFragment();
298
 
                        frag.appendChild(node);
299
 
                        root = frag;
300
 
                        useFrag = true;
301
 
                    }
302
 
                }
303
 
                root = root || node[OWNER_DOCUMENT];
304
 
 
305
 
                id = Y.Selector._escapeId(Y.DOM.getId(node));
306
 
                if (!id) {
307
 
                    id = Y.guid();
308
 
                    Y.DOM.setId(node, id);
309
 
                }
310
 
 
311
 
                for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
312
 
                    group += '[id="' + id + '"]';
313
 
                    items = Y.Selector.query(group, root);
314
 
 
315
 
                    for (j = 0; item = items[j++];) {
316
 
                        if (item === node) {
317
 
                            ret = true;
318
 
                            break;
319
 
                        }
320
 
                    }
321
 
                    if (ret) {
322
 
                        break;
323
 
                    }
324
 
                }
325
 
 
326
 
                if (useFrag) { // cleanup
327
 
                    frag.removeChild(node);
328
 
                }
329
 
            };
330
 
        }
331
 
 
332
 
        return ret;
333
 
    },
334
 
 
335
 
    /**
336
 
     * A convenience function to emulate Y.Node's aNode.ancestor(selector).
337
 
     * @param {HTMLElement} element An HTMLElement to start the query from.
338
 
     * @param {String} selector The CSS selector to test the node against.
339
 
     * @return {HTMLElement} The ancestor node matching the selector, or null.
340
 
     * @param {Boolean} testSelf optional Whether or not to include the element in the scan 
341
 
     * @static
342
 
     * @method ancestor
343
 
     */
344
 
    ancestor: function (element, selector, testSelf) {
345
 
        return Y.DOM.ancestor(element, function(n) {
346
 
            return Y.Selector.test(n, selector);
347
 
        }, testSelf);
348
 
    },
349
 
 
350
 
    _parse: function(name, selector) {
351
 
        return selector.match(Y.Selector._types[name].re);
352
 
    },
353
 
 
354
 
    _replace: function(name, selector) {
355
 
        var o = Y.Selector._types[name];
356
 
        return selector.replace(o.re, o.token);
357
 
    },
358
 
 
359
 
    _restore: function(name, selector, items) {
360
 
        if (items) {
361
 
            var token = Y.Selector._types[name].token,
362
 
                i, len;
363
 
            for (i = 0, len = items.length; i < len; ++i) {
364
 
                selector = selector.replace(token, items[i]);
365
 
            }
366
 
        }
367
 
        return selector;
368
 
    }
369
 
};
370
 
 
371
 
Y.mix(Y.Selector, Selector, true);
372
 
 
373
 
})(Y);
374
 
 
375
 
 
376
 
}, '3.5.1' ,{requires:['dom-base']});