/** * Represents an Ext JS application, which is typically a single page app using a * {@link Ext.container.Viewport Viewport}. * * An application consists of one or more Views. The behavior of a View is managed by its * corresponding {@link Ext.app.ViewController ViewController} and {@link Ext.app.ViewModel * ViewModel}. * * Global activities are coordinated by {@link Ext.app.Controller Controllers} which are * ultimately instantiated by an instance of this (or a derived) class. * * Ext.application({ * name: 'MyApp', * * // The name of the initial view to create. With the classic toolkit this class * // will gain a "viewport" plugin if it does not extend Ext.Viewport. With the * // modern toolkit, the main view will be added to the Viewport. * // * //mainView: 'Main.view.main.Main' * }); * * This does several things. First it creates a global variable called 'MyApp' - all of * your Application's classes (such as its Models, Views and Controllers) will reside under * this single namespace, which drastically lowers the chances of colliding global variables. * * The MyApp global will also have a getApplication method to get a reference to the current * application: * * var app = MyApp.getApplication(); * * # Telling Application about the rest of the app * * Because an Ext.app.Application represents an entire app, we should tell it about the other * parts of the app - namely the Models, Views and Controllers that are bundled with the * application. Let's say we have a blog management app; we might have Models and Controllers for * Posts and Comments, and Views for listing, adding and editing Posts and Comments. Here's how * we'd tell our Application about all these things: * * Ext.application({ * name: 'Blog', * * models: ['Post', 'Comment'], * * controllers: ['Posts', 'Comments'], * * launch: function() { * ... * } * }); * * Note that we didn't actually list the Views directly in the Application itself. This is because * Views are managed by Controllers, so it makes sense to keep those dependencies there. * The Application will load each of the specified Controllers using the pathing conventions * laid out in the [application architecture guide][1] * - in this case expecting the controllers to reside in app/controller/Posts.js and * app/controller/Comments.js. In turn, each Controller simply needs to list the Views it uses * and they will be automatically loaded. Here's how our Posts controller like be defined: * * Ext.define('MyApp.controller.Posts', { * extend: 'Ext.app.Controller', * views: ['posts.List', 'posts.Edit'], * * // the rest of the Controller here * }); * * Because we told our Application about our Models and Controllers, and our Controllers about * their Views, Ext JS will automatically load all of our app files for us. This means we don't * have to manually add script tags into our html files whenever we add a new class, but more * importantly it enables us to create a minimized build of our entire application using Sencha Cmd. * * # Deriving from Ext.app.Application * * Typically, applications do not derive directly from Ext.app.Application. Rather, the * configuration passed to `Ext.application` mimics what you might do in a derived class. * In some cases, however, it can be desirable to share logic by using a derived class * from `Ext.app.Application`. * * Derivation works as you would expect, but using the derived class should still be the * job of the `Ext.application` method. * * Ext.define('MyApp.Application', { * extend: 'Ext.app.Application', * name: 'MyApp', * ... * }); * * Ext.application('MyApp.Application'); * * For more information about writing Ext JS applications, please see * the [application architecture guide][1]. * [1]: ../guides/application_architecture/application_architecture.html */Ext.define('Ext.app.Application', { extend: 'Ext.app.Controller', requires: [ 'Ext.util.History', 'Ext.util.MixedCollection' ], isApplication: true, /** * @cfg {String} extend A class name to use with the `Ext.application` call. The class must * also extend {@link Ext.app.Application}. * * Ext.define('MyApp.Application', { * extend: 'Ext.app.Application', * * launch: function() { * Ext.direct.Manager.addProvider(Ext.REMOTING_API); * } * }); * * Ext.application({ * extend: 'MyApp.Application' * }); */ /** * @cfg {String/String[]} controllers * Names of {@link Ext.app.Controller controllers} that the app uses. By default, * the framework will look for the controllers in the "controller" folder within the * {@link #appFolder}. Controller classes should be named using the syntax of * "{appName}.controller.{ClassName}" with additional sub-folders under the * "controller" folder specified within the class name following "controller.". * * // by default, the following controller class would be located at: * // app/controller/Main.js * controllers: '.Main' // or 'MyApp.controller.Main' * * // while the following would be located at: * // app/controller/customer/Main.js * controllers: 'customer.Main' // or 'MyApp.controller.customer.Main' * * **Note:** If the controller 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. */ /** * @cfg {Object} scope * The scope to execute the {@link #launch} function in. Defaults to the Application instance. */ scope: undefined, /** * @cfg {String/String[]} [namespaces] * * The list of namespace prefixes used in the application to resolve dependencies * like Views and Stores: * * Ext.application({ * name: 'MyApp', * * namespaces: ['Common.code'], * * controllers: [ 'Common.code.controller.Foo', 'Bar' ] * }); * * Ext.define('Common.code.controller.Foo', { * extend: 'Ext.app.Controller', * * models: ['Foo'], // Loads Common.code.model.Foo * views: ['Bar'] // Loads Common.code.view.Bar * }); * * Ext.define('MyApp.controller.Bar', { * extend: 'Ext.app.Controller', * * models: ['Foo'], // Loads MyApp.model.Foo * views: ['Bar'] // Loads MyApp.view.Bar * }); * * You don't need to include main namespace (MyApp), it will be added to the list * automatically. */ namespaces: [], /** * @cfg {Object} paths * Additional load paths to add to Ext.Loader. * See {@link Ext.Loader#paths} config for more details. */ paths: null, /** * @cfg {String} [appFolder="app"] * The path to the directory which contains all application's classes. * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified * in the {@link #name name} config. */ // NOTE - this config has to be processed by Ext.application config: { /** * @cfg {String} name * The name of your application. This will also be the namespace for your views, controllers * models and stores. Don't use spaces or special characters in the name. **Application name * is mandatory**. */ name: '', /** * @cfg {String} appProperty * The name of a property to be assigned to the main namespace to gain a reference to * this application. Can be set to an empty value to prevent the reference from * being created * * Ext.application({ * name: 'MyApp', * appProperty: 'myProp', * * launch: function() { * console.log(MyApp.myProp === this); * } * }); */ appProperty: 'app', // @cmd-auto-dependency { aliasPrefix: "profile.", mvc: true, blame: "all" } /** * @cfg {String/String[]} profiles * Names of the profiles that the app uses. */ profiles: [], /** * @cfg {Ext.app.Profile} */ currentProfile: null, // @cmd-auto-dependency {aliasPrefix: "view.", mvc: true, blame: "all"} /** * @cfg {String/Object/Ext.Component} mainView * The application class to be used as the main viewport view for the * application. The view will be configured with the * {@link Ext.plugin.Viewport viewport plugin} to ensure the view takes up all * available space in the browser viewport. The main view will be created after * the application's {@link #init} method is called and before the * {@link #launch} method. The main view should be an application class type and * not a class from the framework. * * The main view value may be: * - string representing the full class name of the main view or the partial class name * following "AppName.view." (provided your main view class follows that convention). * - config object for the main view * - main view class instance * * Ext.define('MyApp.view.main.Main', { * extend: 'Ext.panel.Panel', * xtype: 'mainview', * title: 'Main Viewport View' * }); * * Ext.application({ * name : 'MyApp', * * mainView: 'MyApp.view.main.Main' * // mainView: 'main.Main' * // mainView: new MyApp.view.main.Main() * // mainView: { xtype: 'mainview' } * }); * * **Note:** You may also call {@link #setMainView} at runtime if you require * logic within the application's {@link #launch} method to be processed prior to * the creation of the main view. */ mainView: { $value: null, lazy: true }, /** * @cfg {String} [defaultToken=null] The default token to be used at application launch * if one is not present. Often this is set to something like `'home'`. */ defaultToken: null, /** * @cfg {String} glyphFontFamily * The glyphFontFamily to use for this application. Used as the default font-family * for all components that support a `glyph` config. */ glyphFontFamily: null, // Docs will go in subclasses quickTips: true, /** * @cfg {Object} router * A configuration object to apply onto the {@link Ext.route.Router Router}. * @since 6.5.0 */ router: null }, onClassExtended: function(cls, data, hooks) { var Controller = Ext.app.Controller, proto = cls.prototype, requires = [], onBeforeClassCreated, namespace; // Ordinary inheritance does not work here so we collect // necessary data from current class data and its superclass namespace = data.name || cls.superclass.name; if (namespace) { data.$namespace = namespace; Ext.app.addNamespaces(namespace); } if (data.namespaces) { Ext.app.addNamespaces(data.namespaces); } if (data['paths processed']) { delete data['paths processed']; } else { Ext.app.setupPaths( namespace, ('appFolder' in data) ? data.appFolder : cls.superclass.appFolder, data.paths ); } // Require all profiles Controller.processDependencies(proto, requires, namespace, 'profile', data.profiles); // This hook is used in the classic toolkit to process other configs that need to // require classes (like tooltips and viewport plugin). proto.getDependencies(cls, data, requires); // Any "requires" also have to be processed before we fire up the App instance. if (requires.length) { onBeforeClassCreated = hooks.onBeforeCreated; hooks.onBeforeCreated = function(cls, data) { var args = Ext.Array.clone(arguments); //<debug> // This hook is to allow unit tests to come in and control the // requires so we don't have to get into the internals of the Loader. // Not intended to be used for any other purpose. if (data.__handleRequires) { data.__handleRequires.call(this, requires, Ext.bind(function() { return onBeforeClassCreated.apply(this, args); }, this)); return; } //</debug> Ext.require(requires, function() { return onBeforeClassCreated.apply(this, args); }); }; } }, getDependencies: Ext.emptyFn, /** * Creates new Application. * @param {Object} [config] Config object. */ constructor: function(config) { var me = this; Ext.route.Router.application = me; me.callParent([config]); //<debug> if (Ext.isEmpty(me.getName())) { Ext.raise("[Ext.app.Application] Name property is required"); } //</debug> me.doInit(me); Ext.on('appupdate', me.onAppUpdate, me, { single: true }); //<debug> Ext.Loader.setConfig({ enabled: true }); //</debug> this.onProfilesReady(); }, applyId: function(id) { return id || this.$className; }, updateRouter: function(cfg) { if (cfg) { Ext.route.Router.setConfig(cfg); } }, /** * @method * @template * Called automatically when an update to either the Application Cache or the Local Storage * Cache is detected. * This is mainly used during production builds. * @param {Object} [updateInfo] updateInfo Update information object contains properties for * checking which cache triggered the update */ onAppUpdate: Ext.emptyFn, onProfilesReady: function() { var me = this, profiles = me.getProfiles(), length = profiles.length, current, i, instance; for (i = 0; i < length; i++) { instance = Ext.create(profiles[i], { application: me }); if (instance.isActive() && !current) { current = instance; me.setCurrentProfile(current); } } if (current) { current.init(); } me.initControllers(); me.onBeforeLaunch(); me.finishInitControllers(); }, doInit: function(app) { this.initNamespace(app); this.callParent([app]); }, initNamespace: function(me) { var appProperty = me.getAppProperty(), ns = Ext.namespace(me.getName()); if (ns) { ns.getApplication = function() { return me; }; if (appProperty) { if (!ns[appProperty]) { ns[appProperty] = me; } //<debug> else if (ns[appProperty] !== me) { Ext.log.warn('An existing reference is being overwritten for ' + name + '.' + appProperty + '. See the appProperty config.' ); } //</debug> } } }, initControllers: function() { var me = this, controllers = Ext.Array.from(me.controllers), profile = me.getCurrentProfile(), i, ln; me.controllers = new Ext.util.MixedCollection(); for (i = 0, ln = controllers.length; i < ln; i++) { me.getController(controllers[i]); } // Also launch controllers for the active profile (if we have one) // if (profile) { controllers = profile.getControllers(); for (i = 0, ln = controllers.length; i < ln; i++) { me.getController(controllers[i]); } } }, finishInitControllers: function() { var me = this, controllers, i, l; controllers = me.controllers.getRange(); for (i = 0, l = controllers.length; i < l; i++) { controllers[i].finishInit(me); } }, /** * @method * @template * Called automatically when the page has completely loaded. This is an empty function that * should be overridden by each application that needs to take action on page load. * @param {String} profile The detected application profile * @return {Boolean} By default, the Application will dispatch to the configured startup * controller and action immediately after running the launch function. Return false * to prevent this behavior. */ launch: Ext.emptyFn, /** * @private */ onBeforeLaunch: function() { var me = this, History = Ext.util.History, defaultToken = me.getDefaultToken(), currentProfile = me.getCurrentProfile(), controllers, c, cLen, controller, token; me.initMainView(); if (currentProfile) { currentProfile.launch(); } me.launch.call(me.scope || me); me.launched = true; me.fireEvent('launch', me); controllers = me.controllers.items; cLen = controllers.length; for (c = 0; c < cLen; c++) { controller = controllers[c]; controller.onLaunch(me); } if (!History.ready) { History.init(); } token = History.getToken(); if (token || token === defaultToken) { Ext.route.Router.onStateChange(token); } else if (defaultToken) { History.replace(defaultToken); } // Microloader has detected an Application Cache or LocalStorage Cache update, inform // everyone that may have added listeners late. if (Ext.Microloader && Ext.Microloader.appUpdate && Ext.Microloader.appUpdate.updated) { Ext.Microloader.fireAppUpdate(); } // After launch we may as well cleanup the namespace cache if (!me.cnsTimer) { me.cnsTimer = Ext.defer(Ext.ClassManager.clearNamespaceCache, 2000, Ext.ClassManager); } }, getModuleClassName: function(name, kind) { return Ext.app.Controller.getFullName(name, kind, this.getName()).absoluteName; }, initMainView: function() { var me = this, currentProfile = me.getCurrentProfile(), mainView; if (currentProfile) { mainView = currentProfile.getMainView(); } if (mainView) { me.setMainView(mainView); } else { // since mainView is a lazy config we have to call the getter to initialize it me.getMainView(); } }, applyMainView: function(value) { var view = this.getView(value); // Ensure the full component stack is available immediately. return view.create({ $initParent: this.viewport }); }, /** * Create an instance of a controller by name. * @param {String} name The name of the controller. For a controller with the * full class name `MyApp.controller.Foo`, the name parameter should be `Foo`. * If the controller already exists, it will be returned. * * @return {Ext.app.Controller} controller */ createController: function(name) { return this.getController(name); }, /** * Destroys a controller, any listeners are unbound. * @param {String/Ext.app.Controller} controller The controller */ destroyController: function(controller) { if (typeof controller === 'string') { controller = this.getController(controller, true); } Ext.destroy(controller); }, /** * Get an application's controller based on name or id. Generally, the controller id will be * the same as the name unless otherwise specified. * @param {String} name The name or id of the controller you are trying to retrieve * @param {Boolean} [preventCreate] (private) */ getController: function(name, preventCreate) { var me = this, controllers = me.controllers, className, controller, len, i, c, all; // First check with the passed value if we have an explicit id controller = controllers.get(name); // In a majority of cases, the controller id will be the same as the name. // However, when a controller is manually given an id, it will be keyed // in the collection that way. So if we don't find it, we attempt to loop // over the existing controllers and find it by classname if (!controller) { all = controllers.items; for (i = 0, len = all.length; i < len; ++i) { c = all[i]; className = c.getModuleClassName(); if (className && className === name) { controller = c; break; } } } if (!controller && !preventCreate) { className = me.getModuleClassName(name, 'controller'); controller = Ext.create(className, { application: me, moduleClassName: className }); controllers.add(controller); if (me._initialized) { controller.doInit(me); } } return controller; }, /** * Unregister a controller from the application. * @private * @param {Ext.app.Controller} controller The controller to unregister */ unregister: function(controller) { this.controllers.remove(controller); }, getApplication: function() { return this; }, destroy: function(destroyRefs) { var me = this, controllers = me.controllers, ns = Ext.namespace(me.getName()), appProp = me.getAppProperty(); Ext.undefer(me.cnsTimer); Ext.un('appupdate', me.onAppUpdate, me); Ext.destroy(me.viewport); if (controllers) { controllers.each(function(controller) { controller.destroy(destroyRefs, true); }); } me.controllers = null; me.callParent([destroyRefs, true]); // Clean up any app reference if (ns && ns[appProp] === me) { delete ns[appProp]; } if (Ext.route.Router.application === me) { Ext.route.Router.application = null; } if (Ext.app.Application.instance === me) { Ext.app.Application.instance = null; } }, updateGlyphFontFamily: function(fontFamily) { Ext.setGlyphFontFamily(fontFamily); }, /** * As a convenience developers can locally qualify profile names (e.g. 'MyProfile' vs * 'MyApp.profile.MyProfile'). This just makes sure everything ends up fully qualified. * @private */ applyProfiles: function(profiles) { var me = this; return Ext.Array.map(profiles, function(profile) { return me.getModuleClassName(profile, "profile"); }); }}, function() { /** * @member Ext * @method getApplication * @return {Ext.app.Application} */ Ext.getApplication = function() { return Ext.app.Application.instance; };});