]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/app/BaseController.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / app / BaseController.js
CommitLineData
6527f429
DM
1/**\r
2 * @protected\r
3 * @class Ext.app.BaseController\r
4 * Base class for Controllers.\r
5 * \r
6 */\r
7Ext.define('Ext.app.BaseController', {\r
8 requires: [\r
9 'Ext.app.EventBus',\r
10 'Ext.app.domain.Global'\r
11 ],\r
12 \r
13 uses: [\r
14 'Ext.app.domain.Controller'\r
15 ],\r
16\r
17 mixins: ['Ext.mixin.Observable'],\r
18\r
19 isController: true,\r
20\r
21 config : {\r
22 /**\r
23 * @cfg {String} id The id of this controller. You can use this id when dispatching.\r
24 * \r
25 * For an example of dispatching, see the examples under the \r
26 * {@link Ext.app.Controller#cfg-listen listen} config.\r
27 *\r
28 * If an id is not explicitly set, it will default to the controller's full classname.\r
29 * \r
30 * @accessor\r
31 */\r
32 id: null,\r
33 \r
34 /**\r
35 * @cfg {Object} control\r
36 * @accessor\r
37 *\r
38 * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an\r
39 * object containing component paths mapped to a hash of listener functions. \r
40 * The function value may also be a string matching the name of a method on the \r
41 * controller.\r
42 *\r
43 * In the following example the `updateUser` function is mapped to to the `click`\r
44 * event on a button component, which is a child of the `useredit` component.\r
45 *\r
46 * Ext.define('MyApp.controller.Users', {\r
47 * extend: 'Ext.app.Controller',\r
48 *\r
49 * control: {\r
50 * 'useredit button[action=save]': {\r
51 * click: 'updateUser'\r
52 * }\r
53 * },\r
54 *\r
55 * updateUser: function(button) {\r
56 * console.log('clicked the Save button');\r
57 * }\r
58 * });\r
59 *\r
60 * The method you pass to the listener will automatically be resolved on the controller.\r
61 * In this case, the `updateUser` method that will get executed on the `button` `click`\r
62 * event will resolve to the `updateUser` method on the controller,\r
63 *\r
64 * See {@link Ext.ComponentQuery} for more information on component selectors.\r
65 */\r
66\r
67 control: null,\r
68\r
69 /**\r
70 * @cfg {Object} listen\r
71 * @accessor\r
72 *\r
73 * Adds listeners to different event sources (also called "event domains"). The\r
74 * primary event domain is that of components, but there are also other event domains:\r
75 * {@link Ext.app.domain.Global Global} domain that intercepts events fired from\r
76 * {@link Ext.GlobalEvents} Observable instance, \r
77 * {@link Ext.app.domain.Controller Controller} domain can be used to listen to events \r
78 * fired by other Controllers, {@link Ext.app.domain.Store Store} domain gives access to \r
79 * Store events, and {@link Ext.app.domain.Direct Direct} domain can be used with \r
80 * Ext Direct Providers to listen to their events.\r
81 *\r
82 * To listen to "bar" events fired by a controller with id="foo":\r
83 *\r
84 * Ext.define('AM.controller.Users', {\r
85 * extend: 'Ext.app.Controller',\r
86 *\r
87 * listen: {\r
88 * controller: {\r
89 * '#foo': {\r
90 * bar: 'onFooBar'\r
91 * }\r
92 * }\r
93 * }\r
94 * });\r
95 *\r
96 * To listen to "bar" events fired by any controller, and "baz" events\r
97 * fired by Store with storeId="baz":\r
98 *\r
99 * Ext.define('AM.controller.Users', {\r
100 * extend: 'Ext.app.Controller',\r
101 *\r
102 * listen: {\r
103 * controller: {\r
104 * '*': {\r
105 * bar: 'onAnyControllerBar'\r
106 * }\r
107 * },\r
108 * store: {\r
109 * '#baz': {\r
110 * baz: 'onStoreBaz'\r
111 * }\r
112 * }\r
113 * }\r
114 * });\r
115 *\r
116 * To listen to "idle" events fired by {@link Ext.GlobalEvents} when other event\r
117 * processing is complete and Ext JS is about to return control to the browser:\r
118 *\r
119 * Ext.define('AM.controller.Users', {\r
120 * extend: 'Ext.app.Controller',\r
121 *\r
122 * listen: {\r
123 * global: { // Global events are always fired\r
124 * idle: 'onIdle' // from the same object, so there\r
125 * } // are no selectors\r
126 * }\r
127 * });\r
128 *\r
129 * As this relates to components, the following example:\r
130 *\r
131 * Ext.define('AM.controller.Users', {\r
132 * extend: 'Ext.app.Controller',\r
133 *\r
134 * listen: {\r
135 * component: {\r
136 * 'useredit button[action=save]': {\r
137 * click: 'updateUser'\r
138 * }\r
139 * }\r
140 * }\r
141 * });\r
142 *\r
143 * Is equivalent to:\r
144 *\r
145 * Ext.define('AM.controller.Users', {\r
146 * extend: 'Ext.app.Controller',\r
147 *\r
148 * control: {\r
149 * 'useredit button[action=save]': {\r
150 * click: 'updateUser'\r
151 * }\r
152 * }\r
153 * });\r
154 *\r
155 * Of course, these can all be combined in a single call and used instead of\r
156 * `control`, like so:\r
157 *\r
158 * Ext.define('AM.controller.Users', {\r
159 * extend: 'Ext.app.Controller',\r
160 *\r
161 * listen: {\r
162 * global: {\r
163 * idle: 'onIdle'\r
164 * },\r
165 * controller: {\r
166 * '*': {\r
167 * foobar: 'onAnyFooBar'\r
168 * },\r
169 * '#foo': {\r
170 * bar: 'onFooBar'\r
171 * }\r
172 * },\r
173 * component: {\r
174 * 'useredit button[action=save]': {\r
175 * click: 'updateUser'\r
176 * }\r
177 * },\r
178 * store: {\r
179 * '#qux': {\r
180 * load: 'onQuxLoad'\r
181 * }\r
182 * }\r
183 * }\r
184 * });\r
185 */\r
186 listen: null,\r
187\r
188 /**\r
189 * @cfg {Object} routes\r
190 * @accessor\r
191 *\r
192 * An object of routes to handle hash changes. A route can be defined in a simple way:\r
193 *\r
194 * routes : {\r
195 * 'foo/bar' : 'handleFoo',\r
196 * 'user/:id' : 'showUser'\r
197 * }\r
198 *\r
199 * Where the property is the hash (which can accept a parameter defined by a colon) and the value\r
200 * is the method on the controller to execute. The parameters will get sent in the action method.\r
201 *\r
202 * At the application level, you can define a event that will be executed when no matching\r
203 * routes are found.\r
204 *\r
205 * Ext.application({\r
206 * name: 'MyApp',\r
207 * listen: {\r
208 * controller: {\r
209 * '#': {\r
210 * unmatchedroute: 'onUnmatchedRoute'\r
211 * }\r
212 * }\r
213 * },\r
214 *\r
215 * onUnmatchedRoute: function(hash) {\r
216 * console.log('Unmatched', hash);\r
217 * // Do something...\r
218 * }\r
219 * });\r
220 *\r
221 * There is also a complex means of defining a route where you can use a before action and even\r
222 * specify your own RegEx for the parameter:\r
223 *\r
224 * routes : {\r
225 * 'foo/bar' : {\r
226 * action : 'handleFoo',\r
227 * before : 'beforeHandleFoo'\r
228 * },\r
229 * 'user/:id' : {\r
230 * action : 'showUser',\r
231 * before : 'beforeShowUser',\r
232 * conditions : {\r
233 * ':id' : '([0-9]+)'\r
234 * }\r
235 * }\r
236 * }\r
237 *\r
238 * This will only match if the `id` parameter is a number.\r
239 *\r
240 * The before action allows you to cancel an action. Every before action will get passed an `action` argument with\r
241 * a `resume` and `stop` methods as the last argument of the method and you *MUST* execute either method:\r
242 *\r
243 * beforeHandleFoo : function(action) {\r
244 * //some logic here\r
245 *\r
246 * //this will allow the handleFoo action to be executed\r
247 * action.resume();\r
248 * },\r
249 * handleFoo : function() {\r
250 * //will get executed due to true being passed in callback in beforeHandleFoo\r
251 * },\r
252 * beforeShowUser : function(id, action) {\r
253 * //allows for async process like an Ajax\r
254 * Ext.Ajax.request({\r
255 * url : 'foo.php',\r
256 * success : function() {\r
257 * //will not allow the showUser method to be executed but will continue other queued actions.\r
258 * action.stop();\r
259 * },\r
260 * failure : function() {\r
261 * //will not allow the showUser method to be executed and will not allow other queued actions to be executed.\r
262 * action.stop(true);\r
263 * }\r
264 * });\r
265 * },\r
266 * showUser : function(id) {\r
267 * //will not get executed due to false being passed in callback in beforeShowUser\r
268 * }\r
269 *\r
270 * You *MUST* execute the `resume` or `stop` method on the `action` argument. Executing `action.resume();` will continue\r
271 * the action, `action.stop();` will not allow the action to resume but will allow other queued actions to resume,\r
272 * `action.stop(true);` will not allow the action and any other queued actions to resume.\r
273 *\r
274 * The default RegEx that will be used is `([%a-zA-Z0-9\\-\\_\\s,]+)` but you can specify any\r
275 * that may suit what you need to accomplish. An example of an advanced condition may be to make\r
276 * a parameter optional and case-insensitive:\r
277 *\r
278 * routes : {\r
279 * 'user:id' : {\r
280 * action : 'showUser',\r
281 * before : 'beforeShowUser',\r
282 * conditions : {\r
283 * ':id' : '(?:(?:\/){1}([%a-z0-9_,\s\-]+))?'\r
284 * }\r
285 * }\r
286 * }\r
287 */\r
288 routes : null,\r
289 before : null\r
290 },\r
291\r
292 /**\r
293 * Creates new Controller.\r
294 *\r
295 * @param {Object} [config] Configuration object.\r
296 */\r
297 constructor: function(config) {\r
298 var me = this;\r
299\r
300 // In versions prior to 5.1, this constructor used to call the Ext.util.Observable\r
301 // constructor (which applied the config properties directly to the instance)\r
302 // AND it used to call initConfig as well. Since the constructor of\r
303 // Ext.mixin.Observable calls initConfig, but does not apply the properties to\r
304 // the instance, we do that here for backward compatibility.\r
305 Ext.apply(me, config);\r
306 // The control and listen properties are also methods so we need to delete them\r
307 // from the instance after applying the config object.\r
308 delete me.control;\r
309 delete me.listen;\r
310\r
311 me.eventbus = Ext.app.EventBus;\r
312\r
313 //need to have eventbus property set before we initialize the config\r
314 me.mixins.observable.constructor.call(me, config);\r
315 // Assuming we haven't set this in updateControl or updateListen, force it here\r
316 me.ensureId();\r
317 },\r
318\r
319 applyListen: function(listen) {\r
320 if (Ext.isObject(listen)) {\r
321 listen = Ext.clone(listen);\r
322 }\r
323\r
324 return listen;\r
325 },\r
326\r
327 applyControl: function(control) {\r
328 if (Ext.isObject(control)) {\r
329 control = Ext.clone(control);\r
330 }\r
331\r
332 return control;\r
333 },\r
334\r
335 /**\r
336 * @param {Object} control The object to pass to the {@link #method-control} method\r
337 * @private\r
338 */\r
339 updateControl: function(control) {\r
340 this.ensureId();\r
341 if (control) {\r
342 this.control(control);\r
343 }\r
344 },\r
345\r
346 /**\r
347 * @param {Object} listen The object to pass to the {@link #method-listen} method\r
348 * @private\r
349 */\r
350 updateListen: function(listen) {\r
351 this.ensureId();\r
352 if (listen) {\r
353 this.listen(listen);\r
354 }\r
355 },\r
356\r
357 /**\r
358 * @param {Object} routes The routes to connect to the {@link Ext.app.route.Router}\r
359 * @private\r
360 */\r
361 updateRoutes : function(routes) {\r
362 if (routes) {\r
363 var me = this,\r
364 befores = me.getBefore() || {},\r
365 Router = Ext.app.route.Router,\r
366 url, config, method;\r
367\r
368 for (url in routes) {\r
369 config = routes[url];\r
370\r
371 if (Ext.isString(config)) {\r
372 config = {\r
373 action : config\r
374 };\r
375 }\r
376\r
377 method = config.action;\r
378\r
379 if (!config.before) {\r
380 config.before = befores[method];\r
381 }\r
382 //<debug>\r
383 else if (befores[method]) {\r
384 Ext.log.warn('You have a before method configured on a route ("' + url + '") and in the before object property also in the "' +\r
385 me.self.getName() + '" controller. Will use the before method in the route and disregard the one in the before property.');\r
386 }\r
387 //</debug>\r
388\r
389 //connect the route config to the Router\r
390 Router.connect(url, config, me);\r
391 }\r
392 }\r
393 },\r
394\r
395 isActive: function() {\r
396 return true;\r
397 },\r
398\r
399 /**\r
400 * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an\r
401 * object containing component paths mapped to a hash of listener functions.\r
402 *\r
403 * In the following example the `updateUser` function is mapped to to the `click`\r
404 * event on a button component, which is a child of the `useredit` component.\r
405 *\r
406 * Ext.define('AM.controller.Users', {\r
407 * init: function() {\r
408 * this.control({\r
409 * 'useredit button[action=save]': {\r
410 * click: this.updateUser\r
411 * }\r
412 * });\r
413 * },\r
414 * \r
415 * updateUser: function(button) {\r
416 * console.log('clicked the Save button');\r
417 * }\r
418 * });\r
419 *\r
420 * Or alternatively one call `control` with two arguments:\r
421 *\r
422 * this.control('useredit button[action=save]', {\r
423 * click: this.updateUser\r
424 * });\r
425 *\r
426 * See {@link Ext.ComponentQuery} for more information on component selectors.\r
427 *\r
428 * @param {String/Object} selectors If a String, the second argument is used as the\r
429 * listeners, otherwise an object of selectors -> listeners is assumed\r
430 * @param {Object} [listeners] Config for listeners.\r
431 */\r
432 control: function(selectors, listeners, controller) {\r
433 var me = this,\r
434 ctrl = controller,\r
435 obj;\r
436\r
437 if (Ext.isString(selectors)) {\r
438 obj = {};\r
439 obj[selectors] = listeners;\r
440 }\r
441 else {\r
442 obj = selectors;\r
443 ctrl = listeners;\r
444 }\r
445\r
446 me.eventbus.control(obj, ctrl || me);\r
447 },\r
448\r
449 /**\r
450 * Adds listeners to different event sources (also called "event domains"). The\r
451 * primary event domain is that of components, but there are also other event domains:\r
452 * {@link Ext.app.domain.Global Global} domain that intercepts events fired from\r
453 * {@link Ext.GlobalEvents} Observable instance, {@link Ext.app.domain.Controller Controller}\r
454 * domain can be used to listen to events fired by other Controllers,\r
455 * {@link Ext.app.domain.Store Store} domain gives access to Store events, and\r
456 * {@link Ext.app.domain.Direct Direct} domain can be used with Ext Direct Providers\r
457 * to listen to their events.\r
458 * \r
459 * To listen to "bar" events fired by a controller with id="foo":\r
460 *\r
461 * Ext.define('AM.controller.Users', {\r
462 * init: function() {\r
463 * this.listen({\r
464 * controller: {\r
465 * '#foo': {\r
466 * bar: this.onFooBar\r
467 * }\r
468 * }\r
469 * });\r
470 * },\r
471 * ...\r
472 * });\r
473 * \r
474 * To listen to "bar" events fired by any controller, and "baz" events\r
475 * fired by Store with storeId="baz":\r
476 *\r
477 * Ext.define('AM.controller.Users', {\r
478 * init: function() {\r
479 * this.listen({\r
480 * controller: {\r
481 * '*': {\r
482 * bar: this.onAnyControllerBar\r
483 * }\r
484 * },\r
485 * store: {\r
486 * '#baz': {\r
487 * baz: this.onStoreBaz\r
488 * }\r
489 * }\r
490 * });\r
491 * },\r
492 * ...\r
493 * });\r
494 *\r
495 * To listen to "idle" events fired by {@link Ext.GlobalEvents} when other event\r
496 * processing is complete and Ext JS is about to return control to the browser:\r
497 *\r
498 * Ext.define('AM.controller.Users', {\r
499 * init: function() {\r
500 * this.listen({\r
501 * global: { // Global events are always fired\r
502 * idle: this.onIdle // from the same object, so there\r
503 * } // are no selectors\r
504 * });\r
505 * }\r
506 * });\r
507 * \r
508 * As this relates to components, the following example:\r
509 *\r
510 * Ext.define('AM.controller.Users', {\r
511 * init: function() {\r
512 * this.listen({\r
513 * component: {\r
514 * 'useredit button[action=save]': {\r
515 * click: this.updateUser\r
516 * }\r
517 * }\r
518 * });\r
519 * },\r
520 * ...\r
521 * });\r
522 * \r
523 * Is equivalent to:\r
524 *\r
525 * Ext.define('AM.controller.Users', {\r
526 * init: function() {\r
527 * this.control({\r
528 * 'useredit button[action=save]': {\r
529 * click: this.updateUser\r
530 * }\r
531 * });\r
532 * },\r
533 * ...\r
534 * });\r
535 *\r
536 * Of course, these can all be combined in a single call and used instead of\r
537 * `control`, like so:\r
538 *\r
539 * Ext.define('AM.controller.Users', {\r
540 * init: function() {\r
541 * this.listen({\r
542 * global: {\r
543 * idle: this.onIdle\r
544 * },\r
545 * controller: {\r
546 * '*': {\r
547 * foobar: this.onAnyFooBar\r
548 * },\r
549 * '#foo': {\r
550 * bar: this.onFooBar\r
551 * }\r
552 * },\r
553 * component: {\r
554 * 'useredit button[action=save]': {\r
555 * click: this.updateUser\r
556 * }\r
557 * },\r
558 * store: {\r
559 * '#qux': {\r
560 * load: this.onQuxLoad\r
561 * }\r
562 * }\r
563 * });\r
564 * },\r
565 * ...\r
566 * });\r
567 *\r
568 * @param {Object} to Config object containing domains, selectors and listeners.\r
569 * @param {Ext.app.Controller} [controller] The controller to add the listeners to. Defaults to the current controller.\r
570 */\r
571 listen: function (to, controller) {\r
572 this.eventbus.listen(to, controller || this);\r
573 },\r
574 \r
575 destroy: function() {\r
576 var me = this,\r
577 bus = me.eventbus;\r
578\r
579 Ext.app.route.Router.disconnectAll(me);\r
580\r
581 if (bus) {\r
582 bus.unlisten(me);\r
583 me.eventbus = null;\r
584 }\r
585 me.callParent();\r
586 },\r
587\r
588 /**\r
589 * Update the hash. By default, it will not execute the routes if the current token and the\r
590 * token passed are the same.\r
591 * \r
592 * @param {String/Ext.data.Model} token The token to redirect to. Can be either a String\r
593 * or a {@link Ext.data.Model Model} instance - if a Model instance is passed it will\r
594 * internally be converted into a String token by calling the Model's\r
595 * {@link Ext.data.Model#toUrl toUrl} function.\r
596 *\r
597 * @param {Boolean} force Force the update of the hash regardless of the current token.\r
598 * \r
599 * @return {Boolean} Will return `true` if the token was updated.\r
600 */\r
601 redirectTo: function(token, force) {\r
602 if (token.isModel) {\r
603 token = token.toUrl();\r
604 }\r
605 if (!force) {\r
606 var currentToken = Ext.util.History.getToken();\r
607\r
608 if (currentToken === token) {\r
609 return false;\r
610 }\r
611 } else {\r
612 Ext.app.route.Router.onStateChange(token);\r
613 }\r
614 Ext.util.History.add(token);\r
615\r
616 return true;\r
617 }\r
618});\r