Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('selector-css2', function (Y, NAME) {
2
 
3
/**
4
 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
5
 * @module dom
6
 * @submodule selector-css2
7
 * @for Selector
8
 */
9
 
10
/*
11
 * Provides helper methods for collecting and filtering DOM elements.
12
 */
13
 
14
var PARENT_NODE = 'parentNode',
15
    TAG_NAME = 'tagName',
16
    ATTRIBUTES = 'attributes',
17
    COMBINATOR = 'combinator',
18
    PSEUDOS = 'pseudos',
19
 
20
    Selector = Y.Selector,
21
 
22
    SelectorCSS2 = {
23
        _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/,
24
        SORT_RESULTS: true,
25
 
26
        // TODO: better detection, document specific
27
        _isXML: (function() {
28
            var isXML = (Y.config.doc.createElement('div').tagName !== 'DIV');
29
            return isXML;
30
        }()),
31
 
32
        /**
33
         * Mapping of shorthand tokens to corresponding attribute selector
34
         * @property shorthand
35
         * @type object
36
         */
37
        shorthand: {
38
            '\\#(-?[_a-z0-9]+[-\\w\\uE000]*)': '[id=$1]',
39
            '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
40
        },
41
 
42
        /**
43
         * List of operators and corresponding boolean functions.
44
         * These functions are passed the attribute and the current node's value of the attribute.
45
         * @property operators
46
         * @type object
47
         */
48
        operators: {
49
            '': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
50
            '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
51
            '|=': '^{val}-?' // optional hyphen-delimited
52
        },
53
 
54
        pseudos: {
55
           'first-child': function(node) {
56
                return Y.DOM._children(node[PARENT_NODE])[0] === node;
57
            }
58
        },
59
 
60
        _bruteQuery: function(selector, root, firstOnly) {
61
            var ret = [],
62
                nodes = [],
63
                visited,
64
                tokens = Selector._tokenize(selector),
65
                token = tokens[tokens.length - 1],
66
                rootDoc = Y.DOM._getDoc(root),
67
                child,
68
                id,
69
                className,
70
                tagName,
71
                isUniversal;
72
 
73
            if (token) {
74
                // prefilter nodes
75
                id = token.id;
76
                className = token.className;
77
                tagName = token.tagName || '*';
78
 
79
                if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
80
                    // try ID first, unless no root.all && root not in document
81
                    // (root.all works off document, but not getElementById)
82
                    if (id && (root.all || (root.nodeType === 9 || Y.DOM.inDoc(root)))) {
83
                        nodes = Y.DOM.allById(id, root);
84
                    // try className
85
                    } else if (className) {
86
                        nodes = root.getElementsByClassName(className);
87
                    } else { // default to tagName
88
                        nodes = root.getElementsByTagName(tagName);
89
                    }
90
 
91
                } else { // brute getElementsByTagName()
92
                    visited = [];
93
                    child = root.firstChild;
94
                    isUniversal = tagName === "*";
95
                    while (child) {
96
                        while (child) {
97
                            // IE 6-7 considers comment nodes as element nodes, and gives them the tagName "!".
98
                            // We can filter them out by checking if its tagName is > "@".
99
                            // This also avoids a superflous nodeType === 1 check.
100
                            if (child.tagName > "@" && (isUniversal || child.tagName === tagName)) {
101
                                nodes.push(child);
102
                            }
103
 
104
                            // We may need to traverse back up the tree to find more unvisited subtrees.
105
                            visited.push(child);
106
                            child = child.firstChild;
107
                        }
108
 
109
                        // Find the most recently visited node who has a next sibling.
110
                        while (visited.length > 0 && !child) {
111
                            child = visited.pop().nextSibling;
112
                        }
113
                    }
114
                }
115
 
116
                if (nodes.length) {
117
                    ret = Selector._filterNodes(nodes, tokens, firstOnly);
118
                }
119
            }
120
 
121
            return ret;
122
        },
123
 
124
        _filterNodes: function(nodes, tokens, firstOnly) {
125
            var i = 0,
126
                j,
127
                len = tokens.length,
128
                n = len - 1,
129
                result = [],
130
                node = nodes[0],
131
                tmpNode = node,
132
                getters = Y.Selector.getters,
133
                operator,
134
                combinator,
135
                token,
136
                path,
137
                pass,
138
                value,
139
                tests,
140
                test;
141
 
142
            for (i = 0; (tmpNode = node = nodes[i++]);) {
143
                n = len - 1;
144
                path = null;
145
 
146
                testLoop:
147
                while (tmpNode && tmpNode.tagName) {
148
                    token = tokens[n];
149
                    tests = token.tests;
150
                    j = tests.length;
151
                    if (j && !pass) {
152
                        while ((test = tests[--j])) {
153
                            operator = test[1];
154
                            if (getters[test[0]]) {
155
                                value = getters[test[0]](tmpNode, test[0]);
156
                            } else {
157
                                value = tmpNode[test[0]];
158
                                if (test[0] === 'tagName' && !Selector._isXML) {
159
                                    value = value.toUpperCase();
160
                                }
161
                                if (typeof value != 'string' && value !== undefined && value.toString) {
162
                                    value = value.toString(); // coerce for comparison
163
                                } else if (value === undefined && tmpNode.getAttribute) {
164
                                    // use getAttribute for non-standard attributes
165
                                    value = tmpNode.getAttribute(test[0], 2); // 2 === force string for IE
166
                                }
167
                            }
168
 
169
                            if ((operator === '=' && value !== test[2]) ||  // fast path for equality
170
                                (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
171
                                operator.test && !operator.test(value)) ||  // regex test
172
                                (!operator.test && // protect against RegExp as function (webkit)
173
                                        typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
174
 
175
                                // skip non element nodes or non-matching tags
176
                                if ((tmpNode = tmpNode[path])) {
177
                                    while (tmpNode &&
178
                                        (!tmpNode.tagName ||
179
                                            (token.tagName && token.tagName !== tmpNode.tagName))
180
                                    ) {
181
                                        tmpNode = tmpNode[path];
182
                                    }
183
                                }
184
                                continue testLoop;
185
                            }
186
                        }
187
                    }
188
 
189
                    n--; // move to next token
190
                    // now that we've passed the test, move up the tree by combinator
191
                    if (!pass && (combinator = token.combinator)) {
192
                        path = combinator.axis;
193
                        tmpNode = tmpNode[path];
194
 
195
                        // skip non element nodes
196
                        while (tmpNode && !tmpNode.tagName) {
197
                            tmpNode = tmpNode[path];
198
                        }
199
 
200
                        if (combinator.direct) { // one pass only
201
                            path = null;
202
                        }
203
 
204
                    } else { // success if we made it this far
205
                        result.push(node);
206
                        if (firstOnly) {
207
                            return result;
208
                        }
209
                        break;
210
                    }
211
                }
212
            }
213
            node = tmpNode = null;
214
            return result;
215
        },
216
 
217
        combinators: {
218
            ' ': {
219
                axis: 'parentNode'
220
            },
221
 
222
            '>': {
223
                axis: 'parentNode',
224
                direct: true
225
            },
226
 
227
 
228
            '+': {
229
                axis: 'previousSibling',
230
                direct: true
231
            }
232
        },
233
 
234
        _parsers: [
235
            {
236
                name: ATTRIBUTES,
237
                re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
238
                fn: function(match, token) {
239
                    var operator = match[2] || '',
240
                        operators = Selector.operators,
241
                        escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
242
                        test;
243
 
244
                    // add prefiltering for ID and CLASS
245
                    if ((match[1] === 'id' && operator === '=') ||
246
                            (match[1] === 'className' &&
247
                            Y.config.doc.documentElement.getElementsByClassName &&
248
                            (operator === '~=' || operator === '='))) {
249
                        token.prefilter = match[1];
250
 
251
 
252
                        match[3] = escVal;
253
 
254
                        // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
255
                        token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
256
 
257
                    }
258
 
259
                    // add tests
260
                    if (operator in operators) {
261
                        test = operators[operator];
262
                        if (typeof test === 'string') {
263
                            match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
264
                            test = new RegExp(test.replace('{val}', match[3]));
265
                        }
266
                        match[2] = test;
267
                    }
268
                    if (!token.last || token.prefilter !== match[1]) {
269
                        return match.slice(1);
270
                    }
271
                }
272
            },
273
            {
274
                name: TAG_NAME,
275
                re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
276
                fn: function(match, token) {
277
                    var tag = match[1];
278
 
279
                    if (!Selector._isXML) {
280
                        tag = tag.toUpperCase();
281
                    }
282
 
283
                    token.tagName = tag;
284
 
285
                    if (tag !== '*' && (!token.last || token.prefilter)) {
286
                        return [TAG_NAME, '=', tag];
287
                    }
288
                    if (!token.prefilter) {
289
                        token.prefilter = 'tagName';
290
                    }
291
                }
292
            },
293
            {
294
                name: COMBINATOR,
295
                re: /^\s*([>+~]|\s)\s*/,
296
                fn: function(match, token) {
297
                }
298
            },
299
            {
300
                name: PSEUDOS,
301
                re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
302
                fn: function(match, token) {
303
                    var test = Selector[PSEUDOS][match[1]];
304
                    if (test) { // reorder match array and unescape special chars for tests
305
                        if (match[2]) {
306
                            match[2] = match[2].replace(/\\/g, '');
307
                        }
308
                        return [match[2], test];
309
                    } else { // selector token not supported (possibly missing CSS3 module)
310
                        return false;
311
                    }
312
                }
313
            }
314
            ],
315
 
316
        _getToken: function(token) {
317
            return {
318
                tagName: null,
319
                id: null,
320
                className: null,
321
                attributes: {},
322
                combinator: null,
323
                tests: []
324
            };
325
        },
326
 
327
        /*
328
            Break selector into token units per simple selector.
329
            Combinator is attached to the previous token.
330
         */
331
        _tokenize: function(selector) {
332
            selector = selector || '';
333
            selector = Selector._parseSelector(Y.Lang.trim(selector));
334
            var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
335
                query = selector, // original query for debug report
336
                tokens = [],    // array of tokens
337
                found = false,  // whether or not any matches were found this pass
338
                match,         // the regex match
339
                test,
340
                i, parser;
341
 
342
            /*
343
                Search for selector patterns, store, and strip them from the selector string
344
                until no patterns match (invalid selector) or we run out of chars.
345
 
346
                Multiple attributes and pseudos are allowed, in any order.
347
                for example:
348
                    'form:first-child[type=button]:not(button)[lang|=en]'
349
            */
350
            outer:
351
            do {
352
                found = false; // reset after full pass
353
                for (i = 0; (parser = Selector._parsers[i++]);) {
354
                    if ( (match = parser.re.exec(selector)) ) { // note assignment
355
                        if (parser.name !== COMBINATOR ) {
356
                            token.selector = selector;
357
                        }
358
                        selector = selector.replace(match[0], ''); // strip current match from selector
359
                        if (!selector.length) {
360
                            token.last = true;
361
                        }
362
 
363
                        if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
364
                            match[1] = Selector._attrFilters[match[1]];
365
                        }
366
 
367
                        test = parser.fn(match, token);
368
                        if (test === false) { // selector not supported
369
                            found = false;
370
                            break outer;
371
                        } else if (test) {
372
                            token.tests.push(test);
373
                        }
374
 
375
                        if (!selector.length || parser.name === COMBINATOR) {
376
                            tokens.push(token);
377
                            token = Selector._getToken(token);
378
                            if (parser.name === COMBINATOR) {
379
                                token.combinator = Y.Selector.combinators[match[1]];
380
                            }
381
                        }
382
                        found = true;
383
                    }
384
                }
385
            } while (found && selector.length);
386
 
387
            if (!found || selector.length) { // not fully parsed
388
                Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
389
                tokens = [];
390
            }
391
            return tokens;
392
        },
393
 
394
        _replaceMarkers: function(selector) {
395
            selector = selector.replace(/\[/g, '\uE003');
396
            selector = selector.replace(/\]/g, '\uE004');
397
 
398
            selector = selector.replace(/\(/g, '\uE005');
399
            selector = selector.replace(/\)/g, '\uE006');
400
            return selector;
401
        },
402
 
403
        _replaceShorthand: function(selector) {
404
            var shorthand = Y.Selector.shorthand,
405
                re;
406
 
407
            for (re in shorthand) {
408
                if (shorthand.hasOwnProperty(re)) {
409
                    selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
410
                }
411
            }
412
 
413
            return selector;
414
        },
415
 
416
        _parseSelector: function(selector) {
417
            var replaced = Y.Selector._replaceSelector(selector),
418
                selector = replaced.selector;
419
 
420
            // replace shorthand (".foo, #bar") after pseudos and attrs
421
            // to avoid replacing unescaped chars
422
            selector = Y.Selector._replaceShorthand(selector);
423
 
424
            selector = Y.Selector._restore('attr', selector, replaced.attrs);
425
            selector = Y.Selector._restore('pseudo', selector, replaced.pseudos);
426
 
427
            // replace braces and parens before restoring escaped chars
428
            // to avoid replacing ecaped markers
429
            selector = Y.Selector._replaceMarkers(selector);
430
            selector = Y.Selector._restore('esc', selector, replaced.esc);
431
 
432
            return selector;
433
        },
434
 
435
        _attrFilters: {
436
            'class': 'className',
437
            'for': 'htmlFor'
438
        },
439
 
440
        getters: {
441
            href: function(node, attr) {
442
                return Y.DOM.getAttribute(node, attr);
443
            },
444
 
445
            id: function(node, attr) {
446
                return Y.DOM.getId(node);
447
            }
448
        }
449
    };
450
 
451
Y.mix(Y.Selector, SelectorCSS2, true);
452
Y.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
453
 
454
// IE wants class with native queries
455
if (Y.Selector.useNative && Y.config.doc.querySelector) {
456
    Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
457
}
458
 
459
 
460
}, '3.18.1', {"requires": ["selector-native"]});