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; }
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> * @class Ext.ComponentQuery
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.
26 * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
28 * - `component` or `.component`
29 * - `gridpanel` or `.gridpanel`
31 * An itemId or id must be prefixed with a #
35 * Attributes must be wrapped in brackets
37 * - `component[autoScroll]`
38 * - `panel[title="Test"]`
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:
43 * var disabledFields = myFormPanel.query("{isDisabled()}");
45 * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
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 < l; i++) {
51 * if (!(c = items[i]).isValid()) {
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 < l; i++) {
62 * invalidFields[i].getEl().frame("red");
66 * Default pseudos include:
70 * Queries return an array of components.
71 * Here are some example queries.
73 * // retrieve all Ext.Panels in the document by xtype
74 * var panelsArray = Ext.ComponentQuery.query('panel');
76 * // retrieve all Ext.Panels within the container with an id myCt
77 * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
79 * // retrieve all direct children which are Ext.Panels within myCt
80 * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
82 * // retrieve all grids and trees
83 * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
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}.
89 Ext.define('Ext.ComponentQuery', {
91 uses: ['Ext.ComponentManager']
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.
104 'for (; i < l; i++) {',
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));
120 getItems = function(items, mode) {
123 length = items.length,
125 deep = mode !== '>';
127 for (; i < length; i++) {
128 candidate = items[i];
129 if (candidate.getRefItems) {
130 result = result.concat(candidate.getRefItems(deep));
136 getAncestors = function(items) {
139 length = items.length,
141 for (; i < length; i++) {
142 candidate = items[i];
143 while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
144 result.push(candidate);
150 // Filters the passed candidate array and returns only items which match the passed xtype
151 filterByXType = function(items, xtype, shallow) {
153 return items.slice();
158 length = items.length,
160 for (; i < length; i++) {
161 candidate = items[i];
162 if (candidate.isXType(xtype, shallow)) {
163 result.push(candidate);
170 // Filters the passed candidate array and returns only items which have the passed className
171 filterByClassName = function(items, className) {
175 length = items.length,
177 for (; i < length; i++) {
178 candidate = items[i];
179 if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
180 result.push(candidate);
186 // Filters the passed candidate array and returns only items which have the specified property match
187 filterByAttribute = function(items, property, operator, value) {
190 length = items.length,
192 for (; i < length; i++) {
193 candidate = items[i];
194 if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
195 result.push(candidate);
201 // Filters the passed candidate array and returns only items which have the specified itemId or id
202 filterById = function(items, id) {
205 length = items.length,
207 for (; i < length; i++) {
208 candidate = items[i];
209 if (candidate.getItemId() === id) {
210 result.push(candidate);
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);
221 // Determines leading mode
222 // > for direct child, and ^ to switch to ownerCt axis
223 modeRe = /^(\s?([>\^])\s?|\s|$)/,
225 // Matches a token with possibly (true|false) appended for the "shallow" parameter
226 tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
229 // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
230 re: /^\.([\w\-]+)(?:\((true|false)\))?/,
231 method: filterByXType
233 // checks for [attribute=value]
234 re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
235 method: filterByAttribute
237 // checks for #cmpItemId
241 // checks for :<pseudo_class>(<selector>)
242 re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
243 method: filterByPseudo
245 // checks for {<member_expression>}
246 re: /^(?:\{([^\}]+)\})/,
247 method: filterFnPattern
250 <span id='Ext-ComponentQuery-Query'> /**
251 </span> * @class Ext.ComponentQuery.Query
255 cq.Query = Ext.extend(Object, {
256 constructor: function(cfg) {
258 Ext.apply(this, cfg);
261 <span id='Ext-ComponentQuery-Query-method-execute'> /**
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.
272 execute : function(root) {
273 var operations = this.operations,
275 length = operations.length,
279 // no root, use all Components in the document
281 workingItems = Ext.ComponentManager.all.getArray();
283 // Root is a candidate Array
284 else if (Ext.isArray(root)) {
288 // We are going to loop over our operations and take care of them
290 for (; i < length; i++) {
291 operation = operations[i];
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. > only gets direct children)
299 if (operation.mode === '^') {
300 workingItems = getAncestors(workingItems || [root]);
302 else if (operation.mode) {
303 workingItems = getItems(workingItems || [root], operation.mode);
306 workingItems = filterItems(workingItems || getItems([root]), operation);
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) {
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],
325 components = filterItems(components, lastOperation);
326 if (components.length === originalLength) {
327 if (operations.length > 1) {
328 for (i = 0, ln = components.length; i < ln; i++) {
329 if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
342 // private cache of selectors and matching ComponentQuery.Query objects
345 // private cache of pseudo class filter functions
347 not: function(components, selector){
348 var CQ = Ext.ComponentQuery,
350 length = components.length,
355 for(; i < length; ++i) {
356 component = components[i];
357 if (!CQ.is(component, selector)) {
358 results[++index] = component;
365 <span id='Ext-ComponentQuery-method-query'> /**
366 </span> * Returns an array of matched Components from within the passed root object.
368 * This method filters returned Components in a similar way to how CSS selector based DOM
369 * queries work using a textual selector string.
371 * See class summary for details.
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.
377 * This parameter may also be an array of Components to filter according to the selector.</p>
378 * @returns {[Ext.Component]} The matched Components.
380 * @member Ext.ComponentQuery
382 query: function(selector, root) {
383 var selectors = selector.split(','),
384 length = selectors.length,
389 query, resultsLn, cmp;
391 for (; i < length; i++) {
392 selector = Ext.String.trim(selectors[i]);
393 query = this.cache[selector];
395 this.cache[selector] = query = this.parse(selector);
397 results = results.concat(query.execute(root));
400 // multiple selectors, potential to find duplicates
401 // lets filter them out.
403 resultsLn = results.length;
404 for (i = 0; i < resultsLn; i++) {
406 if (!dupMatcher[cmp.id]) {
407 noDupResults.push(cmp);
408 dupMatcher[cmp.id] = true;
411 results = noDupResults;
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
423 is: function(component, selector) {
427 var query = this.cache[selector];
429 this.cache[selector] = query = this.parse(selector);
431 return query.is(component);
434 parse: function(selector) {
436 length = matchers.length,
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 && lastSelector !== selector) {
448 lastSelector = selector;
450 // First we check if we are dealing with a token like #, * or an xtype
451 tokenMatch = selector.match(tokenRe);
454 matchedChar = tokenMatch[1];
456 // If the token is prefixed with a # we push a filterById operation to our stack
457 if (matchedChar === '#') {
460 args: [Ext.String.trim(tokenMatch[2])]
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 === '.') {
467 method: filterByClassName,
468 args: [Ext.String.trim(tokenMatch[2])]
471 // If the token is a * or an xtype string, we push a filterByXType
472 // operation to the stack.
475 method: filterByXType,
476 args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
480 // Now we slice of the part we just converted into an operation
481 selector = selector.replace(tokenMatch[0], '');
484 // If the next part of the query is not a space or > or ^, it means we
485 // are going to check for more things that our current selection
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 && i < length; i++) {
491 matcher = matchers[i];
492 selectorMatch = selector.match(matcher.re);
493 method = matcher.method;
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.
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))))
506 args: selectorMatch.slice(1)
508 selector = selector.replace(selectorMatch[0], '');
509 break; // Break on match
512 // Exhausted all matches: It's an error
513 if (i === (length - 1)) {
514 Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
520 // Now we are going to check for a mode change. This means a space
521 // or a > 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!
526 mode: modeMatch[2]||modeMatch[1]
528 selector = selector.replace(modeMatch[0], '');
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