/** * Controllers are the glue that binds an application together. That said, their main * purpose is to listen for events (usually from views) and take some action. Here's how * we might create a Controller to manage Users: * * Ext.define('MyApp.controller.Users', { * extend: 'Ext.app.Controller', * * init: function() { * console.log('Initialized Users! This happens before ' + * 'the Application launch() function is called'); * } * }); * * The init function is a special method that is called when your application boots. It is * called before the {@link Ext.app.Application Application}'s launch function is executed. * This creates an area you can run code prior to Viewport creation. * * The controller's {@link #method-control} function * makes it easy to listen to events on your view classes and take some action with a * handler function. Let's update our Users controller to tell us when the panel is * rendered: * * Ext.define('MyApp.controller.Users', { * extend: 'Ext.app.Controller', * * control: { * 'viewport > panel': { * render: 'onPanelRendered' * } * } * * onPanelRendered: function() { * console.log('The panel was rendered'); * } * }); * * The {@link Ext.app.BaseController#method-control control method} has now set up * listeners on views in our application. The control method uses the ComponentQuery * engine to quickly and easily get references to components on the page. If you are not * familiar with ComponentQuery yet, be sure to check out the * {@link Ext.ComponentQuery documentation}. In brief, it allows us to pass a * CSS-like selector that will find every matching component on the page. * * In our init function above, we supplied 'viewport > panel', which translates to "find me * every Panel that is a direct child of a Viewport". We then supplied an object that maps * event names (just 'render' in this case) to handler functions. In short, whenever * a component that matches our selector fires a 'render' event, our * onPanelRendered function is called. * * ## Event domains * * In Ext JS 4.2, we introduced the concept of event domains. In terms of MVC, an event * domain is one or more base classes that fire events to which a Controller wants to * listen. Besides Component event domain that encompass {@link Ext.Component}-descended * Views, Controllers now can listen to events from data Stores, Ext Direct Providers, * other Controllers, and Ext.GlobalEvents. This feature provides a way to communicate * between parts of the whole application without the need to bind controllers together * tightly, and allows to develop and test application parts in isolation. * * See usage examples in {@link #method-listen} method documentation. * * ## Using refs * * One of the most useful parts of Controllers is the ref system. These use * {@link Ext.ComponentQuery} to make it really easy to get references to Views on your * page. Let's look at an example of this now: * * Ext.define('MyApp.controller.Users', { * extend: 'Ext.app.Controller', * * refs: [{ * ref: 'list', * selector: 'grid' * }], * * control: { * 'button': { * click: 'refreshGrid' * } * }, * * refreshGrid: function() { * this.getList().store.load(); * } * }); * * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which * contains a single button to refresh the Grid when clicked. In our refs array, we set up * a reference to the grid. There are two parts to this - the 'selector', which is a * {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and * assigns it to the reference 'list'. * * By giving the reference a name, we get a number of things for free. The first is the * getList function that we use in the refreshGrid method above. This is generated * automatically by the Controller based on the name of our ref, which was capitalized and * prepended with get to go from 'list' to 'getList'. * * The way this works is that the first time getList is called by your code, the * ComponentQuery selector is run and the first component that matches the selector * ('grid' in this case) will be returned. All future calls to getList will use a cached * reference to that grid. Usually it is advised to use a specific ComponentQuery selector * that will only match a single View in your application (in the case above our selector * will match any grid on the page). * * Bringing it all together, we configure control * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid * function (again, this will match any button on the page so we advise a more specific * selector than just 'button', but have left it this way for simplicity). When the button * is clicked we use out getList function to refresh the grid. * * You can create any number of refs and control any number of components this way, simply * adding more functions to your Controller as you go. For an example of real-world usage * of Controllers see the Feed Viewer example in the examples/app/feed-viewer folder in * the SDK download. * * ## Generated getter methods * * Refs aren't the only thing that generate convenient getter methods. Controllers often * have to deal with Models and Stores so the framework offers a couple of easy ways to * get access to those too. Let's look at another example: * * Ext.define('MyApp.controller.Users', { * extend: 'Ext.app.Controller', * * models: ['User'], * stores: ['AllUsers', 'AdminUsers'], * * init: function() { * var User, allUsers, ed; * * User = this.getUserModel(); * allUsers = this.getAllUsersStore(); * * ed = new User({ name: 'Ed' }); * allUsers.add(ed); * } * }); * * By specifying Models and Stores that the Controller cares about, it again dynamically * loads them from the appropriate locations (app/model/User.js, app/store/AllUsers.js and * app/store/AdminUsers.js in this case) and creates getter functions for them all. The * example above will create a new User model instance and add it to the AllUsers Store. * Of course, you could do anything in this function but in this case we just did * something simple to demonstrate the functionality. * * ## Further Reading * * For more information about writing Ext JS 5 applications, please see the * [Application Architecture](../../../application_architecture/application_architecture.html). * Also see the {@link Ext.app.Application} documentation. */Ext.define('Ext.app.Controller', { extend: 'Ext.app.BaseController', requires: [ 'Ext.app.Util', 'Ext.data.StoreManager', 'Ext.ComponentManager', 'Ext.app.domain.Component', 'Ext.app.domain.Store' ], statics: { strings: { model: { getter: 'getModel', upper: 'Model' }, view: { getter: 'getView', upper: 'View' }, controller: { getter: 'getController', upper: 'Controller' }, store: { getter: 'getStore', upper: 'Store' }, profile: { getter: 'getProfile', upper: 'Profiles' } }, controllerRegex: /^(.*)\.controller\./, profileRegex: /^(.*)\.profile\./, createGetter: function(baseGetter, name) { return function() { return this[baseGetter](name); }; }, getGetterName: function(name, kindUpper) { var fn = 'get', parts = name.split('.'), numParts = parts.length, index; // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc. for (index = 0; index < numParts; index++) { fn += Ext.String.capitalize(parts[index]); } fn += kindUpper; return fn; }, resolveNamespace: function(cls, data) { var Controller = Ext.app.Controller, namespaceRe, className, namespace, match; namespaceRe = cls.prototype.isProfile ? Controller.profileRegex : Controller.controllerRegex; /* * Namespace resolution is tricky business: we should know what namespace * this Controller descendant belongs to, or model/store/view dependency * resolution will be either ambiguous or plainly not possible. To avoid * guessing games we try to look for a forward hint ($namespace) that * Application class sets when its onClassExtended gets processed; if that * fails we try to deduce namespace from class name. * * Note that for Ext.app.Application, Controller.onClassExtended gets executed * *before* Application.onClassExtended so we have to delay namespace handling * until after Application.onClassExtended kicks in, hence it is done in this hook. */ className = Ext.getClassName(cls); namespace = data.$namespace || data.namespace || Ext.app.getNamespace(className) || ((match = namespaceRe.exec(className)) && match[1]); //<debug> if (!namespace) { Ext.log.warn("Missing namespace for " + className + ", please define it " + "in namespaces property of your Application class."); } //</debug> return namespace; }, /** * This method is called like so: * * Ext.app.Controller.processDependencies(proto, requiresArray, 'MyApp', 'model', [ * 'User', * 'Item', * '[email protected]', * '[email protected]' * ]); * * Required dependencies are added to requiresArray. * * @private */ processDependencies: function(cls, requires, namespace, kind, names, profileName) { if (!names || !names.length) { return; } /* eslint-disable-next-line vars-on-top */ var me = this, strings = me.strings[kind], o, absoluteName, shortName, name, j, subLn, getterName, getter; if (!Ext.isArray(names)) { names = [names]; } for (j = 0, subLn = names.length; j < subLn; j++) { name = names[j]; o = me.getFullName(name, kind, namespace, profileName); // Update the name in the array to be the absolute name names[j] = absoluteName = o.absoluteName; shortName = o.shortName; requires.push(absoluteName); getterName = me.getGetterName(shortName, strings.upper); if (!cls[getterName]) { cls[getterName] = getter = me.createGetter(strings.getter, name); } //<debug> else if (getterName === 'getMainView') { Ext.log.warn('Cannot have a view named \'Main\' - getter conflicts ' + 'with mainView config.'); } //</debug> // Application class will init the controller getters if (getter && kind !== 'controller') { // This marker allows the constructor to easily/cheaply identify the // generated getter methods since they all need to be called to get // things initialized. We use a property name that deliberately does // not work with dot-access to reduce any chance of collision. getter['Ext.app.getter'] = true; } } }, getFullName: function(name, kind, namespace, profileName) { var shortName = name, sep, absoluteName; if ((sep = name.indexOf('@')) > 0) { // The unambiguous syntax is [email protected] (or "space.Model@Name") // which contains both the short name ("Model" or "space.Model") and // the full name (Name.space.Model). // shortName = name.substring(0, sep); // "Model" absoluteName = name.substring(sep + 1) + '.' + shortName; // ex: "Name.space.Model" } // Deciding if a class name must be qualified: // // 1 - if the name doesn't contain a dot, we must qualify it // // 2 - the name may be a qualified name of a known class, but: // // 2.1 - in runtime, the loader may not know the class - specially in // production - so we must check the class manager // // 2.2 - in build time, the class manager may not know the class, but // the loader does, so we check the second one (the loader check // assures it's really a class, and not a namespace, so we can // have 'Books.controller.Books', and requesting a controller // called Books will not be underqualified) // else if (name.indexOf('.') > 0 && (Ext.ClassManager.isCreated(name) || this.hasRegisteredPrefix(name))) { absoluteName = name; shortName = name.replace(namespace + '.' + kind + '.', ''); } else { //<debug> if (!namespace) { Ext.log.warn("Cannot find namespace for " + kind + " " + name + ", " + "assuming it is fully qualified class name"); } //</debug> if (namespace) { absoluteName = namespace + '.' + kind + '.' + (profileName ? profileName + '.' + name : name); shortName = name; } else { absoluteName = name; } } return { absoluteName: absoluteName, shortName: shortName }; }, hasRegisteredPrefix: function(className) { var inventory = Ext.ClassManager, prefix = inventory.getPrefix(className); // It's a class if className is not equal to any known namespace return prefix && prefix !== className; } }, // @cmd-auto-dependency {aliasPrefix : "model.", mvc : true, blame: "all"} /** * @cfg {String/String[]} models * Array of models to require from AppName.model namespace. For example: * * Ext.define("MyApp.controller.Foo", { * extend: "Ext.app.Controller", * models: ['User', 'Vehicle'] * }); * * This is equivalent to: * * Ext.define("MyApp.controller.Foo", { * extend: "Ext.app.Controller", * requires: ['MyApp.model.User', 'MyApp.model.Vehicle'], * * getUserModel: function() { * return this.getModel("User"); * }, * * getVehicleModel: function() { * return this.getModel("Vehicle"); * } * }); * * **Note:** If the model has a different namespace than that of the * application you will need to specify the full class name as well as define a path * in the {@link Ext.Loader#cfg-paths Loader's paths} config or * {@link Ext.Loader#method-setPath setPath} method. */ models: null, // @cmd-auto-dependency {aliasPrefix: "view.", mvc: true, blame: "all"} /** * @cfg {String/String[]} views * Array of views to require from AppName.view namespace and to generate getter methods for. * For example: * * Ext.define("MyApp.controller.Foo", { * extend: "Ext.app.Controller", * views: ['List', 'Detail'] * }); * * This is equivalent to: * * Ext.define("MyApp.controller.Foo", { * extend: "Ext.app.Controller", * requires: ['MyApp.view.List', 'MyApp.view.Detail'], * * getListView: function() { * return this.getView("List"); * }, * * getDetailView: function() { * return this.getView("Detail"); * } * }); * * **Note:** If the view has a different namespace than that of the * application you will need to specify the full class name as well as define a path * in the {@link Ext.Loader#cfg-paths Loader's paths} config or * {@link Ext.Loader#method-setPath setPath} method. */ views: null, // @cmd-auto-dependency {aliasPrefix: "store.", mvc: true, blame: "all"} /** * @cfg {String/String[]} stores * Array of stores to require from AppName.store namespace and to generate getter methods for. * For example: * * Ext.define("MyApp.controller.Foo", { * extend: "Ext.app.Controller", * stores: ['Users', 'Vehicles'] * }); * * This is equivalent to: * * Ext.define("MyApp.controller.Foo", { * extend: "Ext.app.Controller", * * requires: [ * 'MyApp.store.Users', * 'MyApp.store.Vehicles' * ] * * getUsersStore: function() { * return this.getStore("Users"); * }, * * getVehiclesStore: function() { * return this.getStore("Vehicles"); * } * }); * * **Note:** If the store has a different namespace than that of the * application you will need to specify the full class name as well as define a path * in the {@link Ext.Loader#cfg-paths Loader's paths} config or * {@link Ext.Loader#method-setPath setPath} method. */ stores: null, // @cmd-auto-dependency {aliasPrefix: "controller.", mvc: true, blame: "all"} controllers: null, config: { /** * @cfg {Ext.app.Application} application * The {@link Ext.app.Application} for this controller accessible via the * getApplication method. * @accessor * @readonly */ application: null, /** * @cfg {Object/Object[]} refs * @accessor * * The refs config creates a getter method on the controller that internally * uses Ext.ComponentQuery to fetch the component instance using the configured * selector. The following example will add the `getList` method to * the controller and will return the first component in the application * hierarchy with an xtype of "grid". By default, *undefined* will be returned * when the query does not locate the target component. * * Ext.define('MyApp.controller.Foo', { * extend: 'Ext.app.Controller', * * refs: [{ * ref: 'list', * selector: 'grid' * }] * }); * * The following fields may be used in the ref definition: * * - `ref` - name of the reference. * - `selector` - Ext.ComponentQuery selector to access the component. * - `autoCreate` - True to create the component automatically if not found on * page. * - `forceCreate` - True to force the creation of the component every time * reference is accessed (when `get<REFNAME>` is called). * - `xtype` - Used to create the component by its xtype with `autoCreate` or * `forceCreate`. If you don't provide `xtype`, an Ext.Component instance will * be created. * * The following example will create a `getList` and `getUser` method on the * controller. * * Ext.define('MyApp.controller.Foo', { * extend: 'Ext.app.Controller', * * refs: [{ * list: 'grid', * user: { * autoCreate: true, * selector: 'form', * xtype: 'form' * } * }] * }); */ refs: null, active: true, /** * @private */ moduleClassName: null }, onClassExtended: function(cls, data, hooks) { var onBeforeClassCreated = hooks.onBeforeCreated; hooks.onBeforeCreated = function(cls, data) { var Controller = Ext.app.Controller, requires = [], namespace, proto; proto = cls.prototype; namespace = Controller.resolveNamespace(cls, data); if (namespace) { proto.$namespace = namespace; } Controller.processDependencies(proto, requires, namespace, 'model', data.models); Controller.processDependencies(proto, requires, namespace, 'view', data.views); Controller.processDependencies(proto, requires, namespace, 'store', data.stores); Controller.processDependencies(proto, requires, namespace, 'controller', data.controllers); Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this)); }; }, /** * Creates new Controller. * * @param {Object} [config] Configuration object. */ constructor: function(config) { this.initAutoGetters(); this.callParent([config]); }, /** * @private * Takes either an object and transforms it into an array. The following are valid * refs values: * * refs: { * myComponent: 'container' * } * * refs: { * myComponent: { * selector: 'container' * } * } * * refs: [ * { * ref: 'myComponent', * selector: 'container' * } * ] * * @param {Array|Object} refs The refs to normalize * @return {Array} The normalized array of refs */ normalizeRefs: function(refs) { var me = this, newRefs = []; if (refs) { if (Ext.isObject(refs)) { Ext.Object.each(refs, function(key, value) { if (Ext.isString(value)) { value = { selector: value }; } value.ref = key; newRefs.push(value); }); } else if (Ext.isArray(refs)) { newRefs = Ext.Array.merge(newRefs, refs); } } refs = me.refs; if (refs) { me.refs = null; refs = me.normalizeRefs(refs); if (refs) { newRefs = Ext.Array.merge(newRefs, refs); } } return newRefs; }, /** * Returns a map of reference names to selectors * @private */ getRefMap: function() { var me = this, refMap = me._refMap, refs, ref, ln, i; if (!refMap) { refs = me.getRefs(); refMap = me._refMap = {}; if (refs) { for (i = 0, ln = refs.length; i < ln; i++) { ref = refs[i]; refMap[ref.ref] = ref.selector; } } } return refMap; }, applyId: function(id) { return id || Ext.app.Controller.getFullName(this.$className, 'controller', this.$namespace).shortName; }, applyRefs: function(refs) { return this.normalizeRefs(Ext.clone(refs)); }, /** * @param {Object} refs The refs to pass to the {@link #ref} method. * @private */ updateRefs: function(refs) { if (refs) { this.ref(refs); } }, initAutoGetters: function() { var proto = this.self.prototype, prop, fn; for (prop in proto) { fn = proto[prop]; // Look for the marker placed on the getters by processDependencies so that // we can know what to call cheaply: if (fn && fn['Ext.app.getter']) { fn.call(this); } } }, doInit: function(app) { var me = this; if (!me._initialized) { me.init(app); me._initialized = true; } }, finishInit: function(app) { var me = this, controllers = me.controllers, controller, i, l; if (me._initialized && controllers && controllers.length) { for (i = 0, l = controllers.length; i < l; i++) { controller = me.getController(controllers[i]); controller.finishInit(app); } } }, /** * @method * * A template method that is called when your application boots. It is called before the * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point * to run any code before your Viewport is created. * * @param {Ext.app.Application} application * * @template */ init: Ext.emptyFn, /** * @method * * A template method like {@link #init}, but called after the viewport is created. * This is called after the {@link Ext.app.Application#launch launch} method of Application * is executed. * * @param {Ext.app.Application} application * * @template */ onLaunch: Ext.emptyFn, /** * Allow the controller to resume receiving events from the event bus. * Routes will also be able to begin firing on this controller. * Also see {@link #deactivate}. */ activate: function() { this.setActive(true); }, /** * Prevent this controller from receiving events from the event bus. * Routes will also not be triggered on inactive controllers unless * the {@link Ext.route.Route#allowInactive} flag is set. * Also see {@link #activate}. */ deactivate: function() { this.setActive(false); }, /** * Checks if this controller is active. See {@link #activate} & * {@link #deactivate}. * @return {Boolean} `true` if this controller is active. */ isActive: function() { return this.getActive(); }, ref: function(refs) { var me = this, i = 0, length = refs.length, info, ref, fn; refs = Ext.Array.from(refs); me.references = me.references || []; for (; i < length; i++) { info = refs[i]; ref = info.ref; fn = 'get' + Ext.String.capitalize(ref); if (!me[fn]) { me[fn] = Ext.Function.pass(me.getRef, [ref, info], me); } me.references.push(ref.toLowerCase()); } }, /** * Registers one or more {@link #refs references}. * * @param {Object/Object[]} refs */ addRef: function(refs) { this.ref(refs); }, getRef: function(ref, info, config) { var me = this, refCache = me.refCache || (me.refCache = {}), cached = refCache[ref]; info = info || {}; config = config || {}; Ext.apply(info, config); if (info.forceCreate) { return Ext.ComponentManager.create(info, 'component'); } if (!cached) { if (info.selector) { refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0]; } if (!cached && info.autoCreate) { refCache[ref] = cached = Ext.ComponentManager.create(info, 'component'); } if (cached) { cached.on('destroy', function() { refCache[ref] = null; }); } } return cached; }, /** * Returns `true` if a {@link #refs reference} is registered. * * @param {String} ref The name of the ref to check for. * @return {Boolean} */ hasRef: function(ref) { var references = this.references; return references && Ext.Array.indexOf(references, ref.toLowerCase()) !== -1; }, /** * Returns instance of a {@link Ext.app.Controller Controller} with the given id. * When controller doesn't exist yet, it's created. Note that this method depends * on Application instance and will return undefined when Application is not * accessible. The only exception is when this Controller instance's id is requested; * in that case we always return the instance even if Application is no available. * * @param {String} id * * @return {Ext.app.Controller} controller instance or undefined. */ getController: function(id) { var app = this.getApplication(); if (id === this.getId()) { return this; } return app && app.getController(id); }, /** * Returns instance of a {@link Ext.data.Store Store} with the given name. * When store doesn't exist yet, it's created. * * @param {String} name * * @return {Ext.data.Store} a store instance. */ getStore: function(name) { var storeId, store; storeId = (name.indexOf('@') === -1) ? name : name.split('@')[0]; store = Ext.StoreManager.get(storeId); if (!store) { name = Ext.app.Controller.getFullName(name, 'store', this.$namespace); if (name) { store = Ext.create(name.absoluteName, { // Use id here. If the store has a configured storeId, // that will take precedence id: storeId }); } } return store; }, /** * Returns a {@link Ext.data.Model Model} class with the given name. * * @param {String} modelName * @return {Ext.Class} A class ultimately derived from `Ext.data.Model`. */ getModel: function(modelName) { var name = Ext.app.Controller.getFullName(modelName, 'model', this.$namespace), ret = Ext.ClassManager.get(name.absoluteName); if (!ret) { ret = Ext.data.schema.Schema.lookupEntity(modelName); } return ret; }, /** * Returns instance of a {@link Ext.app.Profile Profile} with the given name. * * @param {String} name * * @return {String} a profile instance. */ getProfile: function(name) { name = Ext.app.Controller.getFullName(name, 'profile', this.$namespace); return name; }, /** * Returns a View class with the given name. To create an instance of the view, * you can use it like it's used by Application to create the Viewport: * * this.getView('Viewport').create(); * * @param {String} view * * @return {Ext.Base} a view class. */ getView: function(view) { var name = Ext.app.Controller.getFullName(view, 'view', this.$namespace); return name && Ext.ClassManager.get(name.absoluteName); }, /** * @method destroy * @inheritdoc * @param destroyRefs (private) * @param fromApp (private) */ destroy: function(destroyRefs, fromApp) { var me = this, app = me.application, refCache, ref; if (!fromApp && app) { app.unregister(me); } me.application = null; if (destroyRefs) { // Possible destroy stores here too? refCache = me.refCache; for (ref in refCache) { if (refCache.hasOwnProperty(ref)) { Ext.destroy(refCache[ref]); } } } me.callParent(); }});