ComponentQuery.html 22 KB

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