Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / docs / source / ComponentQuery.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-ComponentQuery'>/**
19 </span> * @class Ext.ComponentQuery
20  * @extends Object
21  * @singleton
22  *
23  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
24  * Ext.container.Container on the document with a similar syntax to a CSS selector.
25  *
26  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
27  *
28  * - `component` or `.component`
29  * - `gridpanel` or `.gridpanel`
30  *
31  * An itemId or id must be prefixed with a #
32  *
33  * - `#myContainer`
34  *
35  * Attributes must be wrapped in brackets
36  *
37  * - `component[autoScroll]`
38  * - `panel[title=&quot;Test&quot;]`
39  *
40  * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
41  * the candidate Component will be included in the query:
42  *
43  *     var disabledFields = myFormPanel.query(&quot;{isDisabled()}&quot;);
44  *
45  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
46  *
47  *     // Function receives array and returns a filtered array.
48  *     Ext.ComponentQuery.pseudos.invalid = function(items) {
49  *         var i = 0, l = items.length, c, result = [];
50  *         for (; i &lt; l; i++) {
51  *             if (!(c = items[i]).isValid()) {
52  *                 result.push(c);
53  *             }
54  *         }
55  *         return result;
56  *     };
57  *      
58  *     var invalidFields = myFormPanel.query('field:invalid');
59  *     if (invalidFields.length) {
60  *         invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
61  *         for (var i = 0, l = invalidFields.length; i &lt; l; i++) {
62  *             invalidFields[i].getEl().frame(&quot;red&quot;);
63  *         }
64  *     }
65  *
66  * Default pseudos include:
67  *
68  * - not
69  *
70  * Queries return an array of components.
71  * Here are some example queries.
72  *
73  *     // retrieve all Ext.Panels in the document by xtype
74  *     var panelsArray = Ext.ComponentQuery.query('panel');
75  *
76  *     // retrieve all Ext.Panels within the container with an id myCt
77  *     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
78  *
79  *     // retrieve all direct children which are Ext.Panels within myCt
80  *     var directChildPanel = Ext.ComponentQuery.query('#myCt &gt; panel');
81  *
82  *     // retrieve all grids and trees
83  *     var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
84  *
85  * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
86  * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
87  * {@link Ext.Component#up}.
88  */
89 Ext.define('Ext.ComponentQuery', {
90     singleton: true,
91     uses: ['Ext.ComponentManager']
92 }, function() {
93
94     var cq = this,
95
96         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
97         // as a member on each item in the passed array.
98         filterFnPattern = [
99             'var r = [],',
100                 'i = 0,',
101                 'it = items,',
102                 'l = it.length,',
103                 'c;',
104             'for (; i &lt; l; i++) {',
105                 'c = it[i];',
106                 'if (c.{0}) {',
107                    'r.push(c);',
108                 '}',
109             '}',
110             'return r;'
111         ].join(''),
112
113         filterItems = function(items, operation) {
114             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
115             // The operation's method loops over each item in the candidate array and
116             // returns an array of items which match its criteria
117             return operation.method.apply(this, [ items ].concat(operation.args));
118         },
119
120         getItems = function(items, mode) {
121             var result = [],
122                 i = 0,
123                 length = items.length,
124                 candidate,
125                 deep = mode !== '&gt;';
126                 
127             for (; i &lt; length; i++) {
128                 candidate = items[i];
129                 if (candidate.getRefItems) {
130                     result = result.concat(candidate.getRefItems(deep));
131                 }
132             }
133             return result;
134         },
135
136         getAncestors = function(items) {
137             var result = [],
138                 i = 0,
139                 length = items.length,
140                 candidate;
141             for (; i &lt; length; i++) {
142                 candidate = items[i];
143                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
144                     result.push(candidate);
145                 }
146             }
147             return result;
148         },
149
150         // Filters the passed candidate array and returns only items which match the passed xtype
151         filterByXType = function(items, xtype, shallow) {
152             if (xtype === '*') {
153                 return items.slice();
154             }
155             else {
156                 var result = [],
157                     i = 0,
158                     length = items.length,
159                     candidate;
160                 for (; i &lt; length; i++) {
161                     candidate = items[i];
162                     if (candidate.isXType(xtype, shallow)) {
163                         result.push(candidate);
164                     }
165                 }
166                 return result;
167             }
168         },
169
170         // Filters the passed candidate array and returns only items which have the passed className
171         filterByClassName = function(items, className) {
172             var EA = Ext.Array,
173                 result = [],
174                 i = 0,
175                 length = items.length,
176                 candidate;
177             for (; i &lt; length; i++) {
178                 candidate = items[i];
179                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
180                     result.push(candidate);
181                 }
182             }
183             return result;
184         },
185
186         // Filters the passed candidate array and returns only items which have the specified property match
187         filterByAttribute = function(items, property, operator, value) {
188             var result = [],
189                 i = 0,
190                 length = items.length,
191                 candidate;
192             for (; i &lt; length; i++) {
193                 candidate = items[i];
194                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
195                     result.push(candidate);
196                 }
197             }
198             return result;
199         },
200
201         // Filters the passed candidate array and returns only items which have the specified itemId or id
202         filterById = function(items, id) {
203             var result = [],
204                 i = 0,
205                 length = items.length,
206                 candidate;
207             for (; i &lt; length; i++) {
208                 candidate = items[i];
209                 if (candidate.getItemId() === id) {
210                     result.push(candidate);
211                 }
212             }
213             return result;
214         },
215
216         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
217         filterByPseudo = function(items, name, value) {
218             return cq.pseudos[name](items, value);
219         },
220
221         // Determines leading mode
222         // &gt; for direct child, and ^ to switch to ownerCt axis
223         modeRe = /^(\s?([&gt;\^])\s?|\s|$)/,
224
225         // Matches a token with possibly (true|false) appended for the &quot;shallow&quot; parameter
226         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
227
228         matchers = [{
229             // Checks for .xtype with possibly (true|false) appended for the &quot;shallow&quot; parameter
230             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
231             method: filterByXType
232         },{
233             // checks for [attribute=value]
234             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['&quot;]?(.*?)[&quot;']?)?[\]])/,
235             method: filterByAttribute
236         }, {
237             // checks for #cmpItemId
238             re: /^#([\w\-]+)/,
239             method: filterById
240         }, {
241             // checks for :&lt;pseudo_class&gt;(&lt;selector&gt;)
242             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s&gt;\/]*?(?!\})))\))?/,
243             method: filterByPseudo
244         }, {
245             // checks for {&lt;member_expression&gt;}
246             re: /^(?:\{([^\}]+)\})/,
247             method: filterFnPattern
248         }];
249
250 <span id='Ext-ComponentQuery-Query'>    /**
251 </span>     * @class Ext.ComponentQuery.Query
252      * @extends Object
253      * @private
254      */
255     cq.Query = Ext.extend(Object, {
256         constructor: function(cfg) {
257             cfg = cfg || {};
258             Ext.apply(this, cfg);
259         },
260
261 <span id='Ext-ComponentQuery-Query-method-execute'>        /**
262 </span>         * @private
263          * Executes this Query upon the selected root.
264          * The root provides the initial source of candidate Component matches which are progressively
265          * filtered by iterating through this Query's operations cache.
266          * If no root is provided, all registered Components are searched via the ComponentManager.
267          * root may be a Container who's descendant Components are filtered
268          * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
269          * docked items within a Panel.
270          * root may be an array of candidate Components to filter using this Query.
271          */
272         execute : function(root) {
273             var operations = this.operations,
274                 i = 0,
275                 length = operations.length,
276                 operation,
277                 workingItems;
278
279             // no root, use all Components in the document
280             if (!root) {
281                 workingItems = Ext.ComponentManager.all.getArray();
282             }
283             // Root is a candidate Array
284             else if (Ext.isArray(root)) {
285                 workingItems = root;
286             }
287
288             // We are going to loop over our operations and take care of them
289             // one by one.
290             for (; i &lt; length; i++) {
291                 operation = operations[i];
292
293                 // The mode operation requires some custom handling.
294                 // All other operations essentially filter down our current
295                 // working items, while mode replaces our current working
296                 // items by getting children from each one of our current
297                 // working items. The type of mode determines the type of
298                 // children we get. (e.g. &gt; only gets direct children)
299                 if (operation.mode === '^') {
300                     workingItems = getAncestors(workingItems || [root]);
301                 }
302                 else if (operation.mode) {
303                     workingItems = getItems(workingItems || [root], operation.mode);
304                 }
305                 else {
306                     workingItems = filterItems(workingItems || getItems([root]), operation);
307                 }
308
309                 // If this is the last operation, it means our current working
310                 // items are the final matched items. Thus return them!
311                 if (i === length -1) {
312                     return workingItems;
313                 }
314             }
315             return [];
316         },
317
318         is: function(component) {
319             var operations = this.operations,
320                 components = Ext.isArray(component) ? component : [component],
321                 originalLength = components.length,
322                 lastOperation = operations[operations.length-1],
323                 ln, i;
324
325             components = filterItems(components, lastOperation);
326             if (components.length === originalLength) {
327                 if (operations.length &gt; 1) {
328                     for (i = 0, ln = components.length; i &lt; ln; i++) {
329                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
330                             return false;
331                         }
332                     }
333                 }
334                 return true;
335             }
336             return false;
337         }
338     });
339
340     Ext.apply(this, {
341
342         // private cache of selectors and matching ComponentQuery.Query objects
343         cache: {},
344
345         // private cache of pseudo class filter functions
346         pseudos: {
347             not: function(components, selector){
348                 var CQ = Ext.ComponentQuery,
349                     i = 0,
350                     length = components.length,
351                     results = [],
352                     index = -1,
353                     component;
354                 
355                 for(; i &lt; length; ++i) {
356                     component = components[i];
357                     if (!CQ.is(component, selector)) {
358                         results[++index] = component;
359                     }
360                 }
361                 return results;
362             }
363         },
364
365 <span id='Ext-ComponentQuery-method-query'>        /**
366 </span>         * Returns an array of matched Components from within the passed root object.
367          *
368          * This method filters returned Components in a similar way to how CSS selector based DOM
369          * queries work using a textual selector string.
370          *
371          * See class summary for details.
372          *
373          * @param {String} selector The selector string to filter returned Components
374          * @param {Ext.container.Container} root The Container within which to perform the query.
375          * If omitted, all Components within the document are included in the search.
376          * 
377          * This parameter may also be an array of Components to filter according to the selector.&lt;/p&gt;
378          * @returns {[Ext.Component]} The matched Components.
379          * 
380          * @member Ext.ComponentQuery
381          */
382         query: function(selector, root) {
383             var selectors = selector.split(','),
384                 length = selectors.length,
385                 i = 0,
386                 results = [],
387                 noDupResults = [], 
388                 dupMatcher = {}, 
389                 query, resultsLn, cmp;
390
391             for (; i &lt; length; i++) {
392                 selector = Ext.String.trim(selectors[i]);
393                 query = this.cache[selector];
394                 if (!query) {
395                     this.cache[selector] = query = this.parse(selector);
396                 }
397                 results = results.concat(query.execute(root));
398             }
399
400             // multiple selectors, potential to find duplicates
401             // lets filter them out.
402             if (length &gt; 1) {
403                 resultsLn = results.length;
404                 for (i = 0; i &lt; resultsLn; i++) {
405                     cmp = results[i];
406                     if (!dupMatcher[cmp.id]) {
407                         noDupResults.push(cmp);
408                         dupMatcher[cmp.id] = true;
409                     }
410                 }
411                 results = noDupResults;
412             }
413             return results;
414         },
415
416 <span id='Ext-ComponentQuery-method-is'>        /**
417 </span>         * Tests whether the passed Component matches the selector string.
418          * @param {Ext.Component} component The Component to test
419          * @param {String} selector The selector string to test against.
420          * @return {Boolean} True if the Component matches the selector.
421          * @member Ext.ComponentQuery
422          */
423         is: function(component, selector) {
424             if (!selector) {
425                 return true;
426             }
427             var query = this.cache[selector];
428             if (!query) {
429                 this.cache[selector] = query = this.parse(selector);
430             }
431             return query.is(component);
432         },
433
434         parse: function(selector) {
435             var operations = [],
436                 length = matchers.length,
437                 lastSelector,
438                 tokenMatch,
439                 matchedChar,
440                 modeMatch,
441                 selectorMatch,
442                 i, matcher, method;
443
444             // We are going to parse the beginning of the selector over and
445             // over again, slicing off the selector any portions we converted into an
446             // operation, until it is an empty string.
447             while (selector &amp;&amp; lastSelector !== selector) {
448                 lastSelector = selector;
449
450                 // First we check if we are dealing with a token like #, * or an xtype
451                 tokenMatch = selector.match(tokenRe);
452
453                 if (tokenMatch) {
454                     matchedChar = tokenMatch[1];
455
456                     // If the token is prefixed with a # we push a filterById operation to our stack
457                     if (matchedChar === '#') {
458                         operations.push({
459                             method: filterById,
460                             args: [Ext.String.trim(tokenMatch[2])]
461                         });
462                     }
463                     // If the token is prefixed with a . we push a filterByClassName operation to our stack
464                     // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
465                     else if (matchedChar === '.') {
466                         operations.push({
467                             method: filterByClassName,
468                             args: [Ext.String.trim(tokenMatch[2])]
469                         });
470                     }
471                     // If the token is a * or an xtype string, we push a filterByXType
472                     // operation to the stack.
473                     else {
474                         operations.push({
475                             method: filterByXType,
476                             args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
477                         });
478                     }
479
480                     // Now we slice of the part we just converted into an operation
481                     selector = selector.replace(tokenMatch[0], '');
482                 }
483
484                 // If the next part of the query is not a space or &gt; or ^, it means we
485                 // are going to check for more things that our current selection
486                 // has to comply to.
487                 while (!(modeMatch = selector.match(modeRe))) {
488                     // Lets loop over each type of matcher and execute it
489                     // on our current selector.
490                     for (i = 0; selector &amp;&amp; i &lt; length; i++) {
491                         matcher = matchers[i];
492                         selectorMatch = selector.match(matcher.re);
493                         method = matcher.method;
494
495                         // If we have a match, add an operation with the method
496                         // associated with this matcher, and pass the regular
497                         // expression matches are arguments to the operation.
498                         if (selectorMatch) {
499                             operations.push({
500                                 method: Ext.isString(matcher.method)
501                                     // Turn a string method into a function by formatting the string with our selector matche expression
502                                     // A new method is created for different match expressions, eg {id=='textfield-1024'}
503                                     // Every expression may be different in different selectors.
504                                     ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
505                                     : matcher.method,
506                                 args: selectorMatch.slice(1)
507                             });
508                             selector = selector.replace(selectorMatch[0], '');
509                             break; // Break on match
510                         }
511                         //&lt;debug&gt;
512                         // Exhausted all matches: It's an error
513                         if (i === (length - 1)) {
514                             Ext.Error.raise('Invalid ComponentQuery selector: &quot;' + arguments[0] + '&quot;');
515                         }
516                         //&lt;/debug&gt;
517                     }
518                 }
519
520                 // Now we are going to check for a mode change. This means a space
521                 // or a &gt; to determine if we are going to select all the children
522                 // of the currently matched items, or a ^ if we are going to use the
523                 // ownerCt axis as the candidate source.
524                 if (modeMatch[1]) { // Assignment, and test for truthiness!
525                     operations.push({
526                         mode: modeMatch[2]||modeMatch[1]
527                     });
528                     selector = selector.replace(modeMatch[0], '');
529                 }
530             }
531
532             //  Now that we have all our operations in an array, we are going
533             // to create a new Query using these operations.
534             return new cq.Query({
535                 operations: operations
536             });
537         }
538     });
539 });</pre>
540 </body>
541 </html>