]>
git.proxmox.com Git - sencha-touch.git/blob - src/src/ComponentQuery.js
2 * @class Ext.ComponentQuery
6 * Provides searching of Components within {@link Ext.ComponentManager} (globally) or a specific
7 * {@link Ext.Container} on the document with a similar syntax to a CSS selector.
9 * Components can be retrieved by using their {@link Ext.Component xtype} with an optional '.' prefix
11 * - `component` or `.component`
12 * - `gridpanel` or `.gridpanel`
14 * An itemId or id must be prefixed with a #
18 * Attributes must be wrapped in brackets
20 * - `component[autoScroll]`
21 * - `panel[title="Test"]`
23 * Attributes can use the '=' or '~=' operators to do the pattern matching.
25 * The <strong>'='</strong> operator will return the results that <strong>exactly</strong> match:
27 * Ext.Component.query('panel[cls=my-cls]')
29 * Will match the following Component:
31 * Ext.create('Ext.Panel', {
35 * The <strong>'~='</strong> operator will return results that <strong>exactly</strong> matches one of the whitespace-separated values:
37 * Ext.Component.query('panel[cls~=my-cls]')
39 * Will match the follow Component:
41 * Ext.create('My.Panel', {
42 * cls : 'foo-cls my-cls bar-cls'
45 * This is because it <strong>exactly</strong> matched the 'my-cls' within the cls config.
47 * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
48 * the candidate Component will be included in the query:
50 * var disabledFields = myFormPanel.query("{isDisabled()}");
52 * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
54 * // Function receives array and returns a filtered array.
55 * Ext.ComponentQuery.pseudos.invalid = function(items) {
56 * var i = 0, l = items.length, c, result = [];
57 * for (; i < l; i++) {
58 * if (!(c = items[i]).isValid()) {
65 * var invalidFields = myFormPanel.query('field:invalid');
66 * if (invalidFields.length) {
67 * invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
68 * for (var i = 0, l = invalidFields.length; i < l; i++) {
69 * invalidFields[i].getEl().frame("red");
73 * Default pseudos include:
77 * Queries return an array of components.
78 * Here are some example queries.
80 * // retrieve all Ext.Panels in the document by xtype
81 * var panelsArray = Ext.ComponentQuery.query('panel');
83 * // retrieve all Ext.Panels within the container with an id myCt
84 * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
86 * // retrieve all direct children which are Ext.Panels within myCt
87 * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
89 * // retrieve all grids and trees
90 * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
92 * For easy access to queries based from a particular Container see the {@link Ext.Container#query},
93 * {@link Ext.Container#down} and {@link Ext.Container#child} methods. Also see
94 * {@link Ext.Component#up}.
96 Ext
.define('Ext.ComponentQuery', {
98 uses
: ['Ext.ComponentManager']
103 // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
104 // as a member on each item in the passed array.
111 'for (; i < l; i++) {',
120 filterItems = function(items
, operation
) {
121 // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
122 // The operation's method loops over each item in the candidate array and
123 // returns an array of items which match its criteria
124 return operation
.method
.apply(this, [ items
].concat(operation
.args
));
127 getItems = function(items
, mode
) {
130 length
= items
.length
,
134 for (; i
< length
; i
++) {
135 candidate
= items
[i
];
136 if (candidate
.getRefItems
) {
137 result
= result
.concat(candidate
.getRefItems(deep
));
143 getAncestors = function(items
) {
146 length
= items
.length
,
148 for (; i
< length
; i
++) {
149 candidate
= items
[i
];
150 while (!!(candidate
= (candidate
.ownerCt
|| candidate
.floatParent
))) {
151 result
.push(candidate
);
157 // Filters the passed candidate array and returns only items which match the passed xtype
158 filterByXType = function(items
, xtype
, shallow
) {
160 return items
.slice();
165 length
= items
.length
,
167 for (; i
< length
; i
++) {
168 candidate
= items
[i
];
169 if (candidate
.isXType(xtype
, shallow
)) {
170 result
.push(candidate
);
177 // Filters the passed candidate array and returns only items which have the passed className
178 filterByClassName = function(items
, className
) {
182 length
= items
.length
,
184 for (; i
< length
; i
++) {
185 candidate
= items
[i
];
186 if (candidate
.el
? candidate
.el
.hasCls(className
) : EA
.contains(candidate
.initCls(), className
)) {
187 result
.push(candidate
);
193 // Filters the passed candidate array and returns only items which have the specified property match
194 filterByAttribute = function(items
, property
, operator
, value
) {
197 length
= items
.length
,
198 candidate
, getter
, getValue
;
199 for (; i
< length
; i
++) {
200 candidate
= items
[i
];
201 getter
= Ext
.Class
.getConfigNameMap(property
).get;
202 if (operator
=== '~=') {
205 if (candidate
[getter
]) {
206 getValue
= candidate
[getter
]();
207 } else if (candidate
.config
&& candidate
.config
[property
]) {
208 getValue
= String(candidate
.config
[property
]);
209 } else if (candidate
[property
]) {
210 getValue
= String(candidate
[property
]);
214 //normalize to an array
215 if (!Ext
.isArray(getValue
)) {
216 getValue
= getValue
.split(' ');
220 vLen
= getValue
.length
,
223 for (; v
< vLen
; v
++) {
225 * getValue[v] could still be whitespaced-separated, this normalizes it. This is an example:
229 * cls : 'overlay-footer-item overlay-footer-imprint'
232 val
= String(getValue
[v
]).split(' ');
234 if (Ext
.Array
.indexOf(val
, value
) !== -1) {
235 result
.push(candidate
);
239 } else if (candidate
[getter
]) {
240 getValue
= candidate
[getter
]();
241 if (!value
? !!getValue
: (String(getValue
) === value
)) {
242 result
.push(candidate
);
245 else if (candidate
.config
&& candidate
.config
[property
]) {
246 if (!value
? !!candidate
.config
[property
] : (String(candidate
.config
[property
]) === value
)) {
247 result
.push(candidate
);
250 else if (!value
? !!candidate
[property
] : (String(candidate
[property
]) === value
)) {
251 result
.push(candidate
);
257 // Filters the passed candidate array and returns only items which have the specified itemId or id
258 filterById = function(items
, id
) {
261 length
= items
.length
,
263 for (; i
< length
; i
++) {
264 candidate
= items
[i
];
265 if (candidate
.getId() === id
|| candidate
.getItemId() === id
) {
266 result
.push(candidate
);
272 // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
273 filterByPseudo = function(items
, name
, value
) {
274 return cq
.pseudos
[name
](items
, value
);
277 // Determines leading mode
278 // > for direct child, and ^ to switch to ownerCt axis
279 modeRe
= /^(\s?([>\^])\s?|\s|$)/,
281 // Matches a token with possibly (true|false) appended for the "shallow" parameter
282 tokenRe
= /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
285 // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
286 re
: /^\.([\w\-]+)(?:\((true|false)\))?/,
287 method
: filterByXType
289 // checks for [attribute=value]
290 re
: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
291 method
: filterByAttribute
293 // checks for #cmpItemId
297 // checks for :<pseudo_class>(<selector>)
298 re
: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
299 method
: filterByPseudo
301 // checks for {<member_expression>}
302 re
: /^(?:\{([^\}]+)\})/,
303 method
: filterFnPattern
306 cq
.Query
= Ext
.extend(Object
, {
307 constructor: function(cfg
) {
309 Ext
.apply(this, cfg
);
314 * Executes this Query upon the selected root.
315 * The root provides the initial source of candidate Component matches which are progressively
316 * filtered by iterating through this Query's operations cache.
317 * If no root is provided, all registered Components are searched via the ComponentManager.
318 * root may be a Container who's descendant Components are filtered
319 * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
320 * docked items within a Panel.
321 * root may be an array of candidate Components to filter using this Query.
323 execute : function(root
) {
324 var operations
= this.operations
,
326 length
= operations
.length
,
330 // no root, use all Components in the document
332 workingItems
= Ext
.ComponentManager
.all
.getArray();
334 // Root is a candidate Array
335 else if (Ext
.isArray(root
)) {
339 // We are going to loop over our operations and take care of them
341 for (; i
< length
; i
++) {
342 operation
= operations
[i
];
344 // The mode operation requires some custom handling.
345 // All other operations essentially filter down our current
346 // working items, while mode replaces our current working
347 // items by getting children from each one of our current
348 // working items. The type of mode determines the type of
349 // children we get. (e.g. > only gets direct children)
350 if (operation
.mode
=== '^') {
351 workingItems
= getAncestors(workingItems
|| [root
]);
353 else if (operation
.mode
) {
354 workingItems
= getItems(workingItems
|| [root
], operation
.mode
);
357 workingItems
= filterItems(workingItems
|| getItems([root
]), operation
);
360 // If this is the last operation, it means our current working
361 // items are the final matched items. Thus return them!
362 if (i
=== length
-1) {
369 is: function(component
) {
370 var operations
= this.operations
,
371 components
= Ext
.isArray(component
) ? component
: [component
],
372 originalLength
= components
.length
,
373 lastOperation
= operations
[operations
.length
-1],
376 components
= filterItems(components
, lastOperation
);
377 if (components
.length
=== originalLength
) {
378 if (operations
.length
> 1) {
379 for (i
= 0, ln
= components
.length
; i
< ln
; i
++) {
380 if (Ext
.Array
.indexOf(this.execute(), components
[i
]) === -1) {
393 // private cache of selectors and matching ComponentQuery.Query objects
396 // private cache of pseudo class filter functions
398 not: function(components
, selector
){
399 var CQ
= Ext
.ComponentQuery
,
401 length
= components
.length
,
406 for(; i
< length
; ++i
) {
407 component
= components
[i
];
408 if (!CQ
.is(component
, selector
)) {
409 results
[++index
] = component
;
417 * Returns an array of matched Components from within the passed root object.
419 * This method filters returned Components in a similar way to how CSS selector based DOM
420 * queries work using a textual selector string.
422 * See class summary for details.
424 * @param {String} selector The selector string to filter returned Components
425 * @param {Ext.Container} root The Container within which to perform the query.
426 * If omitted, all Components within the document are included in the search.
428 * This parameter may also be an array of Components to filter according to the selector.
429 * @return {Ext.Component[]} The matched Components.
431 * @member Ext.ComponentQuery
433 query: function(selector
, root
) {
434 var selectors
= selector
.split(','),
435 length
= selectors
.length
,
440 query
, resultsLn
, cmp
;
442 for (; i
< length
; i
++) {
443 selector
= Ext
.String
.trim(selectors
[i
]);
444 query
= this.parse(selector
);
445 // query = this.cache[selector];
447 // this.cache[selector] = query = this.parse(selector);
449 results
= results
.concat(query
.execute(root
));
452 // multiple selectors, potential to find duplicates
453 // lets filter them out.
455 resultsLn
= results
.length
;
456 for (i
= 0; i
< resultsLn
; i
++) {
458 if (!dupMatcher
[cmp
.id
]) {
459 noDupResults
.push(cmp
);
460 dupMatcher
[cmp
.id
] = true;
463 results
= noDupResults
;
469 * Tests whether the passed Component matches the selector string.
470 * @param {Ext.Component} component The Component to test.
471 * @param {String} selector The selector string to test against.
472 * @return {Boolean} `true` if the Component matches the selector.
473 * @member Ext.ComponentQuery
475 is: function(component
, selector
) {
479 var query
= this.cache
[selector
];
481 this.cache
[selector
] = query
= this.parse(selector
);
483 return query
.is(component
);
486 parse: function(selector
) {
488 length
= matchers
.length
,
496 // We are going to parse the beginning of the selector over and
497 // over again, slicing off the selector any portions we converted into an
498 // operation, until it is an empty string.
499 while (selector
&& lastSelector
!== selector
) {
500 lastSelector
= selector
;
502 // First we check if we are dealing with a token like #, * or an xtype
503 tokenMatch
= selector
.match(tokenRe
);
506 matchedChar
= tokenMatch
[1];
508 // If the token is prefixed with a # we push a filterById operation to our stack
509 if (matchedChar
=== '#') {
512 args
: [Ext
.String
.trim(tokenMatch
[2])]
515 // If the token is prefixed with a . we push a filterByClassName operation to our stack
516 // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
517 else if (matchedChar
=== '.') {
519 method
: filterByClassName
,
520 args
: [Ext
.String
.trim(tokenMatch
[2])]
523 // If the token is a * or an xtype string, we push a filterByXType
524 // operation to the stack.
527 method
: filterByXType
,
528 args
: [Ext
.String
.trim(tokenMatch
[2]), Boolean(tokenMatch
[3])]
532 // Now we slice of the part we just converted into an operation
533 selector
= selector
.replace(tokenMatch
[0], '');
536 // If the next part of the query is not a space or > or ^, it means we
537 // are going to check for more things that our current selection
539 while (!(modeMatch
= selector
.match(modeRe
))) {
540 // Lets loop over each type of matcher and execute it
541 // on our current selector.
542 for (i
= 0; selector
&& i
< length
; i
++) {
543 matcher
= matchers
[i
];
544 selectorMatch
= selector
.match(matcher
.re
);
545 method
= matcher
.method
;
547 // If we have a match, add an operation with the method
548 // associated with this matcher, and pass the regular
549 // expression matches are arguments to the operation.
552 method
: Ext
.isString(matcher
.method
)
553 // Turn a string method into a function by formatting the string with our selector matche expression
554 // A new method is created for different match expressions, eg {id=='textfield-1024'}
555 // Every expression may be different in different selectors.
556 ? Ext
.functionFactory('items', Ext
.String
.format
.apply(Ext
.String
, [method
].concat(selectorMatch
.slice(1))))
558 args
: selectorMatch
.slice(1)
560 selector
= selector
.replace(selectorMatch
[0], '');
561 break; // Break on match
564 // Exhausted all matches: It's an error
565 if (i
=== (length
- 1)) {
566 Ext
.Error
.raise('Invalid ComponentQuery selector: "' + arguments
[0] + '"');
572 // Now we are going to check for a mode change. This means a space
573 // or a > to determine if we are going to select all the children
574 // of the currently matched items, or a ^ if we are going to use the
575 // ownerCt axis as the candidate source.
576 if (modeMatch
[1]) { // Assignment, and test for truthiness!
578 mode
: modeMatch
[2]||modeMatch
[1]
580 selector
= selector
.replace(modeMatch
[0], '');
584 // Now that we have all our operations in an array, we are going
585 // to create a new Query using these operations.
586 return new cq
.Query({
587 operations
: operations