Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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