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