Upgrade to ExtJS 4.0.1 - Released 05/18/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  *
22  * Provides searching of Components within Ext.ComponentManager (globally) or a specific
23  * Ext.container.Container on the document with a similar syntax to a CSS selector.
24  *
25  * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
26 &lt;ul&gt;
27     &lt;li&gt;component or .component&lt;/li&gt;
28     &lt;li&gt;gridpanel or .gridpanel&lt;/li&gt;
29 &lt;/ul&gt;
30  *
31  * An itemId or id must be prefixed with a #
32 &lt;ul&gt;
33     &lt;li&gt;#myContainer&lt;/li&gt;
34 &lt;/ul&gt;
35  *
36  *
37  * Attributes must be wrapped in brackets
38 &lt;ul&gt;
39     &lt;li&gt;component[autoScroll]&lt;/li&gt;
40     &lt;li&gt;panel[title=&quot;Test&quot;]&lt;/li&gt;
41 &lt;/ul&gt;
42  *
43  * Member expressions from candidate Components may be tested. If the expression returns a &lt;i&gt;truthy&lt;/i&gt; value,
44  * the candidate Component will be included in the query:&lt;pre&gt;&lt;code&gt;
45 var disabledFields = myFormPanel.query(&quot;{isDisabled()}&quot;);
46 &lt;/code&gt;&lt;/pre&gt;
47  *
48  * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:&lt;code&gt;&lt;pre&gt;
49 // Function receives array and returns a filtered array.
50 Ext.ComponentQuery.pseudos.invalid = function(items) {
51     var i = 0, l = items.length, c, result = [];
52     for (; i &lt; l; i++) {
53         if (!(c = items[i]).isValid()) {
54             result.push(c);
55         }
56     }
57     return result;
58 };
59
60 var invalidFields = myFormPanel.query('field:invalid');
61 if (invalidFields.length) {
62     invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
63     for (var i = 0, l = invalidFields.length; i &lt; l; i++) {
64         invalidFields[i].getEl().frame(&quot;red&quot;);
65     }
66 }
67 &lt;/pre&gt;&lt;/code&gt;
68  * &lt;p&gt;
69  * Default pseudos include:&lt;br /&gt;
70  * - not
71  * &lt;/p&gt;
72  *
73  * Queries return an array of components.
74  * Here are some example queries.
75 &lt;pre&gt;&lt;code&gt;
76     // retrieve all Ext.Panels in the document by xtype
77     var panelsArray = Ext.ComponentQuery.query('panel');
78
79     // retrieve all Ext.Panels within the container with an id myCt
80     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
81
82     // retrieve all direct children which are Ext.Panels within myCt
83     var directChildPanel = Ext.ComponentQuery.query('#myCt &gt; panel');
84
85     // retrieve all gridpanels and listviews
86     var gridsAndLists = Ext.ComponentQuery.query('gridpanel, listview');
87 &lt;/code&gt;&lt;/pre&gt;
88
89 For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
90 {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
91 {@link Ext.Component#up}.
92  * @singleton
93  */
94 Ext.define('Ext.ComponentQuery', {
95     singleton: true,
96     uses: ['Ext.ComponentManager']
97 }, function() {
98
99     var cq = this,
100
101         // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
102         // as a member on each item in the passed array.
103         filterFnPattern = [
104             'var r = [],',
105                 'i = 0,',
106                 'it = items,',
107                 'l = it.length,',
108                 'c;',
109             'for (; i &lt; l; i++) {',
110                 'c = it[i];',
111                 'if (c.{0}) {',
112                    'r.push(c);',
113                 '}',
114             '}',
115             'return r;'
116         ].join(''),
117
118         filterItems = function(items, operation) {
119             // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
120             // The operation's method loops over each item in the candidate array and
121             // returns an array of items which match its criteria
122             return operation.method.apply(this, [ items ].concat(operation.args));
123         },
124
125         getItems = function(items, mode) {
126             var result = [],
127                 i = 0,
128                 length = items.length,
129                 candidate,
130                 deep = mode !== '&gt;';
131                 
132             for (; i &lt; length; i++) {
133                 candidate = items[i];
134                 if (candidate.getRefItems) {
135                     result = result.concat(candidate.getRefItems(deep));
136                 }
137             }
138             return result;
139         },
140
141         getAncestors = function(items) {
142             var result = [],
143                 i = 0,
144                 length = items.length,
145                 candidate;
146             for (; i &lt; length; i++) {
147                 candidate = items[i];
148                 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
149                     result.push(candidate);
150                 }
151             }
152             return result;
153         },
154
155         // Filters the passed candidate array and returns only items which match the passed xtype
156         filterByXType = function(items, xtype, shallow) {
157             if (xtype === '*') {
158                 return items.slice();
159             }
160             else {
161                 var result = [],
162                     i = 0,
163                     length = items.length,
164                     candidate;
165                 for (; i &lt; length; i++) {
166                     candidate = items[i];
167                     if (candidate.isXType(xtype, shallow)) {
168                         result.push(candidate);
169                     }
170                 }
171                 return result;
172             }
173         },
174
175         // Filters the passed candidate array and returns only items which have the passed className
176         filterByClassName = function(items, className) {
177             var EA = Ext.Array,
178                 result = [],
179                 i = 0,
180                 length = items.length,
181                 candidate;
182             for (; i &lt; length; i++) {
183                 candidate = items[i];
184                 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
185                     result.push(candidate);
186                 }
187             }
188             return result;
189         },
190
191         // Filters the passed candidate array and returns only items which have the specified property match
192         filterByAttribute = function(items, property, operator, value) {
193             var result = [],
194                 i = 0,
195                 length = items.length,
196                 candidate;
197             for (; i &lt; length; i++) {
198                 candidate = items[i];
199                 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
200                     result.push(candidate);
201                 }
202             }
203             return result;
204         },
205
206         // Filters the passed candidate array and returns only items which have the specified itemId or id
207         filterById = function(items, id) {
208             var result = [],
209                 i = 0,
210                 length = items.length,
211                 candidate;
212             for (; i &lt; length; i++) {
213                 candidate = items[i];
214                 if (candidate.getItemId() === id) {
215                     result.push(candidate);
216                 }
217             }
218             return result;
219         },
220
221         // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
222         filterByPseudo = function(items, name, value) {
223             return cq.pseudos[name](items, value);
224         },
225
226         // Determines leading mode
227         // &gt; for direct child, and ^ to switch to ownerCt axis
228         modeRe = /^(\s?([&gt;\^])\s?|\s|$)/,
229
230         // Matches a token with possibly (true|false) appended for the &quot;shallow&quot; parameter
231         tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
232
233         matchers = [{
234             // Checks for .xtype with possibly (true|false) appended for the &quot;shallow&quot; parameter
235             re: /^\.([\w\-]+)(?:\((true|false)\))?/,
236             method: filterByXType
237         },{
238             // checks for [attribute=value]
239             re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['&quot;]?(.*?)[&quot;']?)?[\]])/,
240             method: filterByAttribute
241         }, {
242             // checks for #cmpItemId
243             re: /^#([\w\-]+)/,
244             method: filterById
245         }, {
246             // checks for :&lt;pseudo_class&gt;(&lt;selector&gt;)
247             re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s&gt;\/]*?(?!\})))\))?/,
248             method: filterByPseudo
249         }, {
250             // checks for {&lt;member_expression&gt;}
251             re: /^(?:\{([^\}]+)\})/,
252             method: filterFnPattern
253         }];
254
255 <span id='Ext-ComponentQuery-Query'>    /**
256 </span>     * @class Ext.ComponentQuery.Query
257      * @extends Object
258      * @private
259      */
260     cq.Query = Ext.extend(Object, {
261         constructor: function(cfg) {
262             cfg = cfg || {};
263             Ext.apply(this, cfg);
264         },
265
266 <span id='Ext-ComponentQuery-Query-method-execute'>        /**
267 </span>         * @private
268          * Executes this Query upon the selected root.
269          * The root provides the initial source of candidate Component matches which are progressively
270          * filtered by iterating through this Query's operations cache.
271          * If no root is provided, all registered Components are searched via the ComponentManager.
272          * root may be a Container who's descendant Components are filtered
273          * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
274          * docked items within a Panel.
275          * root may be an array of candidate Components to filter using this Query.
276          */
277         execute : function(root) {
278             var operations = this.operations,
279                 i = 0,
280                 length = operations.length,
281                 operation,
282                 workingItems;
283
284             // no root, use all Components in the document
285             if (!root) {
286                 workingItems = Ext.ComponentManager.all.getArray();
287             }
288             // Root is a candidate Array
289             else if (Ext.isArray(root)) {
290                 workingItems = root;
291             }
292
293             // We are going to loop over our operations and take care of them
294             // one by one.
295             for (; i &lt; length; i++) {
296                 operation = operations[i];
297
298                 // The mode operation requires some custom handling.
299                 // All other operations essentially filter down our current
300                 // working items, while mode replaces our current working
301                 // items by getting children from each one of our current
302                 // working items. The type of mode determines the type of
303                 // children we get. (e.g. &gt; only gets direct children)
304                 if (operation.mode === '^') {
305                     workingItems = getAncestors(workingItems || [root]);
306                 }
307                 else if (operation.mode) {
308                     workingItems = getItems(workingItems || [root], operation.mode);
309                 }
310                 else {
311                     workingItems = filterItems(workingItems || getItems([root]), operation);
312                 }
313
314                 // If this is the last operation, it means our current working
315                 // items are the final matched items. Thus return them!
316                 if (i === length -1) {
317                     return workingItems;
318                 }
319             }
320             return [];
321         },
322
323         is: function(component) {
324             var operations = this.operations,
325                 components = Ext.isArray(component) ? component : [component],
326                 originalLength = components.length,
327                 lastOperation = operations[operations.length-1],
328                 ln, i;
329
330             components = filterItems(components, lastOperation);
331             if (components.length === originalLength) {
332                 if (operations.length &gt; 1) {
333                     for (i = 0, ln = components.length; i &lt; ln; i++) {
334                         if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
335                             return false;
336                         }
337                     }
338                 }
339                 return true;
340             }
341             return false;
342         }
343     });
344
345     Ext.apply(this, {
346
347         // private cache of selectors and matching ComponentQuery.Query objects
348         cache: {},
349
350         // private cache of pseudo class filter functions
351         pseudos: {
352             not: function(components, selector){
353                 var CQ = Ext.ComponentQuery,
354                     i = 0,
355                     length = components.length,
356                     results = [],
357                     index = -1,
358                     component;
359                 
360                 for(; i &lt; length; ++i) {
361                     component = components[i];
362                     if (!CQ.is(component, selector)) {
363                         results[++index] = component;
364                     }
365                 }
366                 return results;
367             }
368         },
369
370 <span id='Ext-ComponentQuery-method-query'>        /**
371 </span>         * &lt;p&gt;Returns an array of matched Components from within the passed root object.&lt;/p&gt;
372          * &lt;p&gt;This method filters returned Components in a similar way to how CSS selector based DOM
373          * queries work using a textual selector string.&lt;/p&gt;
374          * &lt;p&gt;See class summary for details.&lt;/p&gt;
375          * @param selector The selector string to filter returned Components
376          * @param root &lt;p&gt;The Container within which to perform the query. If omitted, all Components
377          * within the document are included in the search.&lt;/p&gt;
378          * &lt;p&gt;This parameter may also be an array of Components to filter according to the selector.&lt;/p&gt;
379          * @returns {Array} The matched Components.
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 component The Component to test
419          * @param 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>