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