]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/dom/selector-css2.js
Release 6.2.0beta4
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / dom / selector-css2.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 3.0.0
6 build: 1549
7 */
8 YUI.add('selector-css2', function(Y) {
9
10 /**
11  * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
12  * @module dom
13  * @submodule selector-css2
14  * @for Selector
15  */
16
17 /**
18  * Provides helper methods for collecting and filtering DOM elements.
19  */
20
21 var PARENT_NODE = 'parentNode',
22     TAG_NAME = 'tagName',
23     ATTRIBUTES = 'attributes',
24     COMBINATOR = 'combinator',
25     PSEUDOS = 'pseudos',
26
27     Selector = Y.Selector,
28
29     SelectorCSS2 = {
30         SORT_RESULTS: true,
31         _children: function(node, tag) {
32             var ret = node.children,
33                 i,
34                 children = [],
35                 childNodes,
36                 child;
37
38             if (node.children && tag && node.children.tags) {
39                 children = node.children.tags(tag);
40             } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
41                 childNodes = ret || node.childNodes;
42                 ret = [];
43                 for (i = 0; (child = childNodes[i++]);) {
44                     if (child.tagName) {
45                         if (!tag || tag === child.tagName) {
46                             ret.push(child);
47                         }
48                     }
49                 }
50             }
51
52             return ret || [];
53         },
54
55         _regexCache: {},
56
57         _re: {
58             attr: /(\[.*\])/g,
59             pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\)))*/i
60         },
61
62         /**
63          * Mapping of shorthand tokens to corresponding attribute selector 
64          * @property shorthand
65          * @type object
66          */
67         shorthand: {
68             '\\#(-?[_a-z]+[-\\w]*)': '[id=$1]',
69             '\\.(-?[_a-z]+[-\\w]*)': '[className~=$1]'
70         },
71
72         /**
73          * List of operators and corresponding boolean functions. 
74          * These functions are passed the attribute and the current node's value of the attribute.
75          * @property operators
76          * @type object
77          */
78         operators: {
79             '': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
80             //'': '.+',
81             //'=': '^{val}$', // equality
82             '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
83             '|=': '^{val}-?' // optional hyphen-delimited
84         },
85
86         pseudos: {
87            'first-child': function(node) { 
88                 return Y.Selector._children(node[PARENT_NODE])[0] === node; 
89             } 
90         },
91
92         _bruteQuery: function(selector, root, firstOnly) {
93             var ret = [],
94                 nodes = [],
95                 tokens = Selector._tokenize(selector),
96                 token = tokens[tokens.length - 1],
97                 rootDoc = Y.DOM._getDoc(root),
98                 id,
99                 className,
100                 tagName;
101
102
103             // if we have an initial ID, set to root when in document
104             if (tokens[0] && rootDoc === root &&  
105                     (id = tokens[0].id) &&
106                     rootDoc.getElementById(id)) {
107                 root = rootDoc.getElementById(id);
108             }
109
110             if (token) {
111                 // prefilter nodes
112                 id = token.id;
113                 className = token.className;
114                 tagName = token.tagName || '*';
115
116                 // try ID first
117                 if (id) {
118                     if (rootDoc.getElementById(id)) { // if in document
119                     nodes = [rootDoc.getElementById(id)]; // TODO: DOM.byId?
120                 }
121                 // try className if supported
122                 } else if (className) {
123                     nodes = root.getElementsByClassName(className);
124                 } else if (tagName) { // default to tagName
125                     nodes = root.getElementsByTagName(tagName || '*');
126                 }
127
128                 if (nodes.length) {
129                     ret = Selector._filterNodes(nodes, tokens, firstOnly);
130                 }
131             }
132
133             return ret;
134         },
135         
136         _filterNodes: function(nodes, tokens, firstOnly) {
137             var i = 0,
138                 j,
139                 len = tokens.length,
140                 n = len - 1,
141                 result = [],
142                 node = nodes[0],
143                 tmpNode = node,
144                 getters = Y.Selector.getters,
145                 operator,
146                 combinator,
147                 token,
148                 path,
149                 pass,
150                 //FUNCTION = 'function',
151                 value,
152                 tests,
153                 test;
154
155             //do {
156             for (i = 0; (tmpNode = node = nodes[i++]);) {
157                 n = len - 1;
158                 path = null;
159                 
160                 testLoop:
161                 while (tmpNode && tmpNode.tagName) {
162                     token = tokens[n];
163                     tests = token.tests;
164                     j = tests.length;
165                     if (j && !pass) {
166                         while ((test = tests[--j])) {
167                             operator = test[1];
168                             if (getters[test[0]]) {
169                                 value = getters[test[0]](tmpNode, test[0]);
170                             } else {
171                                 value = tmpNode[test[0]];
172                                 // use getAttribute for non-standard attributes
173                                 if (value === undefined && tmpNode.getAttribute) {
174                                     value = tmpNode.getAttribute(test[0]);
175                                 }
176                             }
177
178                             if ((operator === '=' && value !== test[2]) ||  // fast path for equality
179                                 (operator.test && !operator.test(value)) ||  // regex test
180                                 (operator.call && !operator(tmpNode, test[0]))) { // function test
181
182                                 // skip non element nodes or non-matching tags
183                                 if ((tmpNode = tmpNode[path])) {
184                                     while (tmpNode &&
185                                         (!tmpNode.tagName ||
186                                             (token.tagName && token.tagName !== tmpNode.tagName))
187                                     ) {
188                                         tmpNode = tmpNode[path]; 
189                                     }
190                                 }
191                                 continue testLoop;
192                             }
193                         }
194                     }
195
196                     n--; // move to next token
197                     // now that we've passed the test, move up the tree by combinator
198                     if (!pass && (combinator = token.combinator)) {
199                         path = combinator.axis;
200                         tmpNode = tmpNode[path];
201
202                         // skip non element nodes
203                         while (tmpNode && !tmpNode.tagName) {
204                             tmpNode = tmpNode[path]; 
205                         }
206
207                         if (combinator.direct) { // one pass only
208                             path = null; 
209                         }
210
211                     } else { // success if we made it this far
212                         result.push(node);
213                         if (firstOnly) {
214                             return result;
215                         }
216                         break;
217                     }
218                 }
219             }// while (tmpNode = node = nodes[++i]);
220             node = tmpNode = null;
221             return result;
222         },
223
224         _getRegExp: function(str, flags) {
225             var regexCache = Selector._regexCache;
226             flags = flags || '';
227             if (!regexCache[str + flags]) {
228                 regexCache[str + flags] = new RegExp(str, flags);
229             }
230             return regexCache[str + flags];
231         },
232
233         combinators: {
234             ' ': {
235                 axis: 'parentNode'
236             },
237
238             '>': {
239                 axis: 'parentNode',
240                 direct: true
241             },
242
243
244             '+': {
245                 axis: 'previousSibling',
246                 direct: true
247             }
248         },
249
250         _parsers: [
251             {
252                 name: ATTRIBUTES,
253                 re: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
254                 fn: function(match, token) {
255                     var operator = match[2] || '',
256                         operators = Y.Selector.operators,
257                         test;
258
259                     // add prefiltering for ID and CLASS
260                     if ((match[1] === 'id' && operator === '=') ||
261                             (match[1] === 'className' &&
262                             document.getElementsByClassName &&
263                             (operator === '~=' || operator === '='))) {
264                         token.prefilter = match[1];
265                         token[match[1]] = match[3];
266                     }
267
268                     // add tests
269                     if (operator in operators) {
270                         test = operators[operator];
271                         if (typeof test === 'string') {
272                             test = Y.Selector._getRegExp(test.replace('{val}', match[3]));
273                         }
274                         match[2] = test;
275                     }
276                     if (!token.last || token.prefilter !== match[1]) {
277                         return match.slice(1);
278                     }
279                 }
280
281             },
282             {
283                 name: TAG_NAME,
284                 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
285                 fn: function(match, token) {
286                     var tag = match[1].toUpperCase();
287                     token.tagName = tag;
288
289                     if (tag !== '*' && (!token.last || token.prefilter)) {
290                         return [TAG_NAME, '=', tag];
291                     }
292                     if (!token.prefilter) {
293                         token.prefilter = 'tagName';
294                     }
295                 }
296             },
297             {
298                 name: COMBINATOR,
299                 re: /^\s*([>+~]|\s)\s*/,
300                 fn: function(match, token) {
301                 }
302             },
303             {
304                 name: PSEUDOS,
305                 re: /^:([\-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
306                 fn: function(match, token) {
307                     var test = Selector[PSEUDOS][match[1]];
308                     if (test) { // reorder match array
309                         return [match[2], test];
310                     } else { // selector token not supported (possibly missing CSS3 module)
311                         return false;
312                     }
313                 }
314             }
315             ],
316
317         _getToken: function(token) {
318             return {
319                 tagName: null,
320                 id: null,
321                 className: null,
322                 attributes: {},
323                 combinator: null,
324                 tests: []
325             };
326         },
327
328         /**
329             Break selector into token units per simple selector.
330             Combinator is attached to the previous token.
331          */
332         _tokenize: function(selector) {
333             selector = selector || '';
334             selector = Selector._replaceShorthand(Y.Lang.trim(selector)); 
335             var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
336                 query = selector, // original query for debug report
337                 tokens = [],    // array of tokens
338                 found = false,  // whether or not any matches were found this pass
339                 match,         // the regex match
340                 test,
341                 i, parser;
342
343             /*
344                 Search for selector patterns, store, and strip them from the selector string
345                 until no patterns match (invalid selector) or we run out of chars.
346
347                 Multiple attributes and pseudos are allowed, in any order.
348                 for example:
349                     'form:first-child[type=button]:not(button)[lang|=en]'
350             */
351             outer:
352             do {
353                 found = false; // reset after full pass
354                 for (i = 0; (parser = Selector._parsers[i++]);) {
355                     if ( (match = parser.re.exec(selector)) ) { // note assignment
356                         if (parser !== COMBINATOR ) {
357                             token.selector = selector;
358                         }
359                         selector = selector.replace(match[0], ''); // strip current match from selector
360                         if (!selector.length) {
361                             token.last = true;
362                         }
363
364                         if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
365                             match[1] = Selector._attrFilters[match[1]];
366                         }
367
368                         test = parser.fn(match, token);
369                         if (test === false) { // selector not supported
370                             found = false;
371                             break outer;
372                         } else if (test) {
373                             token.tests.push(test);
374                         }
375
376                         if (!selector.length || parser.name === COMBINATOR) {
377                             tokens.push(token);
378                             token = Selector._getToken(token);
379                             if (parser.name === COMBINATOR) {
380                                 token.combinator = Y.Selector.combinators[match[1]];
381                             }
382                         }
383                         found = true;
384                     }
385                 }
386             } while (found && selector.length);
387
388             if (!found || selector.length) { // not fully parsed
389                 tokens = [];
390             }
391             return tokens;
392         },
393
394         _replaceShorthand: function(selector) {
395             var shorthand = Selector.shorthand,
396                 attrs = selector.match(Selector._re.attr), // pull attributes to avoid false pos on "." and "#"
397                 pseudos = selector.match(Selector._re.pseudos), // pull attributes to avoid false pos on "." and "#"
398                 re, i, len;
399
400             if (pseudos) {
401                 selector = selector.replace(Selector._re.pseudos, '!!REPLACED_PSEUDO!!');
402             }
403
404             if (attrs) {
405                 selector = selector.replace(Selector._re.attr, '!!REPLACED_ATTRIBUTE!!');
406             }
407
408             for (re in shorthand) {
409                 if (shorthand.hasOwnProperty(re)) {
410                     selector = selector.replace(Selector._getRegExp(re, 'gi'), shorthand[re]);
411                 }
412             }
413
414             if (attrs) {
415                 for (i = 0, len = attrs.length; i < len; ++i) {
416                     selector = selector.replace('!!REPLACED_ATTRIBUTE!!', attrs[i]);
417                 }
418             }
419             if (pseudos) {
420                 for (i = 0, len = pseudos.length; i < len; ++i) {
421                     selector = selector.replace('!!REPLACED_PSEUDO!!', pseudos[i]);
422                 }
423             }
424             return selector;
425         },
426
427         _attrFilters: {
428             'class': 'className',
429             'for': 'htmlFor'
430         },
431
432         getters: {
433             href: function(node, attr) {
434                 return Y.DOM.getAttribute(node, attr);
435             }
436         }
437     };
438
439 Y.mix(Y.Selector, SelectorCSS2, true);
440 Y.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
441
442 // IE wants class with native queries
443 if (Y.Selector.useNative && document.querySelector) {
444     Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
445 }
446
447
448
449 }, '3.0.0' ,{requires:['selector-native']});