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