]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * Provides searching of Components within Ext.ComponentManager (globally) or a specific\r | |
3 | * Ext.container.Container on the document with a similar syntax to a CSS selector.\r | |
4 | * Returns Array of matching Components, or empty Array.\r | |
5 | *\r | |
6 | * ## Basic Component lookup\r | |
7 | *\r | |
8 | * Components can be retrieved by using their {@link Ext.Component xtype}:\r | |
9 | *\r | |
10 | * - `component`\r | |
11 | * - `gridpanel`\r | |
12 | *\r | |
13 | * Matching by `xtype` matches inherited types, so in the following code, the previous field\r | |
14 | * *of any type which inherits from `TextField`* will be found:\r | |
15 | *\r | |
16 | * prevField = myField.previousNode('textfield');\r | |
17 | *\r | |
18 | * To match only the exact type, pass the "shallow" flag by adding `(true)` to xtype\r | |
19 | * (See Component's {@link Ext.Component#isXType isXType} method):\r | |
20 | *\r | |
21 | * prevTextField = myField.previousNode('textfield(true)');\r | |
22 | *\r | |
23 | * You can search Components by their `id` or `itemId` property, prefixed with a #:\r | |
24 | *\r | |
25 | * #myContainer\r | |
26 | *\r | |
27 | * Component `xtype` and `id` or `itemId` can be used together to avoid possible\r | |
28 | * id collisions between Components of different types:\r | |
29 | *\r | |
30 | * panel#myPanel\r | |
31 | *\r | |
32 | * When Component's `id` or `xtype` contains dots, you can escape them in your selector:\r | |
33 | *\r | |
34 | * my\.panel#myPanel\r | |
35 | *\r | |
36 | * Keep in mind that JavaScript treats the backslash character in a special way, so you\r | |
37 | * need to escape it, too, in the actual code:\r | |
38 | *\r | |
39 | * var myPanel = Ext.ComponentQuery.query('my\\.panel#myPanel');\r | |
40 | *\r | |
41 | * ## Traversing Component tree\r | |
42 | *\r | |
43 | * Components can be found by their relation to other Components. There are several\r | |
44 | * relationship operators, mostly taken from CSS selectors:\r | |
45 | *\r | |
46 | * - **`E F`** All descendant Components of E that match F\r | |
47 | * - **`E > F`** All direct children Components of E that match F\r | |
48 | * - **`E ^ F`** All parent Components of E that match F\r | |
49 | *\r | |
50 | * Expressions between relationship operators are matched left to right, i.e. leftmost\r | |
51 | * selector is applied first, then if one or more matches are found, relationship operator\r | |
52 | * itself is applied, then next selector expression, etc. It is possible to combine\r | |
53 | * relationship operators in complex selectors:\r | |
54 | *\r | |
55 | * window[title="Input form"] textfield[name=login] ^ form > button[action=submit]\r | |
56 | *\r | |
57 | * That selector can be read this way: Find a window with title "Input form", in that\r | |
58 | * window find a TextField with name "login" at any depth (including subpanels and/or\r | |
59 | * FieldSets), then find an `Ext.form.Panel` that is a parent of the TextField, and in\r | |
60 | * that form find a direct child that is a button with custom property `action` set to\r | |
61 | * value "submit".\r | |
62 | *\r | |
63 | * Whitespace on both sides of `^` and `>` operators is non-significant, i.e. can be\r | |
64 | * omitted, but usually is used for clarity.\r | |
65 | *\r | |
66 | * ## Searching by Component attributes\r | |
67 | *\r | |
68 | * Components can be searched by their object property values (attributes). To do that,\r | |
69 | * use attribute matching expression in square brackets:\r | |
70 | *\r | |
71 | * - `component[disabled]` - matches any Component that has `disabled` property with\r | |
72 | * any truthy (non-empty, not `false`) value.\r | |
73 | * - `panel[title="Test"]` - matches any Component that has `title` property set to\r | |
74 | * "Test". Note that if the value does not contain spaces, the quotes are optional.\r | |
75 | *\r | |
76 | * Attributes can use any of the following operators to compare values:\r | |
77 | * `=`, `!=`, `^=`, `$=`, `*=`, `%=`, `|=` and `~=`.\r | |
78 | *\r | |
79 | * Prefixing the attribute name with an at sign `@` means that the property must be\r | |
80 | * the object's `ownProperty`, not a property from the prototype chain.\r | |
81 | *\r | |
82 | * Specifications like `[propName]` check that the property is a truthy value. To check\r | |
83 | * that the object has an `ownProperty` of a certain name, regardless of the value use\r | |
84 | * the form `[?propName]`.\r | |
85 | *\r | |
86 | * The specified value is coerced to match the type of the property found in the\r | |
87 | * candidate Component using {@link Ext#coerce}.\r | |
88 | *\r | |
89 | * If you need to find Components by their `itemId` property, use the `#id` form; it will\r | |
90 | * do the same as `[itemId=id]` but is easier to read.\r | |
91 | *\r | |
92 | * If you need to include a metacharacter like (, ), [, ], etc., in the query, escape it\r | |
93 | * by prefixing it with a backslash:\r | |
94 | *\r | |
95 | * var component = Ext.ComponentQuery.query('[myProperty=\\[foo\\]]');\r | |
96 | *\r | |
97 | * ## Attribute matching operators\r | |
98 | *\r | |
99 | * The '=' operator will return the results that **exactly** match the\r | |
100 | * specified object property (attribute):\r | |
101 | *\r | |
102 | * Ext.ComponentQuery.query('panel[cls=my-cls]');\r | |
103 | *\r | |
104 | * Will match the following Component:\r | |
105 | *\r | |
106 | * Ext.create('Ext.window.Window', {\r | |
107 | * cls: 'my-cls'\r | |
108 | * });\r | |
109 | *\r | |
110 | * But will not match the following Component, because 'my-cls' is one value\r | |
111 | * among others:\r | |
112 | *\r | |
113 | * Ext.create('Ext.panel.Panel', {\r | |
114 | * cls: 'foo-cls my-cls bar-cls'\r | |
115 | * });\r | |
116 | *\r | |
117 | * You can use the '~=' operator instead, it will return Components with\r | |
118 | * the property that **exactly** matches one of the whitespace-separated\r | |
119 | * values. This is also true for properties that only have *one* value:\r | |
120 | *\r | |
121 | * Ext.ComponentQuery.query('panel[cls~=my-cls]');\r | |
122 | *\r | |
123 | * Will match both Components:\r | |
124 | *\r | |
125 | * Ext.create('Ext.panel.Panel', {\r | |
126 | * cls: 'foo-cls my-cls bar-cls'\r | |
127 | * });\r | |
128 | * \r | |
129 | * Ext.create('Ext.window.Window', {\r | |
130 | * cls: 'my-cls'\r | |
131 | * });\r | |
132 | *\r | |
133 | * Generally, '=' operator is more suited for object properties other than\r | |
134 | * CSS classes, while '~=' operator will work best with properties that\r | |
135 | * hold lists of whitespace-separated CSS classes.\r | |
136 | *\r | |
137 | * The '^=' operator will return Components with specified attribute that\r | |
138 | * start with the passed value:\r | |
139 | *\r | |
140 | * Ext.ComponentQuery.query('panel[title^=Sales]');\r | |
141 | *\r | |
142 | * Will match the following Component:\r | |
143 | *\r | |
144 | * Ext.create('Ext.panel.Panel', {\r | |
145 | * title: 'Sales estimate for Q4'\r | |
146 | * });\r | |
147 | *\r | |
148 | * The '$=' operator will return Components with specified properties that\r | |
149 | * end with the passed value:\r | |
150 | *\r | |
151 | * Ext.ComponentQuery.query('field[fieldLabel$=name]');\r | |
152 | *\r | |
153 | * Will match the following Component:\r | |
154 | *\r | |
155 | * Ext.create('Ext.form.field.Text', {\r | |
156 | * fieldLabel: 'Enter your name'\r | |
157 | * });\r | |
158 | *\r | |
159 | * The '/=' operator will return Components with specified properties that\r | |
160 | * match the passed regular expression:\r | |
161 | *\r | |
162 | * Ext.ComponentQuery.query('button[action/="edit|save"]');\r | |
163 | *\r | |
164 | * Will match the following Components with a custom `action` property:\r | |
165 | *\r | |
166 | * Ext.create('Ext.button.Button', {\r | |
167 | * action: 'edit'\r | |
168 | * });\r | |
169 | *\r | |
170 | * Ext.create('Ext.button.Button', {\r | |
171 | * action: 'save'\r | |
172 | * });\r | |
173 | *\r | |
174 | * When you need to use meta characters like [], (), etc. in your query, make sure\r | |
175 | * to escape them with back slashes:\r | |
176 | *\r | |
177 | * Ext.ComponentQuery.query('panel[title="^Sales for Q\\[1-4\\]"]');\r | |
178 | *\r | |
179 | * The following test will find panels with their `ownProperty` collapsed being equal to\r | |
180 | * `false`. It will **not** match a collapsed property from the prototype chain.\r | |
181 | *\r | |
182 | * Ext.ComponentQuery.query('panel[@collapsed=false]');\r | |
183 | *\r | |
184 | * Member expressions from candidate Components may be tested. If the expression returns\r | |
185 | * a *truthy* value, the candidate Component will be included in the query:\r | |
186 | *\r | |
187 | * var disabledFields = myFormPanel.query("{isDisabled()}");\r | |
188 | *\r | |
189 | * Such expressions are executed in Component's context, and the above expression is\r | |
190 | * similar to running this snippet for every Component in your application:\r | |
191 | *\r | |
192 | * if (component.isDisabled()) {\r | |
193 | * matches.push(component);\r | |
194 | * }\r | |
195 | *\r | |
196 | * It is important to use only methods that are available in **every** Component instance\r | |
197 | * to avoid run time exceptions. If you need to match your Components with a custom\r | |
198 | * condition formula, you can augment `Ext.Component` to provide custom matcher that\r | |
199 | * will return `false` by default, and override it in your custom classes:\r | |
200 | * \r | |
201 | * Ext.define('My.Component', {\r | |
202 | * override: 'Ext.Component',\r | |
203 | * myMatcher: function() { return false; }\r | |
204 | * });\r | |
205 | *\r | |
206 | * Ext.define('My.Panel', {\r | |
207 | * extend: 'Ext.panel.Panel',\r | |
208 | * requires: ['My.Component'], // Ensure that Component override is applied\r | |
209 | * myMatcher: function(selector) {\r | |
210 | * return selector === 'myPanel';\r | |
211 | * }\r | |
212 | * });\r | |
213 | *\r | |
214 | * After that you can use a selector with your custom matcher to find all instances\r | |
215 | * of `My.Panel`:\r | |
216 | *\r | |
217 | * Ext.ComponentQuery.query("{myMatcher('myPanel')}");\r | |
218 | *\r | |
219 | * However if you really need to use a custom matcher, you may find it easier to implement\r | |
220 | * a custom Pseudo class instead (see below).\r | |
221 | *\r | |
222 | * ## Conditional matching\r | |
223 | *\r | |
224 | * Attribute matchers can be combined to select only Components that match **all**\r | |
225 | * conditions (logical AND operator):\r | |
226 | *\r | |
227 | * Ext.ComponentQuery.query('panel[cls~=my-cls][floating=true][title$="sales data"]');\r | |
228 | *\r | |
229 | * E.g., the query above will match only a Panel-descended Component that has 'my-cls'\r | |
230 | * CSS class *and* is floating *and* with a title that ends with "sales data".\r | |
231 | *\r | |
232 | * Expressions separated with commas will match any Component that satisfies\r | |
233 | * *either* expression (logical OR operator):\r | |
234 | *\r | |
235 | * Ext.ComponentQuery.query('field[fieldLabel^=User], field[fieldLabel*=password]');\r | |
236 | *\r | |
237 | * E.g., the query above will match any field with field label starting with "User",\r | |
238 | * *or* any field that has "password" in its label.\r | |
239 | *\r | |
240 | * If you need to include a comma in an attribute matching expression, escape it with a\r | |
241 | * backslash:\r | |
242 | *\r | |
243 | * Ext.ComponentQuery.query('field[fieldLabel^="User\\, foo"], field[fieldLabel*=password]');\r | |
244 | *\r | |
245 | * ## Pseudo classes\r | |
246 | *\r | |
247 | * Pseudo classes may be used to filter results in the same way as in\r | |
248 | * {@link Ext.dom.Query}. There are five default pseudo classes:\r | |
249 | *\r | |
250 | * * `not` Negates a selector.\r | |
251 | * * `first` Filters out all except the first matching item for a selector.\r | |
252 | * * `last` Filters out all except the last matching item for a selector.\r | |
253 | * * `focusable` Filters out all except Components which are currently able to recieve\r | |
254 | * focus.\r | |
255 | * * `nth-child` Filters Components by ordinal position in the selection.\r | |
256 | * * `scrollable` Filters out all except Components which are scrollable.\r | |
257 | *\r | |
258 | * These pseudo classes can be used with other matchers or without them:\r | |
259 | *\r | |
260 | * // Select first direct child button in any panel\r | |
261 | * Ext.ComponentQuery.query('panel > button:first');\r | |
262 | *\r | |
263 | * // Select last field in Profile form\r | |
264 | * Ext.ComponentQuery.query('form[title=Profile] field:last');\r | |
265 | *\r | |
266 | * // Find first focusable Component in a panel and focus it\r | |
267 | * panel.down(':focusable').focus();\r | |
268 | *\r | |
269 | * // Select any field that is not hidden in a form\r | |
270 | * form.query('field:not(hiddenfield)');\r | |
271 | *\r | |
272 | * // Find last scrollable Component and reset its scroll positions.\r | |
273 | * tabpanel.down(':scrollable[hideMode=display]:last').getScrollable().scrollTo(0, 0);\r | |
274 | *\r | |
275 | * Pseudo class `nth-child` can be used to find any child Component by its\r | |
276 | * position relative to its siblings. This class' handler takes one argument\r | |
277 | * that specifies the selection formula as `Xn` or `Xn+Y`:\r | |
278 | *\r | |
279 | * // Find every odd field in a form\r | |
280 | * form.query('field:nth-child(2n+1)'); // or use shortcut: :nth-child(odd)\r | |
281 | *\r | |
282 | * // Find every even field in a form\r | |
283 | * form.query('field:nth-child(2n)'); // or use shortcut: :nth-child(even)\r | |
284 | *\r | |
285 | * // Find every 3rd field in a form\r | |
286 | * form.query('field:nth-child(3n)');\r | |
287 | *\r | |
288 | * Pseudo classes can be combined to further filter the results, e.g., in the\r | |
289 | * form example above we can modify the query to exclude hidden fields:\r | |
290 | *\r | |
291 | * // Find every 3rd non-hidden field in a form\r | |
292 | * form.query('field:not(hiddenfield):nth-child(3n)');\r | |
293 | *\r | |
294 | * Note that when combining pseudo classes, whitespace is significant, i.e.\r | |
295 | * there should be no spaces between pseudo classes. This is a common mistake;\r | |
296 | * if you accidentally type a space between `field` and `:not`, the query\r | |
297 | * will not return any result because it will mean "find *field's children\r | |
298 | * Components* that are not hidden fields...".\r | |
299 | *\r | |
300 | * ## Custom pseudo classes\r | |
301 | *\r | |
302 | * It is possible to define your own custom pseudo classes. In fact, a\r | |
303 | * pseudo class is just a property in `Ext.ComponentQuery.pseudos` object\r | |
304 | * that defines pseudo class name (property name) and pseudo class handler\r | |
305 | * (property value):\r | |
306 | *\r | |
307 | * // Function receives array and returns a filtered array.\r | |
308 | * Ext.ComponentQuery.pseudos.invalid = function(items) {\r | |
309 | * var i = 0, l = items.length, c, result = [];\r | |
310 | * for (; i < l; i++) {\r | |
311 | * if (!(c = items[i]).isValid()) {\r | |
312 | * result.push(c);\r | |
313 | * }\r | |
314 | * }\r | |
315 | * return result;\r | |
316 | * };\r | |
317 | * \r | |
318 | * var invalidFields = myFormPanel.query('field:invalid');\r | |
319 | * if (invalidFields.length) {\r | |
320 | * invalidFields[0].getEl().scrollIntoView(myFormPanel.body);\r | |
321 | * for (var i = 0, l = invalidFields.length; i < l; i++) {\r | |
322 | * invalidFields[i].getEl().frame("red");\r | |
323 | * }\r | |
324 | * }\r | |
325 | *\r | |
326 | * Pseudo class handlers can be even more flexible, with a selector\r | |
327 | * argument used to define the logic:\r | |
328 | *\r | |
329 | * // Handler receives array of itmes and selector in parentheses\r | |
330 | * Ext.ComponentQuery.pseudos.titleRegex = function(components, selector) {\r | |
331 | * var i = 0, l = components.length, c, result = [], regex = new RegExp(selector);\r | |
332 | * for (; i < l; i++) {\r | |
333 | * c = components[i];\r | |
334 | * if (c.title && regex.test(c.title)) {\r | |
335 | * result.push(c);\r | |
336 | * }\r | |
337 | * }\r | |
338 | * return result;\r | |
339 | * }\r | |
340 | *\r | |
341 | * var salesTabs = tabPanel.query('panel:titleRegex("sales\\s+for\\s+201[123]")');\r | |
342 | *\r | |
343 | * Be careful when using custom pseudo classes with MVC Controllers: when\r | |
344 | * you use a pseudo class in Controller's `control` or `listen` component\r | |
345 | * selectors, the pseudo class' handler function will be called very often\r | |
346 | * and may slow down your application significantly. A good rule of thumb\r | |
347 | * is to always specify Component xtype with the pseudo class so that the\r | |
348 | * handlers are only called on Components that you need, and try to make\r | |
349 | * the condition checks as cheap in terms of execution time as possible.\r | |
350 | * Note how in the example above, handler function checks that Component\r | |
351 | * *has* a title first, before running regex test on it.\r | |
352 | *\r | |
353 | * ## Query examples\r | |
354 | *\r | |
355 | * Queries return an array of Components. Here are some example queries:\r | |
356 | *\r | |
357 | * // retrieve all Ext.Panels in the document by xtype\r | |
358 | * var panelsArray = Ext.ComponentQuery.query('panel');\r | |
359 | *\r | |
360 | * // retrieve all Ext.Panels within the container with an id myCt\r | |
361 | * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');\r | |
362 | *\r | |
363 | * // retrieve all direct children which are Ext.Panels within myCt\r | |
364 | * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');\r | |
365 | *\r | |
366 | * // retrieve all grids or trees\r | |
367 | * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');\r | |
368 | *\r | |
369 | * // Focus first Component\r | |
370 | * myFormPanel.child(':focusable').focus();\r | |
371 | *\r | |
372 | * // Retrieve every odd text field in a form\r | |
373 | * myFormPanel.query('textfield:nth-child(odd)');\r | |
374 | *\r | |
375 | * // Retrieve every even field in a form, excluding hidden fields\r | |
376 | * myFormPanel.query('field:not(hiddenfield):nth-child(even)');\r | |
377 | *\r | |
378 | * // Retrieve every scrollable in a tabpanel\r | |
379 | * tabpanel.query(':scrollable');\r | |
380 | *\r | |
381 | * For easy access to queries based from a particular Container see the\r | |
382 | * {@link Ext.container.Container#query}, {@link Ext.container.Container#down} and\r | |
383 | * {@link Ext.container.Container#child} methods. Also see\r | |
384 | * {@link Ext.Component#up}.\r | |
385 | */\r | |
386 | Ext.define('Ext.ComponentQuery', {\r | |
387 | singleton: true,\r | |
388 | requires: [\r | |
389 | 'Ext.ComponentManager',\r | |
390 | 'Ext.util.Operators',\r | |
391 | 'Ext.util.LruCache'\r | |
392 | ]\r | |
393 | }, function() {\r | |
394 | \r | |
395 | var cq = this,\r | |
396 | queryOperators = Ext.util.Operators,\r | |
397 | nthRe = /(\d*)n\+?(\d*)/,\r | |
398 | nthRe2 = /\D/,\r | |
399 | stripLeadingSpaceRe = /^(\s)+/,\r | |
400 | unescapeRe = /\\(.)/g,\r | |
401 | regexCache = new Ext.util.LruCache({\r | |
402 | maxSize: 100\r | |
403 | }),\r | |
404 | \r | |
405 | // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied\r | |
406 | // as a member on each item in the passed array.\r | |
407 | filterFnPattern = [\r | |
408 | 'var r = [],',\r | |
409 | 'i = 0,',\r | |
410 | 'it = items,',\r | |
411 | 'l = it.length,',\r | |
412 | 'c;',\r | |
413 | 'for (; i < l; i++) {',\r | |
414 | 'c = it[i];',\r | |
415 | 'if (c.{0}) {',\r | |
416 | 'r.push(c);',\r | |
417 | '}',\r | |
418 | '}',\r | |
419 | 'return r;'\r | |
420 | ].join(''),\r | |
421 | \r | |
422 | filterItems = function(items, operation) {\r | |
423 | // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]\r | |
424 | // The operation's method loops over each item in the candidate array and\r | |
425 | // returns an array of items which match its criteria\r | |
426 | return operation.method.apply(this, [ items ].concat(operation.args));\r | |
427 | },\r | |
428 | \r | |
429 | getItems = function(items, mode) {\r | |
430 | var result = [],\r | |
431 | i = 0,\r | |
432 | length = items.length,\r | |
433 | candidate,\r | |
434 | deep = mode !== '>';\r | |
435 | \r | |
436 | for (; i < length; i++) {\r | |
437 | candidate = items[i];\r | |
438 | if (candidate.getRefItems) {\r | |
439 | result = result.concat(candidate.getRefItems(deep));\r | |
440 | }\r | |
441 | }\r | |
442 | return result;\r | |
443 | },\r | |
444 | \r | |
445 | getAncestors = function(items) {\r | |
446 | var result = [],\r | |
447 | i = 0,\r | |
448 | length = items.length,\r | |
449 | candidate;\r | |
450 | for (; i < length; i++) {\r | |
451 | candidate = items[i];\r | |
452 | while (!!(candidate = candidate.getRefOwner())) {\r | |
453 | result.push(candidate);\r | |
454 | }\r | |
455 | }\r | |
456 | return result;\r | |
457 | },\r | |
458 | \r | |
459 | // Filters the passed candidate array and returns only items which match the passed xtype\r | |
460 | filterByXType = function(items, xtype, shallow) {\r | |
461 | if (xtype === '*') {\r | |
462 | return items.slice();\r | |
463 | }\r | |
464 | else {\r | |
465 | var result = [],\r | |
466 | i = 0,\r | |
467 | length = items.length,\r | |
468 | candidate;\r | |
469 | for (; i < length; i++) {\r | |
470 | candidate = items[i];\r | |
471 | if (candidate.isXType(xtype, shallow)) {\r | |
472 | result.push(candidate);\r | |
473 | }\r | |
474 | }\r | |
475 | return result;\r | |
476 | }\r | |
477 | },\r | |
478 | \r | |
479 | // Filters the passed candidate array and returns only items which have the specified property match\r | |
480 | filterByAttribute = function(items, property, operator, compareTo) {\r | |
481 | var result = [],\r | |
482 | i = 0,\r | |
483 | length = items.length,\r | |
484 | mustBeOwnProperty,\r | |
485 | presenceOnly,\r | |
486 | candidate, propValue,\r | |
487 | j, propLen,\r | |
488 | config;\r | |
489 | \r | |
490 | // Prefixing property name with an @ means that the property must be in the candidate, not in its prototype\r | |
491 | if (property.charAt(0) === '@') {\r | |
492 | mustBeOwnProperty = true;\r | |
493 | property = property.substr(1);\r | |
494 | }\r | |
495 | if (property.charAt(0) === '?') {\r | |
496 | mustBeOwnProperty = true;\r | |
497 | presenceOnly = true;\r | |
498 | property = property.substr(1);\r | |
499 | }\r | |
500 | \r | |
501 | for (; i < length; i++) {\r | |
502 | candidate = items[i];\r | |
503 | \r | |
504 | // If the candidate is a product of the Ext class system, then\r | |
505 | // use the configurator to call getters to access the property.\r | |
506 | // CQ can be used to filter raw Objects.\r | |
507 | config = candidate.getConfigurator && candidate.self.$config.configs[property];\r | |
508 | if (config) {\r | |
509 | propValue = candidate[config.names.get]();\r | |
510 | } else if (mustBeOwnProperty && !candidate.hasOwnProperty(property)) {\r | |
511 | continue;\r | |
512 | } else {\r | |
513 | propValue = candidate[property];\r | |
514 | }\r | |
515 | \r | |
516 | if (presenceOnly) {\r | |
517 | result.push(candidate);\r | |
518 | }\r | |
519 | // implies property is an array, and we must compare value against each element.\r | |
520 | else if (operator === '~=') {\r | |
521 | if (propValue) {\r | |
522 | //We need an array\r | |
523 | if (!Ext.isArray(propValue)) {\r | |
524 | propValue = propValue.split(' ');\r | |
525 | }\r | |
526 | \r | |
527 | for (j = 0, propLen = propValue.length; j < propLen; j++) {\r | |
528 | if (queryOperators[operator](Ext.coerce(propValue[j], compareTo), compareTo)) {\r | |
529 | result.push(candidate);\r | |
530 | break;\r | |
531 | }\r | |
532 | }\r | |
533 | }\r | |
534 | }\r | |
535 | else if (operator === '/=') {\r | |
536 | if (propValue != null && compareTo.test(propValue)) {\r | |
537 | result.push(candidate);\r | |
538 | }\r | |
539 | } else if (!compareTo ? !!candidate[property] : queryOperators[operator](Ext.coerce(propValue, compareTo), compareTo)) {\r | |
540 | result.push(candidate);\r | |
541 | }\r | |
542 | }\r | |
543 | return result;\r | |
544 | },\r | |
545 | \r | |
546 | // Filters the passed candidate array and returns only items which have the specified itemId or id\r | |
547 | filterById = function(items, id) {\r | |
548 | var result = [],\r | |
549 | i = 0,\r | |
550 | length = items.length,\r | |
551 | candidate;\r | |
552 | for (; i < length; i++) {\r | |
553 | candidate = items[i];\r | |
554 | if (candidate.getItemId() === id) {\r | |
555 | result.push(candidate);\r | |
556 | }\r | |
557 | }\r | |
558 | return result;\r | |
559 | },\r | |
560 | \r | |
561 | // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in\r | |
562 | filterByPseudo = function(items, name, value) {\r | |
563 | return cq.pseudos[name](items, value);\r | |
564 | },\r | |
565 | \r | |
566 | // Determines leading mode\r | |
567 | // > for direct child, and ^ to switch to ownerCt axis\r | |
568 | modeRe = /^(\s?([>\^])\s?|\s|$)/,\r | |
569 | \r | |
570 | // Matches a token with possibly (true|false) appended for the "shallow" parameter\r | |
571 | tokenRe = /^(#)?((?:\\\.|[\w\-])+|\*)(?:\((true|false)\))?/,\r | |
572 | \r | |
573 | matchers = [{\r | |
574 | // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter\r | |
575 | re: /^\.((?:\\\.|[\w\-])+)(?:\((true|false)\))?/,\r | |
576 | method: filterByXType,\r | |
577 | argTransform: function(args) {\r | |
578 | //<debug>\r | |
579 | var selector = args[0];\r | |
580 | Ext.log.warn('"'+ selector +'" ComponentQuery selector style is deprecated,' +\r | |
581 | ' use "' + selector.replace(/^\./, '') + '" without the leading dot instead');\r | |
582 | //</debug>\r | |
583 | \r | |
584 | if (args[1] !== undefined) {\r | |
585 | args[1] = args[1].replace(unescapeRe, '$1');\r | |
586 | }\r | |
587 | \r | |
588 | return args.slice(1);\r | |
589 | }\r | |
590 | }, {\r | |
591 | // Allow [@attribute] to check truthy ownProperty\r | |
592 | // Allow [?attribute] to check for presence of ownProperty\r | |
593 | // Allow [$attribute]\r | |
594 | // Checks for @|?|$ -> word/hyphen chars -> any special attribute selector characters before\r | |
595 | // the '=', etc. It strips out whitespace.\r | |
596 | // For example:\r | |
597 | // [attribute=value], [attribute^=value], [attribute$=value], [attribute*=value],\r | |
598 | // [attribute~=value], [attribute%=value], [attribute!=value]\r | |
599 | re: /^(?:\[((?:[@?$])?[\w\-]*)\s*(?:([\^$*~%!\/]?=)\s*(['"])?((?:\\\]|.)*?)\3)?(?!\\)\])/,\r | |
600 | method: filterByAttribute,\r | |
601 | argTransform: function(args) {\r | |
602 | var selector = args[0],\r | |
603 | property = args[1],\r | |
604 | operator = args[2],\r | |
605 | //quote = args[3],\r | |
606 | compareTo = args[4],\r | |
607 | compareRe;\r | |
608 | \r | |
609 | // Unescape the attribute value matcher first\r | |
610 | if (compareTo !== undefined) {\r | |
611 | compareTo = compareTo.replace(unescapeRe, '$1');\r | |
612 | \r | |
613 | //<debug>\r | |
614 | var format = Ext.String.format,\r | |
615 | msg = "ComponentQuery selector '{0}' has an unescaped ({1}) character at the {2} " +\r | |
616 | "of the attribute value pattern. Usually that indicates an error " +\r | |
617 | "where the opening quote is not followed by the closing quote. " +\r | |
618 | "If you need to match a ({1}) character at the {2} of the attribute " +\r | |
619 | "value, escape the quote character in your pattern: (\\{1})",\r | |
620 | match;\r | |
621 | \r | |
622 | if (match = /^(['"]).*?[^'"]$/.exec(compareTo)) { // jshint ignore:line\r | |
623 | Ext.log.warn(format(msg, selector, match[1], 'beginning'));\r | |
624 | }\r | |
625 | else if (match = /^[^'"].*?(['"])$/.exec(compareTo)) { // jshint ignore:line\r | |
626 | Ext.log.warn(format(msg, selector, match[1], 'end'));\r | |
627 | }\r | |
628 | //</debug>\r | |
629 | }\r | |
630 | \r | |
631 | if (operator === '/=') {\r | |
632 | compareRe = regexCache.get(compareTo);\r | |
633 | if (compareRe) {\r | |
634 | compareTo = compareRe;\r | |
635 | } else {\r | |
636 | compareTo = regexCache.add(compareTo, new RegExp(compareTo));\r | |
637 | }\r | |
638 | }\r | |
639 | \r | |
640 | return [property, operator, compareTo];\r | |
641 | }\r | |
642 | }, {\r | |
643 | // checks for #cmpItemId\r | |
644 | re: /^#((?:\\\.|[\w\-])+)/,\r | |
645 | method: filterById\r | |
646 | }, {\r | |
647 | // checks for :<pseudo_class>(<selector>)\r | |
648 | re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,\r | |
649 | method: filterByPseudo,\r | |
650 | argTransform: function(args) {\r | |
651 | if (args[2] !== undefined) {\r | |
652 | args[2] = args[2].replace(unescapeRe, '$1');\r | |
653 | }\r | |
654 | \r | |
655 | return args.slice(1);\r | |
656 | }\r | |
657 | }, {\r | |
658 | // checks for {<member_expression>}\r | |
659 | re: /^(?:\{([^\}]+)\})/,\r | |
660 | method: filterFnPattern\r | |
661 | }];\r | |
662 | \r | |
663 | // Internal class Ext.ComponentQuery.Query\r | |
664 | cq.Query = Ext.extend(Object, {\r | |
665 | constructor: function(cfg) {\r | |
666 | cfg = cfg || {};\r | |
667 | Ext.apply(this, cfg);\r | |
668 | },\r | |
669 | \r | |
670 | // Executes this Query upon the selected root.\r | |
671 | // The root provides the initial source of candidate Component matches which are progressively\r | |
672 | // filtered by iterating through this Query's operations cache.\r | |
673 | // If no root is provided, all registered Components are searched via the ComponentManager.\r | |
674 | // root may be a Container who's descendant Components are filtered\r | |
675 | // root may be a Component with an implementation of getRefItems which provides some nested Components such as the\r | |
676 | // docked items within a Panel.\r | |
677 | // root may be an array of candidate Components to filter using this Query.\r | |
678 | execute: function(root) {\r | |
679 | var operations = this.operations,\r | |
680 | result = [],\r | |
681 | op, i, len;\r | |
682 | \r | |
683 | for (i = 0, len = operations.length; i < len; i++) {\r | |
684 | op = operations[i];\r | |
685 | \r | |
686 | result = result.concat(this._execute(root, op));\r | |
687 | }\r | |
688 | \r | |
689 | return result;\r | |
690 | },\r | |
691 | \r | |
692 | _execute: function(root, operations) {\r | |
693 | var i = 0,\r | |
694 | length = operations.length,\r | |
695 | operation,\r | |
696 | workingItems;\r | |
697 | \r | |
698 | // no root, use all Components in the document\r | |
699 | if (!root) {\r | |
700 | workingItems = Ext.ComponentManager.getAll();\r | |
701 | }\r | |
702 | // Root is an iterable object like an Array, or system Collection, eg HtmlCollection\r | |
703 | else if (Ext.isIterable(root)) {\r | |
704 | workingItems = root;\r | |
705 | }\r | |
706 | // Root is a MixedCollection\r | |
707 | else if (root.isMixedCollection) {\r | |
708 | workingItems = root.items;\r | |
709 | }\r | |
710 | \r | |
711 | // We are going to loop over our operations and take care of them\r | |
712 | // one by one.\r | |
713 | for (; i < length; i++) {\r | |
714 | operation = operations[i];\r | |
715 | \r | |
716 | // The mode operation requires some custom handling.\r | |
717 | // All other operations essentially filter down our current\r | |
718 | // working items, while mode replaces our current working\r | |
719 | // items by getting children from each one of our current\r | |
720 | // working items. The type of mode determines the type of\r | |
721 | // children we get. (e.g. > only gets direct children)\r | |
722 | if (operation.mode === '^') {\r | |
723 | workingItems = getAncestors(workingItems || [root]);\r | |
724 | }\r | |
725 | else if (operation.mode) {\r | |
726 | workingItems = getItems(workingItems || [root], operation.mode);\r | |
727 | }\r | |
728 | else {\r | |
729 | workingItems = filterItems(workingItems || getItems([root]), operation);\r | |
730 | }\r | |
731 | \r | |
732 | // If this is the last operation, it means our current working\r | |
733 | // items are the final matched items. Thus return them!\r | |
734 | if (i === length -1) {\r | |
735 | return workingItems;\r | |
736 | }\r | |
737 | }\r | |
738 | return [];\r | |
739 | },\r | |
740 | \r | |
741 | is: function(component) {\r | |
742 | var operations = this.operations,\r | |
743 | result = false,\r | |
744 | len = operations.length,\r | |
745 | op, i;\r | |
746 | \r | |
747 | if (len === 0) {\r | |
748 | return true;\r | |
749 | }\r | |
750 | \r | |
751 | for (i = 0; i < len; i++) {\r | |
752 | op = operations[i];\r | |
753 | \r | |
754 | result = this._is(component, op);\r | |
755 | \r | |
756 | if (result) {\r | |
757 | return result;\r | |
758 | }\r | |
759 | }\r | |
760 | \r | |
761 | return false;\r | |
762 | },\r | |
763 | \r | |
764 | _is: function(component, operations) {\r | |
765 | var len = operations.length,\r | |
766 | active = [component],\r | |
767 | operation, i, j, mode, items, item;\r | |
768 | \r | |
769 | // Loop backwards, since we're going up the hierarchy\r | |
770 | for (i = len - 1; i >= 0; --i) {\r | |
771 | operation = operations[i];\r | |
772 | mode = operation.mode;\r | |
773 | // Traversing hierarchy\r | |
774 | if (mode) {\r | |
775 | if (mode === '^') {\r | |
776 | active = getItems(active, ' ');\r | |
777 | } else if (mode === '>') {\r | |
778 | items = [];\r | |
779 | for (j = 0, len = active.length; j < len; ++j) {\r | |
780 | item = active[j].getRefOwner();\r | |
781 | if (item) {\r | |
782 | items.push(item);\r | |
783 | }\r | |
784 | }\r | |
785 | active = items;\r | |
786 | } else {\r | |
787 | active = getAncestors(active);\r | |
788 | }\r | |
789 | \r | |
790 | // After traversing the hierarchy, if we have no items, jump out\r | |
791 | if (active.length === 0) {\r | |
792 | return false;\r | |
793 | }\r | |
794 | \r | |
795 | } else {\r | |
796 | active = filterItems(active, operation);\r | |
797 | if (active.length === 0) {\r | |
798 | return false;\r | |
799 | }\r | |
800 | }\r | |
801 | }\r | |
802 | return true;\r | |
803 | },\r | |
804 | \r | |
805 | getMatches: function(components, operations) {\r | |
806 | var len = operations.length,\r | |
807 | i;\r | |
808 | \r | |
809 | for (i = 0; i < len; ++i) {\r | |
810 | components = filterItems(components, operations[i]);\r | |
811 | // Performance enhancement, if we have nothing, we can\r | |
812 | // never add anything new, so jump out\r | |
813 | if (components.length === 0) {\r | |
814 | break;\r | |
815 | } \r | |
816 | }\r | |
817 | return components;\r | |
818 | },\r | |
819 | \r | |
820 | isMultiMatch: function() {\r | |
821 | return this.operations.length > 1;\r | |
822 | }\r | |
823 | });\r | |
824 | \r | |
825 | Ext.apply(cq, {\r | |
826 | \r | |
827 | /**\r | |
828 | * @private\r | |
829 | * Cache of selectors and matching ComponentQuery.Query objects\r | |
830 | */\r | |
831 | cache: new Ext.util.LruCache({\r | |
832 | maxSize: 100\r | |
833 | }),\r | |
834 | \r | |
835 | /**\r | |
836 | * @private\r | |
837 | * Cache of pseudo class filter functions\r | |
838 | */\r | |
839 | pseudos: {\r | |
840 | not: function(components, selector){\r | |
841 | var i = 0,\r | |
842 | length = components.length,\r | |
843 | results = [],\r | |
844 | index = -1,\r | |
845 | component;\r | |
846 | \r | |
847 | for(; i < length; ++i) {\r | |
848 | component = components[i];\r | |
849 | if (!cq.is(component, selector)) {\r | |
850 | results[++index] = component;\r | |
851 | }\r | |
852 | }\r | |
853 | return results;\r | |
854 | },\r | |
855 | first: function(components) {\r | |
856 | var ret = [];\r | |
857 | \r | |
858 | if (components.length > 0) {\r | |
859 | ret.push(components[0]);\r | |
860 | }\r | |
861 | return ret;\r | |
862 | },\r | |
863 | last: function(components) {\r | |
864 | var len = components.length,\r | |
865 | ret = [];\r | |
866 | \r | |
867 | if (len > 0) {\r | |
868 | ret.push(components[len - 1]);\r | |
869 | }\r | |
870 | return ret;\r | |
871 | },\r | |
872 | focusable: function(cmps) {\r | |
873 | var len = cmps.length,\r | |
874 | results = [],\r | |
875 | i = 0,\r | |
876 | c;\r | |
877 | \r | |
878 | for (; i < len; i++) {\r | |
879 | c = cmps[i];\r | |
880 | \r | |
881 | if (c.isFocusable && c.isFocusable()) {\r | |
882 | results.push(c);\r | |
883 | }\r | |
884 | }\r | |
885 | \r | |
886 | return results;\r | |
887 | },\r | |
888 | "nth-child" : function(c, a) {\r | |
889 | var result = [],\r | |
890 | m = nthRe.exec(a === "even" && "2n" || a === "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),\r | |
891 | f = (m[1] || 1) - 0, len = m[2] - 0,\r | |
892 | i, n, nodeIndex;\r | |
893 | \r | |
894 | for (i = 0; n = c[i]; i++) { // jshint ignore:line\r | |
895 | nodeIndex = i + 1;\r | |
896 | if (f === 1) {\r | |
897 | if (len === 0 || nodeIndex === len) {\r | |
898 | result.push(n);\r | |
899 | }\r | |
900 | } else if ((nodeIndex + len) % f === 0){\r | |
901 | result.push(n);\r | |
902 | }\r | |
903 | }\r | |
904 | \r | |
905 | return result;\r | |
906 | },\r | |
907 | scrollable: function(cmps) {\r | |
908 | var len = cmps.length,\r | |
909 | results = [],\r | |
910 | i = 0,\r | |
911 | c;\r | |
912 | \r | |
913 | for (; i < len; i++) {\r | |
914 | c = cmps[i];\r | |
915 | \r | |
916 | // Note that modern toolkit prefixes with an underscore.\r | |
917 | if (c.scrollable || c._scrollable) {\r | |
918 | results.push(c);\r | |
919 | }\r | |
920 | }\r | |
921 | \r | |
922 | return results;\r | |
923 | }\r | |
924 | },\r | |
925 | \r | |
926 | /**\r | |
927 | * Returns an array of matched Components from within the passed root object.\r | |
928 | *\r | |
929 | * This method filters returned Components in a similar way to how CSS selector based DOM\r | |
930 | * queries work using a textual selector string.\r | |
931 | *\r | |
932 | * See class summary for details.\r | |
933 | *\r | |
934 | * @param {String} selector The selector string to filter returned Components.\r | |
935 | * @param {Ext.container.Container} [root] The Container within which to perform the query.\r | |
936 | * If omitted, all Components within the document are included in the search.\r | |
937 | *\r | |
938 | * This parameter may also be an array of Components to filter according to the selector.\r | |
939 | * @return {Ext.Component[]} The matched Components.\r | |
940 | *\r | |
941 | * @member Ext.ComponentQuery\r | |
942 | */\r | |
943 | query: function(selector, root) {\r | |
944 | // An empty query will match every Component\r | |
945 | if (!selector) {\r | |
946 | return Ext.ComponentManager.all.getArray();\r | |
947 | }\r | |
948 | \r | |
949 | var results = [],\r | |
950 | noDupResults = [], \r | |
951 | dupMatcher = {}, \r | |
952 | query = cq.cache.get(selector),\r | |
953 | resultsLn, cmp, i;\r | |
954 | \r | |
955 | if (!query) {\r | |
956 | query = cq.cache.add(selector, cq.parse(selector));\r | |
957 | }\r | |
958 | \r | |
959 | results = query.execute(root);\r | |
960 | \r | |
961 | // multiple selectors, potential to find duplicates\r | |
962 | // lets filter them out.\r | |
963 | if (query.isMultiMatch()) {\r | |
964 | resultsLn = results.length;\r | |
965 | for (i = 0; i < resultsLn; i++) {\r | |
966 | cmp = results[i];\r | |
967 | if (!dupMatcher[cmp.id]) {\r | |
968 | noDupResults.push(cmp);\r | |
969 | dupMatcher[cmp.id] = true;\r | |
970 | }\r | |
971 | }\r | |
972 | results = noDupResults;\r | |
973 | }\r | |
974 | return results;\r | |
975 | },\r | |
976 | \r | |
977 | /**\r | |
978 | * Traverses the tree rooted at the passed root in pre-order mode, calling the passed function on the nodes at each level.\r | |
979 | * That is the function is called upon each node **before** being called on its children).\r | |
980 | *\r | |
981 | * For an object to be queryable, it must implement the `getRefItems` method which returns all\r | |
982 | * immediate child items.\r | |
983 | *\r | |
984 | * This method is used at each level down the cascade. Currently {@link Ext.Component Component}s\r | |
985 | * and {@link Ext.data.TreeModel TreeModel}s are queryable.\r | |
986 | *\r | |
987 | * If you have tree-structured data, you can make your nodes queryable, and use ComponentQuery on them.\r | |
988 | *\r | |
989 | * @param {Object} selector A ComponentQuery selector used to filter candidate nodes before calling the function.\r | |
990 | * An empty string matches any node.\r | |
991 | * @param {String} root The root queryable object to start from.\r | |
992 | * @param {Function} fn The function to call. Return `false` to abort the traverse.\r | |
993 | * @param {Object} fn.node The node being visited.\r | |
994 | * @param {Object} [scope] The context (`this` reference) in which the function is executed.\r | |
995 | * @param {Array} [extraArgs] A set of arguments to be appended to the function's argument list to pass down extra data known to the caller\r | |
996 | * **after** the node being visited.\r | |
997 | */\r | |
998 | visitPreOrder: function(selector, root, fn, scope, extraArgs) {\r | |
999 | cq._visit(true, selector, root, fn, scope, extraArgs);\r | |
1000 | },\r | |
1001 | \r | |
1002 | /**\r | |
1003 | * Traverses the tree rooted at the passed root in post-order mode, calling the passed function on the nodes at each level.\r | |
1004 | * That is the function is called upon each node **after** being called on its children).\r | |
1005 | *\r | |
1006 | * For an object to be queryable, it must implement the `getRefItems` method which returns all\r | |
1007 | * immediate child items.\r | |
1008 | *\r | |
1009 | * This method is used at each level down the cascade. Currently {@link Ext.Component Component}s\r | |
1010 | * and {@link Ext.data.TreeModel TreeModel}s are queryable.\r | |
1011 | *\r | |
1012 | * If you have tree-structured data, you can make your nodes queryable, and use ComponentQuery on them.\r | |
1013 | *\r | |
1014 | * @param {Object} selector A ComponentQuery selector used to filter candidate nodes before calling the function.\r | |
1015 | * An empty string matches any node.\r | |
1016 | * @param {String} root The root queryable object to start from.\r | |
1017 | * @param {Function} fn The function to call. Return `false` to abort the traverse.\r | |
1018 | * @param {Object} fn.node The node being visited.\r | |
1019 | * @param {Object} [scope] The context (`this` reference) in which the function is executed.\r | |
1020 | * @param {Array} [extraArgs] A set of arguments to be appended to the function's argument list to pass down extra data known to the caller\r | |
1021 | * **after** the node being visited.\r | |
1022 | */\r | |
1023 | visitPostOrder: function(selector, root, fn, scope, extraArgs) {\r | |
1024 | cq._visit(false, selector, root, fn, scope, extraArgs);\r | |
1025 | },\r | |
1026 | \r | |
1027 | /**\r | |
1028 | * @private\r | |
1029 | * Visit implementation which handles both preOrder and postOrder modes.\r | |
1030 | */\r | |
1031 | _visit: function(preOrder, selector, root, fn, scope, extraArgs) {\r | |
1032 | var query = cq.cache.get(selector),\r | |
1033 | callArgs = [root],\r | |
1034 | children,\r | |
1035 | len = 0,\r | |
1036 | i, rootMatch;\r | |
1037 | \r | |
1038 | if (!query) {\r | |
1039 | query = cq.cache.add(selector, cq.parse(selector));\r | |
1040 | }\r | |
1041 | \r | |
1042 | rootMatch = query.is(root);\r | |
1043 | \r | |
1044 | if (root.getRefItems) {\r | |
1045 | children = root.getRefItems();\r | |
1046 | len = children.length;\r | |
1047 | }\r | |
1048 | \r | |
1049 | // append optional extraArgs\r | |
1050 | if (extraArgs) {\r | |
1051 | Ext.Array.push(callArgs, extraArgs);\r | |
1052 | }\r | |
1053 | if (preOrder) {\r | |
1054 | if (rootMatch) {\r | |
1055 | if (fn.apply(scope || root, callArgs) === false) {\r | |
1056 | return false;\r | |
1057 | }\r | |
1058 | }\r | |
1059 | }\r | |
1060 | for (i = 0; i < len; i++) {\r | |
1061 | if (cq._visit.call(cq, preOrder, selector, children[i], fn, scope, extraArgs) === false) {\r | |
1062 | return false;\r | |
1063 | }\r | |
1064 | }\r | |
1065 | if (!preOrder) {\r | |
1066 | if (rootMatch) {\r | |
1067 | if (fn.apply(scope || root, callArgs) === false) {\r | |
1068 | return false;\r | |
1069 | }\r | |
1070 | }\r | |
1071 | }\r | |
1072 | },\r | |
1073 | \r | |
1074 | /**\r | |
1075 | * Tests whether the passed Component matches the selector string.\r | |
1076 | * An empty selector will always match.\r | |
1077 | *\r | |
1078 | * @param {Ext.Component} component The Component to test\r | |
1079 | * @param {String} selector The selector string to test against.\r | |
1080 | * @return {Boolean} True if the Component matches the selector.\r | |
1081 | * @member Ext.ComponentQuery\r | |
1082 | */\r | |
1083 | is: function(component, selector) {\r | |
1084 | if (!selector) {\r | |
1085 | return true;\r | |
1086 | }\r | |
1087 | \r | |
1088 | var query = cq.cache.get(selector);\r | |
1089 | if (!query) {\r | |
1090 | query = cq.cache.add(selector, cq.parse(selector));\r | |
1091 | }\r | |
1092 | \r | |
1093 | return query.is(component);\r | |
1094 | },\r | |
1095 | \r | |
1096 | parse: function(selector) {\r | |
1097 | var operations = [],\r | |
1098 | selectors, sel, i, len;\r | |
1099 | \r | |
1100 | selectors = Ext.splitAndUnescape(selector, ',');\r | |
1101 | \r | |
1102 | for (i = 0, len = selectors.length; i < len; i++) {\r | |
1103 | // Trim the whitespace as the parser expects it.\r | |
1104 | sel = Ext.String.trim(selectors[i]);\r | |
1105 | \r | |
1106 | // Usually, a dangling comma at the end of a selector means a typo.\r | |
1107 | // In that case, the last sel value will be an empty string; the parser\r | |
1108 | // will silently ignore it which is not good, so we throw an error here.\r | |
1109 | //<debug>\r | |
1110 | if (sel === '') {\r | |
1111 | Ext.raise('Invalid ComponentQuery selector: ""');\r | |
1112 | }\r | |
1113 | //</debug>\r | |
1114 | \r | |
1115 | operations.push(cq._parse(sel));\r | |
1116 | }\r | |
1117 | \r | |
1118 | // Now that we have all our operations in an array, we are going\r | |
1119 | // to create a new Query using these operations.\r | |
1120 | return new cq.Query({\r | |
1121 | operations: operations\r | |
1122 | });\r | |
1123 | },\r | |
1124 | \r | |
1125 | _parse: function(selector) {\r | |
1126 | var operations = [],\r | |
1127 | trim = Ext.String.trim,\r | |
1128 | length = matchers.length,\r | |
1129 | lastSelector,\r | |
1130 | tokenMatch,\r | |
1131 | token,\r | |
1132 | matchedChar,\r | |
1133 | modeMatch,\r | |
1134 | selectorMatch,\r | |
1135 | transform,\r | |
1136 | i, matcher, method, args;\r | |
1137 | \r | |
1138 | // We are going to parse the beginning of the selector over and\r | |
1139 | // over again, slicing off the selector any portions we converted into an\r | |
1140 | // operation, until it is an empty string.\r | |
1141 | while (selector && lastSelector !== selector) {\r | |
1142 | lastSelector = selector;\r | |
1143 | \r | |
1144 | // First we check if we are dealing with a token like #, * or an xtype\r | |
1145 | tokenMatch = selector.match(tokenRe);\r | |
1146 | \r | |
1147 | if (tokenMatch) {\r | |
1148 | matchedChar = tokenMatch[1];\r | |
1149 | token = trim(tokenMatch[2]).replace(unescapeRe, '$1');\r | |
1150 | \r | |
1151 | // If the token is prefixed with a # we push a filterById operation to our stack\r | |
1152 | if (matchedChar === '#') {\r | |
1153 | operations.push({\r | |
1154 | method: filterById,\r | |
1155 | args: [token]\r | |
1156 | });\r | |
1157 | }\r | |
1158 | // If the token is a * or an xtype string, we push a filterByXType\r | |
1159 | // operation to the stack.\r | |
1160 | else {\r | |
1161 | operations.push({\r | |
1162 | method: filterByXType,\r | |
1163 | args: [token, Boolean(tokenMatch[3])]\r | |
1164 | });\r | |
1165 | }\r | |
1166 | \r | |
1167 | // Now we slice of the part we just converted into an operation\r | |
1168 | selector = selector.replace(tokenMatch[0], '').replace(stripLeadingSpaceRe, '$1');\r | |
1169 | }\r | |
1170 | \r | |
1171 | // If the next part of the query is not a space or > or ^, it means we\r | |
1172 | // are going to check for more things that our current selection\r | |
1173 | // has to comply to.\r | |
1174 | while (!(modeMatch = selector.match(modeRe))) {\r | |
1175 | // Lets loop over each type of matcher and execute it\r | |
1176 | // on our current selector.\r | |
1177 | for (i = 0; selector && i < length; i++) {\r | |
1178 | matcher = matchers[i];\r | |
1179 | selectorMatch = selector.match(matcher.re);\r | |
1180 | method = matcher.method;\r | |
1181 | transform = matcher.argTransform;\r | |
1182 | \r | |
1183 | // If we have a match, add an operation with the method\r | |
1184 | // associated with this matcher, and pass the regular\r | |
1185 | // expression matches are arguments to the operation.\r | |
1186 | if (selectorMatch) {\r | |
1187 | // Transform function will do unescaping and additional checks\r | |
1188 | if (transform) {\r | |
1189 | args = transform(selectorMatch);\r | |
1190 | }\r | |
1191 | else {\r | |
1192 | args = selectorMatch.slice(1);\r | |
1193 | }\r | |
1194 | \r | |
1195 | operations.push({\r | |
1196 | method: Ext.isString(matcher.method)\r | |
1197 | // Turn a string method into a function by formatting the string with our selector matche expression\r | |
1198 | // A new method is created for different match expressions, eg {id=='textfield-1024'}\r | |
1199 | // Every expression may be different in different selectors.\r | |
1200 | ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))\r | |
1201 | : matcher.method,\r | |
1202 | args: args\r | |
1203 | });\r | |
1204 | \r | |
1205 | selector = selector.replace(selectorMatch[0], '').replace(stripLeadingSpaceRe, '$1');\r | |
1206 | break; // Break on match\r | |
1207 | }\r | |
1208 | // Exhausted all matches: It's an error\r | |
1209 | if (i === (length - 1)) {\r | |
1210 | Ext.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');\r | |
1211 | }\r | |
1212 | }\r | |
1213 | }\r | |
1214 | \r | |
1215 | // Now we are going to check for a mode change. This means a space\r | |
1216 | // or a > to determine if we are going to select all the children\r | |
1217 | // of the currently matched items, or a ^ if we are going to use the\r | |
1218 | // ownerCt axis as the candidate source.\r | |
1219 | if (modeMatch[1]) { // Assignment, and test for truthiness!\r | |
1220 | operations.push({\r | |
1221 | mode: modeMatch[2]||modeMatch[1]\r | |
1222 | });\r | |
1223 | \r | |
1224 | // When we have consumed the mode character, clean up leading spaces\r | |
1225 | selector = selector.replace(modeMatch[0], '').replace(stripLeadingSpaceRe, '');\r | |
1226 | }\r | |
1227 | }\r | |
1228 | \r | |
1229 | return operations;\r | |
1230 | }\r | |
1231 | });\r | |
1232 | \r | |
1233 | /**\r | |
1234 | * Same as {@link Ext.ComponentQuery#query}.\r | |
1235 | * @param {String} selector The selector string to filter returned Components.\r | |
1236 | * @param {Ext.container.Container} [root] The Container within which to perform the query.\r | |
1237 | * If omitted, all Components within the document are included in the search.\r | |
1238 | *\r | |
1239 | * This parameter may also be an array of Components to filter according to the selector.\r | |
1240 | * @return {Ext.Component[]} The matched Components.\r | |
1241 | * @method all\r | |
1242 | * @member Ext\r | |
1243 | */\r | |
1244 | Ext.all = function () {\r | |
1245 | return cq.query.apply(cq, arguments);\r | |
1246 | };\r | |
1247 | \r | |
1248 | /**\r | |
1249 | * Returns the first match to the given component query.\r | |
1250 | * See {@link Ext.ComponentQuery#query}.\r | |
1251 | * @param {String} selector The selector string to filter returned Component.\r | |
1252 | * @param {Ext.container.Container} [root] The Container within which to perform the query.\r | |
1253 | * If omitted, all Components within the document are included in the search.\r | |
1254 | *\r | |
1255 | * This parameter may also be an array of Components to filter according to the selector.\r | |
1256 | * @return {Ext.Component} The first matched Component or `null`.\r | |
1257 | * @method first\r | |
1258 | * @member Ext\r | |
1259 | */\r | |
1260 | Ext.first = function () {\r | |
1261 | var matches = cq.query.apply(cq, arguments);\r | |
1262 | return (matches && matches[0]) || null;\r | |
1263 | }\r | |
1264 | });\r |