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