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; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
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.
22 * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
24 * - `component` or `.component`
25 * - `gridpanel` or `.gridpanel`
27 * An itemId or id must be prefixed with a #
31 * Attributes must be wrapped in brackets
33 * - `component[autoScroll]`
34 * - `panel[title="Test"]`
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:
39 * var disabledFields = myFormPanel.query("{isDisabled()}");
41 * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
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 < l; i++) {
47 * if (!(c = items[i]).isValid()) {
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 < l; i++) {
58 * invalidFields[i].getEl().frame("red");
62 * Default pseudos include:
67 * Queries return an array of components.
68 * Here are some example queries.
70 * // retrieve all Ext.Panels in the document by xtype
71 * var panelsArray = Ext.ComponentQuery.query('panel');
73 * // retrieve all Ext.Panels within the container with an id myCt
74 * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
76 * // retrieve all direct children which are Ext.Panels within myCt
77 * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
79 * // retrieve all grids and trees
80 * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
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}.
86 Ext.define('Ext.ComponentQuery', {
88 uses: ['Ext.ComponentManager']
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.
101 'for (; i < l; i++) {',
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));
117 getItems = function(items, mode) {
120 length = items.length,
122 deep = mode !== '>';
124 for (; i < length; i++) {
125 candidate = items[i];
126 if (candidate.getRefItems) {
127 result = result.concat(candidate.getRefItems(deep));
133 getAncestors = function(items) {
136 length = items.length,
138 for (; i < length; i++) {
139 candidate = items[i];
140 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
141 result.push(candidate);
147 // Filters the passed candidate array and returns only items which match the passed xtype
148 filterByXType = function(items, xtype, shallow) {
150 return items.slice();
155 length = items.length,
157 for (; i < length; i++) {
158 candidate = items[i];
159 if (candidate.isXType(xtype, shallow)) {
160 result.push(candidate);
167 // Filters the passed candidate array and returns only items which have the passed className
168 filterByClassName = function(items, className) {
172 length = items.length,
174 for (; i < length; i++) {
175 candidate = items[i];
176 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
177 result.push(candidate);
183 // Filters the passed candidate array and returns only items which have the specified property match
184 filterByAttribute = function(items, property, operator, value) {
187 length = items.length,
189 for (; i < length; i++) {
190 candidate = items[i];
191 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
192 result.push(candidate);
198 // Filters the passed candidate array and returns only items which have the specified itemId or id
199 filterById = function(items, id) {
202 length = items.length,
204 for (; i < length; i++) {
205 candidate = items[i];
206 if (candidate.getItemId() === id) {
207 result.push(candidate);
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);
218 // Determines leading mode
219 // > for direct child, and ^ to switch to ownerCt axis
220 modeRe = /^(\s?([>\^])\s?|\s|$)/,
222 // Matches a token with possibly (true|false) appended for the "shallow" parameter
223 tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
226 // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
227 re: /^\.([\w\-]+)(?:\((true|false)\))?/,
228 method: filterByXType
230 // checks for [attribute=value]
231 re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
232 method: filterByAttribute
234 // checks for #cmpItemId
238 // checks for :<pseudo_class>(<selector>)
239 re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
240 method: filterByPseudo
242 // checks for {<member_expression>}
243 re: /^(?:\{([^\}]+)\})/,
244 method: filterFnPattern
247 // @class Ext.ComponentQuery.Query
248 // This internal class is completely hidden in documentation.
249 cq.Query = Ext.extend(Object, {
250 constructor: function(cfg) {
252 Ext.apply(this, cfg);
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,
266 length = operations.length,
270 // no root, use all Components in the document
272 workingItems = Ext.ComponentManager.all.getArray();
274 // Root is a candidate Array
275 else if (Ext.isArray(root)) {
279 // We are going to loop over our operations and take care of them
281 for (; i < length; i++) {
282 operation = operations[i];
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. > only gets direct children)
290 if (operation.mode === '^') {
291 workingItems = getAncestors(workingItems || [root]);
293 else if (operation.mode) {
294 workingItems = getItems(workingItems || [root], operation.mode);
297 workingItems = filterItems(workingItems || getItems([root]), operation);
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) {
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],
316 components = filterItems(components, lastOperation);
317 if (components.length === originalLength) {
318 if (operations.length > 1) {
319 for (i = 0, ln = components.length; i < ln; i++) {
320 if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
333 // private cache of selectors and matching ComponentQuery.Query objects
336 // private cache of pseudo class filter functions
338 not: function(components, selector){
339 var CQ = Ext.ComponentQuery,
341 length = components.length,
346 for(; i < length; ++i) {
347 component = components[i];
348 if (!CQ.is(component, selector)) {
349 results[++index] = component;
354 last: function(components) {
355 return components[components.length - 1];
359 <span id='Ext-ComponentQuery-method-query'> /**
360 </span> * Returns an array of matched Components from within the passed root object.
362 * This method filters returned Components in a similar way to how CSS selector based DOM
363 * queries work using a textual selector string.
365 * See class summary for details.
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.
371 * This parameter may also be an array of Components to filter according to the selector.</p>
372 * @returns {Ext.Component[]} The matched Components.
374 * @member Ext.ComponentQuery
376 query: function(selector, root) {
377 var selectors = selector.split(','),
378 length = selectors.length,
383 query, resultsLn, cmp;
385 for (; i < length; i++) {
386 selector = Ext.String.trim(selectors[i]);
387 query = this.cache[selector];
389 this.cache[selector] = query = this.parse(selector);
391 results = results.concat(query.execute(root));
394 // multiple selectors, potential to find duplicates
395 // lets filter them out.
397 resultsLn = results.length;
398 for (i = 0; i < resultsLn; i++) {
400 if (!dupMatcher[cmp.id]) {
401 noDupResults.push(cmp);
402 dupMatcher[cmp.id] = true;
405 results = noDupResults;
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
417 is: function(component, selector) {
421 var query = this.cache[selector];
423 this.cache[selector] = query = this.parse(selector);
425 return query.is(component);
428 parse: function(selector) {
430 length = matchers.length,
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 && lastSelector !== selector) {
442 lastSelector = selector;
444 // First we check if we are dealing with a token like #, * or an xtype
445 tokenMatch = selector.match(tokenRe);
448 matchedChar = tokenMatch[1];
450 // If the token is prefixed with a # we push a filterById operation to our stack
451 if (matchedChar === '#') {
454 args: [Ext.String.trim(tokenMatch[2])]
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 === '.') {
461 method: filterByClassName,
462 args: [Ext.String.trim(tokenMatch[2])]
465 // If the token is a * or an xtype string, we push a filterByXType
466 // operation to the stack.
469 method: filterByXType,
470 args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
474 // Now we slice of the part we just converted into an operation
475 selector = selector.replace(tokenMatch[0], '');
478 // If the next part of the query is not a space or > or ^, it means we
479 // are going to check for more things that our current selection
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 && i < length; i++) {
485 matcher = matchers[i];
486 selectorMatch = selector.match(matcher.re);
487 method = matcher.method;
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.
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))))
500 args: selectorMatch.slice(1)
502 selector = selector.replace(selectorMatch[0], '');
503 break; // Break on match
506 // Exhausted all matches: It's an error
507 if (i === (length - 1)) {
508 Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
514 // Now we are going to check for a mode change. This means a space
515 // or a > 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!
520 mode: modeMatch[2]||modeMatch[1]
522 selector = selector.replace(modeMatch[0], '');
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