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
        return (firstOnly) ? (ret[0] || null) : ret;
165
 
166
    },
167
 
168
    _replaceSelector: function(selector) {
169
        var esc = Y.Selector._parse('esc', selector), // pull escaped colon, brackets, etc.
170
            attrs,
171
            pseudos;
172
 
173
        // first replace escaped chars, which could be present in attrs or pseudos
174
        selector = Y.Selector._replace('esc', selector);
175
 
176
        // then replace pseudos before attrs to avoid replacing :not([foo])
177
        pseudos = Y.Selector._parse('pseudo', selector);
178
        selector = Selector._replace('pseudo', selector);
179
 
180
        attrs = Y.Selector._parse('attr', selector);
181
        selector = Y.Selector._replace('attr', selector);
182
 
183
        return {
184
            esc: esc,
185
            attrs: attrs,
186
            pseudos: pseudos,
187
            selector: selector
188
        };
189
    },
190
 
191
    _restoreSelector: function(replaced) {
192
        var selector = replaced.selector;
193
        selector = Y.Selector._restore('attr', selector, replaced.attrs);
194
        selector = Y.Selector._restore('pseudo', selector, replaced.pseudos);
195
        selector = Y.Selector._restore('esc', selector, replaced.esc);
196
        return selector;
197
    },
198
 
199
    _replaceCommas: function(selector) {
200
        var replaced = Y.Selector._replaceSelector(selector),
201
            selector = replaced.selector;
202
 
203
        if (selector) {
204
            selector = selector.replace(/,/g, '\uE007');
205
            replaced.selector = selector;
206
            selector = Y.Selector._restoreSelector(replaced);
207
        }
208
        return selector;
209
    },
210
 
211
    // allows element scoped queries to begin with combinator
212
    // e.g. query('> p', document.body) === query('body > p')
213
    _splitQueries: function(selector, node) {
214
        if (selector.indexOf(',') > -1) {
215
            selector = Y.Selector._replaceCommas(selector);
216
        }
217
 
218
        var groups = selector.split('\uE007'), // split on replaced comma token
219
            queries = [],
220
            prefix = '',
221
            id,
222
            i,
223
            len;
224
 
225
        if (node) {
226
            // enforce for element scoping
227
            if (node.nodeType === 1) { // Elements only
228
                id = Y.Selector._escapeId(Y.DOM.getId(node));
229
 
230
                if (!id) {
231
                    id = Y.guid();
232
                    Y.DOM.setId(node, id);
233
                }
234
 
235
                prefix = '[id="' + id + '"] ';
236
            }
237
 
238
            for (i = 0, len = groups.length; i < len; ++i) {
239
                selector =  prefix + groups[i];
240
                queries.push([selector, node]);
241
            }
242
        }
243
 
244
        return queries;
245
    },
246
 
247
    _nativeQuery: function(selector, root, one) {
248
        if (
249
            (Y.UA.webkit || Y.UA.opera) &&          // webkit (chrome, safari) and Opera
250
            selector.indexOf(':checked') > -1 &&    // fail to pick up "selected"  with ":checked"
251
            (Y.Selector.pseudos && Y.Selector.pseudos.checked)
252
        ) {
253
            return Y.Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
254
        }
255
        try {
256
            return root['querySelector' + (one ? '' : 'All')](selector);
257
        } catch(e) { // fallback to brute if available
258
            return Y.Selector.query(selector, root, one, true); // redo with skipNative true
259
        }
260
    },
261
 
262
    /**
263
     * Filters out nodes that do not match the given CSS selector.
264
     * @method filter
265
     *
266
     * @param {HTMLElement[]} nodes An array of nodes.
267
     * @param {String} selector A CSS selector to test each node against.
268
     * @return {HTMLElement[]} The nodes that matched the given CSS selector.
269
     * @static
270
     */
271
    filter: function(nodes, selector) {
272
        var ret = [],
273
            i, node;
274
 
275
        if (nodes && selector) {
276
            for (i = 0; (node = nodes[i++]);) {
277
                if (Y.Selector.test(node, selector)) {
278
                    ret[ret.length] = node;
279
                }
280
            }
281
        } else {
282
        }
283
 
284
        return ret;
285
    },
286
 
287
    /**
288
     * Determines whether or not the given node matches the given CSS selector.
289
     * @method test
290
     *
291
     * @param {HTMLElement} node A node to test.
292
     * @param {String} selector A CSS selector to test the node against.
293
     * @param {HTMLElement} root optional A node to start the query from. Defaults to the parent document of the node.
294
     * @return {Boolean} Whether or not the given node matched the given CSS selector.
295
     * @static
296
     */
297
    test: function(node, selector, root) {
298
        var ret = false,
299
            useFrag = false,
300
            groups,
301
            parent,
302
            item,
303
            items,
304
            frag,
305
            id,
306
            i, j, group;
307
 
308
        if (node && node.tagName) { // only test HTMLElements
309
 
310
            if (typeof selector == 'function') { // test with function
311
                ret = selector.call(node, node);
312
            } else { // test with query
313
                // we need a root if off-doc
314
                groups = selector.split(',');
315
                if (!root && !Y.DOM.inDoc(node)) {
316
                    parent = node.parentNode;
317
                    if (parent) {
318
                        root = parent;
319
                    } else { // only use frag when no parent to query
320
                        frag = node[OWNER_DOCUMENT].createDocumentFragment();
321
                        frag.appendChild(node);
322
                        root = frag;
323
                        useFrag = true;
324
                    }
325
                }
326
                root = root || node[OWNER_DOCUMENT];
327
 
328
                id = Y.Selector._escapeId(Y.DOM.getId(node));
329
                if (!id) {
330
                    id = Y.guid();
331
                    Y.DOM.setId(node, id);
332
                }
333
 
334
                for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
335
                    group += '[id="' + id + '"]';
336
                    items = Y.Selector.query(group, root);
337
 
338
                    for (j = 0; item = items[j++];) {
339
                        if (item === node) {
340
                            ret = true;
341
                            break;
342
                        }
343
                    }
344
                    if (ret) {
345
                        break;
346
                    }
347
                }
348
 
349
                if (useFrag) { // cleanup
350
                    frag.removeChild(node);
351
                }
352
            };
353
        }
354
 
355
        return ret;
356
    },
357
 
358
    /**
359
     * A convenience method to emulate Y.Node's aNode.ancestor(selector).
360
     * @method ancestor
361
     *
362
     * @param {HTMLElement} node A node to start the query from.
363
     * @param {String} selector A CSS selector to test the node against.
364
     * @param {Boolean} testSelf optional Whether or not to include the node in the scan.
365
     * @return {HTMLElement} The ancestor node matching the selector, or null.
366
     * @static
367
     */
368
    ancestor: function (node, selector, testSelf) {
369
        return Y.DOM.ancestor(node, function(n) {
370
            return Y.Selector.test(n, selector);
371
        }, testSelf);
372
    },
373
 
374
    _parse: function(name, selector) {
375
        return selector.match(Y.Selector._types[name].re);
376
    },
377
 
378
    _replace: function(name, selector) {
379
        var o = Y.Selector._types[name];
380
        return selector.replace(o.re, o.token);
381
    },
382
 
383
    _restore: function(name, selector, items) {
384
        if (items) {
385
            var token = Y.Selector._types[name].token,
386
                i, len;
387
            for (i = 0, len = items.length; i < len; ++i) {
388
                selector = selector.replace(token, items[i]);
389
            }
390
        }
391
        return selector;
392
    }
393
};
394
 
395
Y.mix(Y.Selector, Selector, true);
396
 
397
})(Y);
398
 
399
 
400
}, '3.18.1', {"requires": ["dom-base"]});