/** * @protected * @class Ext.app.BaseController * Base class for Controllers. * */Ext.define('Ext.app.BaseController', { requires: [ 'Ext.app.EventBus', 'Ext.app.domain.Global' ], uses: [ 'Ext.app.domain.Controller' ], mixins: ['Ext.mixin.Observable'], isController: true, config : { /** * @cfg {String} id The id of this controller. You can use this id when dispatching. * * For an example of dispatching, see the examples under the * {@link Ext.app.Controller#cfg-listen listen} config. * * If an id is not explicitly set, it will default to the controller's full classname. * * @accessor */ id: null, /** * @cfg {Object} control * @accessor * * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an * object containing component paths mapped to a hash of listener functions. * The function value may also be a string matching the name of a method on the * controller. * * In the following example the `updateUser` function is mapped to to the `click` * event on a button component, which is a child of the `useredit` component. * * Ext.define('MyApp.controller.Users', { * extend: 'Ext.app.Controller', * * control: { * 'useredit button[action=save]': { * click: 'updateUser' * } * }, * * updateUser: function(button) { * console.log('clicked the Save button'); * } * }); * * The method you pass to the listener will automatically be resolved on the controller. * In this case, the `updateUser` method that will get executed on the `button` `click` * event will resolve to the `updateUser` method on the controller, * * See {@link Ext.ComponentQuery} for more information on component selectors. */ control: null, /** * @cfg {Object} listen * @accessor * * Adds listeners to different event sources (also called "event domains"). The * primary event domain is that of components, but there are also other event domains: * {@link Ext.app.domain.Global Global} domain that intercepts events fired from * {@link Ext.GlobalEvents} Observable instance, * {@link Ext.app.domain.Controller Controller} domain can be used to listen to events * fired by other Controllers, {@link Ext.app.domain.Store Store} domain gives access to * Store events, and {@link Ext.app.domain.Direct Direct} domain can be used with * Ext.Direct Providers to listen to their events. * * To listen to "bar" events fired by a controller with id="foo": * * Ext.define('AM.controller.Users', { * extend: 'Ext.app.Controller', * * listen: { * controller: { * '#foo': { * bar: 'onFooBar' * } * } * } * }); * * To listen to "bar" events fired by any controller, and "baz" events * fired by Store with storeId="baz": * * Ext.define('AM.controller.Users', { * extend: 'Ext.app.Controller', * * listen: { * controller: { * '*': { * bar: 'onAnyControllerBar' * } * }, * store: { * '#baz': { * baz: 'onStoreBaz' * } * } * } * }); * * To listen to "idle" events fired by {@link Ext.GlobalEvents} when other event * processing is complete and Ext JS is about to return control to the browser: * * Ext.define('AM.controller.Users', { * extend: 'Ext.app.Controller', * * listen: { * global: { // Global events are always fired * idle: 'onIdle' // from the same object, so there * } // are no selectors * } * }); * * As this relates to components, the following example: * * Ext.define('AM.controller.Users', { * extend: 'Ext.app.Controller', * * listen: { * component: { * 'useredit button[action=save]': { * click: 'updateUser' * } * } * } * }); * * Is equivalent to: * * Ext.define('AM.controller.Users', { * extend: 'Ext.app.Controller', * * control: { * 'useredit button[action=save]': { * click: 'updateUser' * } * } * }); * * Of course, these can all be combined in a single call and used instead of * `control`, like so: * * Ext.define('AM.controller.Users', { * extend: 'Ext.app.Controller', * * listen: { * global: { * idle: 'onIdle' * }, * controller: { * '*': { * foobar: 'onAnyFooBar' * }, * '#foo': { * bar: 'onFooBar' * } * }, * component: { * 'useredit button[action=save]': { * click: 'updateUser' * } * }, * store: { * '#qux': { * load: 'onQuxLoad' * } * } * } * }); */ listen: null, /** * @cfg {Object} routes * @accessor * * An object of routes to handle hash changes. A route can be defined in a simple way: * * routes : { * 'foo/bar' : 'handleFoo', * 'user/:id' : 'showUser' * } * * Where the property is the hash (which can accept a parameter defined by a colon) and the value * is the method on the controller to execute. The parameters will get sent in the action method. * * At the application level, you can define a event that will be executed when no matching * routes are found. * * Ext.application({ * name: 'MyApp', * listen: { * controller: { * '#': { * unmatchedroute: 'onUnmatchedRoute' * } * } * }, * * onUnmatchedRoute: function(hash) { * console.log('Unmatched', hash); * // Do something... * } * }); * * There is also a complex means of defining a route where you can use a before action and even * specify your own RegEx for the parameter: * * routes : { * 'foo/bar' : { * action : 'handleFoo', * before : 'beforeHandleFoo' * }, * 'user/:id' : { * action : 'showUser', * before : 'beforeShowUser', * conditions : { * ':id' : '([0-9]+)' * } * } * } * * This will only match if the `id` parameter is a number. * * The before action allows you to cancel an action. Every before action will get passed an `action` argument with * a `resume` and `stop` methods as the last argument of the method and you *MUST* execute either method: * * beforeHandleFoo : function(action) { * //some logic here * * //this will allow the handleFoo action to be executed * action.resume(); * }, * handleFoo : function() { * //will get executed due to true being passed in callback in beforeHandleFoo * }, * beforeShowUser : function(id, action) { * //allows for async process like an Ajax * Ext.Ajax.request({ * url : 'foo.php', * success : function() { * //will not allow the showUser method to be executed but will continue other queued actions. * action.stop(); * }, * failure : function() { * //will not allow the showUser method to be executed and will not allow other queued actions to be executed. * action.stop(true); * } * }); * }, * showUser : function(id) { * //will not get executed due to false being passed in callback in beforeShowUser * } * * You *MUST* execute the `resume` or `stop` method on the `action` argument. Executing `action.resume();` will continue * the action, `action.stop();` will not allow the action to resume but will allow other queued actions to resume, * `action.stop(true);` will not allow the action and any other queued actions to resume. * * The default RegEx that will be used is `([%a-zA-Z0-9\\-\\_\\s,]+)` but you can specify any * that may suit what you need to accomplish. An example of an advanced condition may be to make * a parameter optional and case-insensitive: * * routes : { * 'user:id' : { * action : 'showUser', * before : 'beforeShowUser', * conditions : { * ':id' : '(?:(?:\/){1}([%a-z0-9_,\s\-]+))?' * } * } * } */ routes : null, before : null }, /** * Creates new Controller. * * @param {Object} [config] Configuration object. */ constructor: function(config) { var me = this; // In versions prior to 5.1, this constructor used to call the Ext.util.Observable // constructor (which applied the config properties directly to the instance) // AND it used to call initConfig as well. Since the constructor of // Ext.mixin.Observable calls initConfig, but does not apply the properties to // the instance, we do that here for backward compatibility. Ext.apply(me, config); // The control and listen properties are also methods so we need to delete them // from the instance after applying the config object. delete me.control; delete me.listen; me.eventbus = Ext.app.EventBus; //need to have eventbus property set before we initialize the config me.mixins.observable.constructor.call(me, config); // Assuming we haven't set this in updateControl or updateListen, force it here me.ensureId(); }, applyListen: function(listen) { if (Ext.isObject(listen)) { listen = Ext.clone(listen); } return listen; }, applyControl: function(control) { if (Ext.isObject(control)) { control = Ext.clone(control); } return control; }, /** * @param {Object} control The object to pass to the {@link #method-control} method * @private */ updateControl: function(control) { this.ensureId(); if (control) { this.control(control); } }, /** * @param {Object} listen The object to pass to the {@link #method-listen} method * @private */ updateListen: function(listen) { this.ensureId(); if (listen) { this.listen(listen); } }, /** * @param {Object} routes The routes to connect to the {@link Ext.app.route.Router} * @private */ updateRoutes : function(routes) { if (routes) { var me = this, befores = me.getBefore() || {}, Router = Ext.app.route.Router, url, config, method; for (url in routes) { config = routes[url]; if (Ext.isString(config)) { config = { action : config }; } method = config.action; if (!config.before) { config.before = befores[method]; } //<debug> else if (befores[method]) { Ext.log.warn('You have a before method configured on a route ("' + url + '") and in the before object property also in the "' + me.self.getName() + '" controller. Will use the before method in the route and disregard the one in the before property.'); } //</debug> //connect the route config to the Router Router.connect(url, config, me); } } }, isActive: function() { return true; }, /** * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an * object containing component paths mapped to a hash of listener functions. * * In the following example the `updateUser` function is mapped to to the `click` * event on a button component, which is a child of the `useredit` component. * * Ext.define('AM.controller.Users', { * init: function() { * this.control({ * 'useredit button[action=save]': { * click: this.updateUser * } * }); * }, * * updateUser: function(button) { * console.log('clicked the Save button'); * } * }); * * Or alternatively one call `control` with two arguments: * * this.control('useredit button[action=save]', { * click: this.updateUser * }); * * See {@link Ext.ComponentQuery} for more information on component selectors. * * @param {String/Object} selectors If a String, the second argument is used as the * listeners, otherwise an object of selectors -> listeners is assumed * @param {Object} [listeners] Config for listeners. */ control: function(selectors, listeners, controller) { var me = this, ctrl = controller, obj; if (Ext.isString(selectors)) { obj = {}; obj[selectors] = listeners; } else { obj = selectors; ctrl = listeners; } me.eventbus.control(obj, ctrl || me); }, /** * Adds listeners to different event sources (also called "event domains"). The * primary event domain is that of components, but there are also other event domains: * {@link Ext.app.domain.Global Global} domain that intercepts events fired from * {@link Ext.GlobalEvents} Observable instance, {@link Ext.app.domain.Controller Controller} * domain can be used to listen to events fired by other Controllers, * {@link Ext.app.domain.Store Store} domain gives access to Store events, and * {@link Ext.app.domain.Direct Direct} domain can be used with Ext.Direct Providers * to listen to their events. * * To listen to "bar" events fired by a controller with id="foo": * * Ext.define('AM.controller.Users', { * init: function() { * this.listen({ * controller: { * '#foo': { * bar: this.onFooBar * } * } * }); * }, * ... * }); * * To listen to "bar" events fired by any controller, and "baz" events * fired by Store with storeId="baz": * * Ext.define('AM.controller.Users', { * init: function() { * this.listen({ * controller: { * '*': { * bar: this.onAnyControllerBar * } * }, * store: { * '#baz': { * baz: this.onStoreBaz * } * } * }); * }, * ... * }); * * To listen to "idle" events fired by {@link Ext.GlobalEvents} when other event * processing is complete and Ext JS is about to return control to the browser: * * Ext.define('AM.controller.Users', { * init: function() { * this.listen({ * global: { // Global events are always fired * idle: this.onIdle // from the same object, so there * } // are no selectors * }); * } * }); * * As this relates to components, the following example: * * Ext.define('AM.controller.Users', { * init: function() { * this.listen({ * component: { * 'useredit button[action=save]': { * click: this.updateUser * } * } * }); * }, * ... * }); * * Is equivalent to: * * Ext.define('AM.controller.Users', { * init: function() { * this.control({ * 'useredit button[action=save]': { * click: this.updateUser * } * }); * }, * ... * }); * * Of course, these can all be combined in a single call and used instead of * `control`, like so: * * Ext.define('AM.controller.Users', { * init: function() { * this.listen({ * global: { * idle: this.onIdle * }, * controller: { * '*': { * foobar: this.onAnyFooBar * }, * '#foo': { * bar: this.onFooBar * } * }, * component: { * 'useredit button[action=save]': { * click: this.updateUser * } * }, * store: { * '#qux': { * load: this.onQuxLoad * } * } * }); * }, * ... * }); * * @param {Object} to Config object containing domains, selectors and listeners. * @param {Ext.app.Controller} [controller] The controller to add the listeners to. Defaults to the current controller. */ listen: function (to, controller) { this.eventbus.listen(to, controller || this); }, destroy: function() { var me = this, bus = me.eventbus; Ext.app.route.Router.disconnectAll(me); if (bus) { bus.unlisten(me); me.eventbus = null; } me.clearListeners(); me.callParent(); }, /** * Update the hash. By default, it will not execute the routes if the current token and the * token passed are the same. * * @param {String/Ext.data.Model} token The token to redirect to. Can be either a String * or a {@link Ext.data.Model Model} instance - if a Model instance is passed it will * internally be converted into a String token by calling the Model's * {@link Ext.data.Model#toUrl toUrl} function. * * @param {Boolean} force Force the update of the hash regardless of the current token. * * @return {Boolean} Will return `true` if the token was updated. */ redirectTo: function(token, force) { if (token.isModel) { token = token.toUrl(); } var isCurrent = Ext.util.History.getToken() === token, ret = false; if (!isCurrent) { ret = true; Ext.util.History.add(token); } else if (force) { ret = true; Ext.app.route.Router.onStateChange(token); } return ret; }});