]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/dom/selector.js
Release 6.2.0beta4
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / dom / selector.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-native', function(Y) {
9
10 (function(Y) {
11 /**
12  * The selector-native module provides support for native querySelector
13  * @module dom
14  * @submodule selector-native
15  * @for Selector
16  */
17
18 /**
19  * Provides support for using CSS selectors to query the DOM 
20  * @class Selector 
21  * @static
22  * @for Selector
23  */
24
25 Y.namespace('Selector'); // allow native module to standalone
26
27 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
28     OWNER_DOCUMENT = 'ownerDocument',
29     TMP_PREFIX = 'yui-tmp-',
30     g_counter = 0;
31
32 var Selector = {
33     _foundCache: [],
34
35     useNative: true,
36
37     _compare: ('sourceIndex' in document.documentElement) ?
38         function(nodeA, nodeB) {
39             var a = nodeA.sourceIndex,
40                 b = nodeB.sourceIndex;
41
42             if (a === b) {
43                 return 0;
44             } else if (a > b) {
45                 return 1;
46             }
47
48             return -1;
49
50         } : (document.documentElement[COMPARE_DOCUMENT_POSITION] ?
51         function(nodeA, nodeB) {
52             if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
53                 return -1;
54             } else {
55                 return 1;
56             }
57         } :
58         function(nodeA, nodeB) {
59             var rangeA, rangeB, compare;
60             if (nodeA && nodeB) {
61                 rangeA = nodeA[OWNER_DOCUMENT].createRange();
62                 rangeA.setStart(nodeA, 0);
63                 rangeB = nodeB[OWNER_DOCUMENT].createRange();
64                 rangeB.setStart(nodeB, 0);
65                 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
66             }
67
68             return compare;
69         
70     }),
71
72     _sort: function(nodes) {
73         if (nodes) {
74             nodes = Y.Array(nodes, 0, true);
75             if (nodes.sort) {
76                 nodes.sort(Selector._compare);
77             }
78         }
79
80         return nodes;
81     },
82
83     _deDupe: function(nodes) {
84         var ret = [],
85             i, node;
86
87         for (i = 0; (node = nodes[i++]);) {
88             if (!node._found) {
89                 ret[ret.length] = node;
90                 node._found = true;
91             }
92         }
93
94         for (i = 0; (node = ret[i++]);) {
95             node._found = null;
96             node.removeAttribute('_found');
97         }
98
99         return ret;
100     },
101
102     /**
103      * Retrieves a set of nodes based on a given CSS selector. 
104      * @method query
105      *
106      * @param {string} selector The CSS Selector to test the node against.
107      * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
108      * @param {Boolean} firstOnly optional Whether or not to return only the first match.
109      * @return {Array} An array of nodes that match the given selector.
110      * @static
111      */
112     query: function(selector, root, firstOnly, skipNative) {
113         root = root || Y.config.doc;
114         var ret = [],
115             useNative = (Y.Selector.useNative && document.querySelector && !skipNative),
116             queries = [[selector, root]],
117             query,
118             result,
119             i,
120             fn = (useNative) ? Y.Selector._nativeQuery : Y.Selector._bruteQuery;
121
122         if (selector && fn) {
123             // split group into seperate queries
124             if (!skipNative && // already done if skipping
125                     (!useNative || root.tagName)) { // split native when element scoping is needed
126                 queries = Selector._splitQueries(selector, root);
127             }
128
129             for (i = 0; (query = queries[i++]);) {
130                 result = fn(query[0], query[1], firstOnly);
131                 if (!firstOnly) { // coerce DOM Collection to Array
132                     result = Y.Array(result, 0, true);
133                 }
134                 if (result) {
135                     ret = ret.concat(result);
136                 }
137             }
138
139             if (queries.length > 1) { // remove dupes and sort by doc order 
140                 ret = Selector._sort(Selector._deDupe(ret));
141             }
142         }
143
144         return (firstOnly) ? (ret[0] || null) : ret;
145
146     },
147
148     // allows element scoped queries to begin with combinator
149     // e.g. query('> p', document.body) === query('body > p')
150     _splitQueries: function(selector, node) {
151         var groups = selector.split(','),
152             queries = [],
153             prefix = '',
154             i, len;
155
156         if (node) {
157             // enforce for element scoping
158             if (node.tagName) {
159                 node.id = node.id || Y.guid();
160                 prefix = '#' + node.id + ' ';
161             }
162
163             for (i = 0, len = groups.length; i < len; ++i) {
164                 selector =  prefix + groups[i];
165                 queries.push([selector, node]);
166             }
167         }
168
169         return queries;
170     },
171
172     _nativeQuery: function(selector, root, one) {
173         try {
174             return root['querySelector' + (one ? '' : 'All')](selector);
175         } catch(e) { // fallback to brute if available
176             return Y.Selector.query(selector, root, one, true); // redo with skipNative true
177         }
178     },
179
180     filter: function(nodes, selector) {
181         var ret = [],
182             i, node;
183
184         if (nodes && selector) {
185             for (i = 0; (node = nodes[i++]);) {
186                 if (Y.Selector.test(node, selector)) {
187                     ret[ret.length] = node;
188                 }
189             }
190         } else {
191         }
192
193         return ret;
194     },
195
196     test: function(node, selector, root) {
197         var ret = false,
198             groups = selector.split(','),
199             item,
200             i, group;
201
202         if (node && node.tagName) { // only test HTMLElements
203             root = root || node.ownerDocument;
204
205             if (!node.id) {
206                 node.id = TMP_PREFIX + g_counter++;
207             }
208             for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
209                 group += '#' + node.id; // add ID for uniqueness
210                 item = Y.Selector.query(group, root, true);
211                 ret = (item === node);
212                 if (ret) {
213                     break;
214                 }
215             }
216         }
217
218         return ret;
219     }
220 };
221
222 Y.mix(Y.Selector, Selector, true);
223
224 })(Y);
225
226
227 }, '3.0.0' ,{requires:['dom-base']});
228 YUI.add('selector-css2', function(Y) {
229
230 /**
231  * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
232  * @module dom
233  * @submodule selector-css2
234  * @for Selector
235  */
236
237 /**
238  * Provides helper methods for collecting and filtering DOM elements.
239  */
240
241 var PARENT_NODE = 'parentNode',
242     TAG_NAME = 'tagName',
243     ATTRIBUTES = 'attributes',
244     COMBINATOR = 'combinator',
245     PSEUDOS = 'pseudos',
246
247     Selector = Y.Selector,
248
249     SelectorCSS2 = {
250         SORT_RESULTS: true,
251         _children: function(node, tag) {
252             var ret = node.children,
253                 i,
254                 children = [],
255                 childNodes,
256                 child;
257
258             if (node.children && tag && node.children.tags) {
259                 children = node.children.tags(tag);
260             } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
261                 childNodes = ret || node.childNodes;
262                 ret = [];
263                 for (i = 0; (child = childNodes[i++]);) {
264                     if (child.tagName) {
265                         if (!tag || tag === child.tagName) {
266                             ret.push(child);
267                         }
268                     }
269                 }
270             }
271
272             return ret || [];
273         },
274
275         _regexCache: {},
276
277         _re: {
278             attr: /(\[.*\])/g,
279             pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\)))*/i
280         },
281
282         /**
283          * Mapping of shorthand tokens to corresponding attribute selector 
284          * @property shorthand
285          * @type object
286          */
287         shorthand: {
288             '\\#(-?[_a-z]+[-\\w]*)': '[id=$1]',
289             '\\.(-?[_a-z]+[-\\w]*)': '[className~=$1]'
290         },
291
292         /**
293          * List of operators and corresponding boolean functions. 
294          * These functions are passed the attribute and the current node's value of the attribute.
295          * @property operators
296          * @type object
297          */
298         operators: {
299             '': function(node, attr) { return Y.DOM.getAttribute(node, attr) !== ''; }, // Just test for existence of attribute
300             //'': '.+',
301             //'=': '^{val}$', // equality
302             '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
303             '|=': '^{val}-?' // optional hyphen-delimited
304         },
305
306         pseudos: {
307            'first-child': function(node) { 
308                 return Y.Selector._children(node[PARENT_NODE])[0] === node; 
309             } 
310         },
311
312         _bruteQuery: function(selector, root, firstOnly) {
313             var ret = [],
314                 nodes = [],
315                 tokens = Selector._tokenize(selector),
316                 token = tokens[tokens.length - 1],
317                 rootDoc = Y.DOM._getDoc(root),
318                 id,
319                 className,
320                 tagName;
321
322
323             // if we have an initial ID, set to root when in document
324             if (tokens[0] && rootDoc === root &&  
325                     (id = tokens[0].id) &&
326                     rootDoc.getElementById(id)) {
327                 root = rootDoc.getElementById(id);
328             }
329
330             if (token) {
331                 // prefilter nodes
332                 id = token.id;
333                 className = token.className;
334                 tagName = token.tagName || '*';
335
336                 // try ID first
337                 if (id) {
338                     if (rootDoc.getElementById(id)) { // if in document
339                     nodes = [rootDoc.getElementById(id)]; // TODO: DOM.byId?
340                 }
341                 // try className if supported
342                 } else if (className) {
343                     nodes = root.getElementsByClassName(className);
344                 } else if (tagName) { // default to tagName
345                     nodes = root.getElementsByTagName(tagName || '*');
346                 }
347
348                 if (nodes.length) {
349                     ret = Selector._filterNodes(nodes, tokens, firstOnly);
350                 }
351             }
352
353             return ret;
354         },
355         
356         _filterNodes: function(nodes, tokens, firstOnly) {
357             var i = 0,
358                 j,
359                 len = tokens.length,
360                 n = len - 1,
361                 result = [],
362                 node = nodes[0],
363                 tmpNode = node,
364                 getters = Y.Selector.getters,
365                 operator,
366                 combinator,
367                 token,
368                 path,
369                 pass,
370                 //FUNCTION = 'function',
371                 value,
372                 tests,
373                 test;
374
375             //do {
376             for (i = 0; (tmpNode = node = nodes[i++]);) {
377                 n = len - 1;
378                 path = null;
379                 
380                 testLoop:
381                 while (tmpNode && tmpNode.tagName) {
382                     token = tokens[n];
383                     tests = token.tests;
384                     j = tests.length;
385                     if (j && !pass) {
386                         while ((test = tests[--j])) {
387                             operator = test[1];
388                             if (getters[test[0]]) {
389                                 value = getters[test[0]](tmpNode, test[0]);
390                             } else {
391                                 value = tmpNode[test[0]];
392                                 // use getAttribute for non-standard attributes
393                                 if (value === undefined && tmpNode.getAttribute) {
394                                     value = tmpNode.getAttribute(test[0]);
395                                 }
396                             }
397
398                             if ((operator === '=' && value !== test[2]) ||  // fast path for equality
399                                 (operator.test && !operator.test(value)) ||  // regex test
400                                 (operator.call && !operator(tmpNode, test[0]))) { // function test
401
402                                 // skip non element nodes or non-matching tags
403                                 if ((tmpNode = tmpNode[path])) {
404                                     while (tmpNode &&
405                                         (!tmpNode.tagName ||
406                                             (token.tagName && token.tagName !== tmpNode.tagName))
407                                     ) {
408                                         tmpNode = tmpNode[path]; 
409                                     }
410                                 }
411                                 continue testLoop;
412                             }
413                         }
414                     }
415
416                     n--; // move to next token
417                     // now that we've passed the test, move up the tree by combinator
418                     if (!pass && (combinator = token.combinator)) {
419                         path = combinator.axis;
420                         tmpNode = tmpNode[path];
421
422                         // skip non element nodes
423                         while (tmpNode && !tmpNode.tagName) {
424                             tmpNode = tmpNode[path]; 
425                         }
426
427                         if (combinator.direct) { // one pass only
428                             path = null; 
429                         }
430
431                     } else { // success if we made it this far
432                         result.push(node);
433                         if (firstOnly) {
434                             return result;
435                         }
436                         break;
437                     }
438                 }
439             }// while (tmpNode = node = nodes[++i]);
440             node = tmpNode = null;
441             return result;
442         },
443
444         _getRegExp: function(str, flags) {
445             var regexCache = Selector._regexCache;
446             flags = flags || '';
447             if (!regexCache[str + flags]) {
448                 regexCache[str + flags] = new RegExp(str, flags);
449             }
450             return regexCache[str + flags];
451         },
452
453         combinators: {
454             ' ': {
455                 axis: 'parentNode'
456             },
457
458             '>': {
459                 axis: 'parentNode',
460                 direct: true
461             },
462
463
464             '+': {
465                 axis: 'previousSibling',
466                 direct: true
467             }
468         },
469
470         _parsers: [
471             {
472                 name: ATTRIBUTES,
473                 re: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
474                 fn: function(match, token) {
475                     var operator = match[2] || '',
476                         operators = Y.Selector.operators,
477                         test;
478
479                     // add prefiltering for ID and CLASS
480                     if ((match[1] === 'id' && operator === '=') ||
481                             (match[1] === 'className' &&
482                             document.getElementsByClassName &&
483                             (operator === '~=' || operator === '='))) {
484                         token.prefilter = match[1];
485                         token[match[1]] = match[3];
486                     }
487
488                     // add tests
489                     if (operator in operators) {
490                         test = operators[operator];
491                         if (typeof test === 'string') {
492                             test = Y.Selector._getRegExp(test.replace('{val}', match[3]));
493                         }
494                         match[2] = test;
495                     }
496                     if (!token.last || token.prefilter !== match[1]) {
497                         return match.slice(1);
498                     }
499                 }
500
501             },
502             {
503                 name: TAG_NAME,
504                 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
505                 fn: function(match, token) {
506                     var tag = match[1].toUpperCase();
507                     token.tagName = tag;
508
509                     if (tag !== '*' && (!token.last || token.prefilter)) {
510                         return [TAG_NAME, '=', tag];
511                     }
512                     if (!token.prefilter) {
513                         token.prefilter = 'tagName';
514                     }
515                 }
516             },
517             {
518                 name: COMBINATOR,
519                 re: /^\s*([>+~]|\s)\s*/,
520                 fn: function(match, token) {
521                 }
522             },
523             {
524                 name: PSEUDOS,
525                 re: /^:([\-\w]+)(?:\(['"]?(.+)['"]?\))*/i,
526                 fn: function(match, token) {
527                     var test = Selector[PSEUDOS][match[1]];
528                     if (test) { // reorder match array
529                         return [match[2], test];
530                     } else { // selector token not supported (possibly missing CSS3 module)
531                         return false;
532                     }
533                 }
534             }
535             ],
536
537         _getToken: function(token) {
538             return {
539                 tagName: null,
540                 id: null,
541                 className: null,
542                 attributes: {},
543                 combinator: null,
544                 tests: []
545             };
546         },
547
548         /**
549             Break selector into token units per simple selector.
550             Combinator is attached to the previous token.
551          */
552         _tokenize: function(selector) {
553             selector = selector || '';
554             selector = Selector._replaceShorthand(Y.Lang.trim(selector)); 
555             var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
556                 query = selector, // original query for debug report
557                 tokens = [],    // array of tokens
558                 found = false,  // whether or not any matches were found this pass
559                 match,         // the regex match
560                 test,
561                 i, parser;
562
563             /*
564                 Search for selector patterns, store, and strip them from the selector string
565                 until no patterns match (invalid selector) or we run out of chars.
566
567                 Multiple attributes and pseudos are allowed, in any order.
568                 for example:
569                     'form:first-child[type=button]:not(button)[lang|=en]'
570             */
571             outer:
572             do {
573                 found = false; // reset after full pass
574                 for (i = 0; (parser = Selector._parsers[i++]);) {
575                     if ( (match = parser.re.exec(selector)) ) { // note assignment
576                         if (parser !== COMBINATOR ) {
577                             token.selector = selector;
578                         }
579                         selector = selector.replace(match[0], ''); // strip current match from selector
580                         if (!selector.length) {
581                             token.last = true;
582                         }
583
584                         if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
585                             match[1] = Selector._attrFilters[match[1]];
586                         }
587
588                         test = parser.fn(match, token);
589                         if (test === false) { // selector not supported
590                             found = false;
591                             break outer;
592                         } else if (test) {
593                             token.tests.push(test);
594                         }
595
596                         if (!selector.length || parser.name === COMBINATOR) {
597                             tokens.push(token);
598                             token = Selector._getToken(token);
599                             if (parser.name === COMBINATOR) {
600                                 token.combinator = Y.Selector.combinators[match[1]];
601                             }
602                         }
603                         found = true;
604                     }
605                 }
606             } while (found && selector.length);
607
608             if (!found || selector.length) { // not fully parsed
609                 tokens = [];
610             }
611             return tokens;
612         },
613
614         _replaceShorthand: function(selector) {
615             var shorthand = Selector.shorthand,
616                 attrs = selector.match(Selector._re.attr), // pull attributes to avoid false pos on "." and "#"
617                 pseudos = selector.match(Selector._re.pseudos), // pull attributes to avoid false pos on "." and "#"
618                 re, i, len;
619
620             if (pseudos) {
621                 selector = selector.replace(Selector._re.pseudos, '!!REPLACED_PSEUDO!!');
622             }
623
624             if (attrs) {
625                 selector = selector.replace(Selector._re.attr, '!!REPLACED_ATTRIBUTE!!');
626             }
627
628             for (re in shorthand) {
629                 if (shorthand.hasOwnProperty(re)) {
630                     selector = selector.replace(Selector._getRegExp(re, 'gi'), shorthand[re]);
631                 }
632             }
633
634             if (attrs) {
635                 for (i = 0, len = attrs.length; i < len; ++i) {
636                     selector = selector.replace('!!REPLACED_ATTRIBUTE!!', attrs[i]);
637                 }
638             }
639             if (pseudos) {
640                 for (i = 0, len = pseudos.length; i < len; ++i) {
641                     selector = selector.replace('!!REPLACED_PSEUDO!!', pseudos[i]);
642                 }
643             }
644             return selector;
645         },
646
647         _attrFilters: {
648             'class': 'className',
649             'for': 'htmlFor'
650         },
651
652         getters: {
653             href: function(node, attr) {
654                 return Y.DOM.getAttribute(node, attr);
655             }
656         }
657     };
658
659 Y.mix(Y.Selector, SelectorCSS2, true);
660 Y.Selector.getters.src = Y.Selector.getters.rel = Y.Selector.getters.href;
661
662 // IE wants class with native queries
663 if (Y.Selector.useNative && document.querySelector) {
664     Y.Selector.shorthand['\\.(-?[_a-z]+[-\\w]*)'] = '[class~=$1]';
665 }
666
667
668
669 }, '3.0.0' ,{requires:['selector-native']});
670
671
672 YUI.add('selector', function(Y){}, '3.0.0' ,{use:['selector-native', 'selector-css2']});
673