/**
* A Profile represents a range of devices that fall under a common category. For the vast majority
* of apps that use device profiles, the app defines a Phone profile and a Tablet profile. Doing
* this enables you to easily customize the experience for the different sized screens offered by
* those device types.
*
* Only one Profile can be active at a time, and each Profile defines a simple {@link #isActive}
* function that should return either true or false. The first Profile to return true from its
* isActive function is set as your Application's
* {@link Ext.app.Application#currentProfile current profile}.
*
* A Profile can define any number of {@link #models}, {@link #views}, {@link #controllers} and
* {@link #stores} which will be loaded if the Profile is activated. It can also define
* a {@link #launch} function that will be called after all of its dependencies have been loaded,
* just before the {@link Ext.app.Application#launch application launch} function is called.
*
* ## Sample Usage
*
* First you need to tell your Application about your Profile(s):
*
* Ext.application({
* name: 'MyApp',
* profiles: ['Phone', 'Tablet']
* });
*
* This will load app/profile/Phone.js and app/profile/Tablet.js. Here's how we might define the
* Phone profile:
*
* Ext.define('MyApp.profile.Phone', {
* extend: 'Ext.app.Profile',
*
* views: ['Main'],
*
* isActive: function() {
* return Ext.os.is('Phone');
* }
* });
*
* The isActive function returns true if we detect that we are running on a phone device. If that
* is the case the Application will set this Profile active and load the 'Main' view specified
* in the Profile's {@link #views} config.
*
* ## Class Specializations
*
* Because Profiles are specializations of an application, all of the models, views, controllers
* and stores defined in a Profile are expected to be namespaced under the name of the Profile.
* Here's an expanded form of the example above:
*
* Ext.define('MyApp.profile.Phone', {
* extend: 'Ext.app.Profile',
*
* views: ['Main'],
* controllers: ['Signup'],
* models: ['MyApp.model.Group'],
*
* isActive: function() {
* return Ext.os.is('Phone');
* }
* });
*
* In this case, the Profile is going to load *app/view/phone/Main.js*,
* *app/controller/phone/Signup.js* and *app/model/Group.js*. Notice that in each of the first
* two cases the name of the profile ('phone' in this case) was injected into the class names.
* In the third case we specified the full Model name (for Group) so the Profile name was not
* injected.
*
* For a fuller understanding of the ideas behind Profiles and how best to use them in your app,
* we suggest you read the [device profiles guide](/touch/2.4/core_concepts/device_profiles.html).
*
*/
Ext.define('Ext.app.Profile', {
mixins: [
'Ext.mixin.Observable'
],
requires: [
'Ext.app.Controller'
],
/**
* @property {Boolean}
* `true` to identify an object as an instance of `Ext.app.Profile`
*/
isProfile: true,
/**
* @cfg {String} [namespace]
* The namespace that this Profile's classes can be found in. Defaults to the lowercase
* Profile {@link #name}, for example a Profile called MyApp.profile.Phone will by default
* have a 'phone' namespace, which means that this Profile's additional models, stores, views
* and controllers will be loaded from the MyApp.model.phone.*, MyApp.store.phone.*,
* MyApp.view.phone.* and MyApp.controller.phone.* namespaces respectively.
* @accessor
*/
/**
* @cfg {String} [name]
* The name of this Profile. Defaults to the last section of the class name (e.g. a profile
* called MyApp.profile.Phone will default the name to 'Phone').
* @accessor
*/
config: {
/**
* @cfg {String} mainView
*/
mainView: {
$value: null,
lazy: true
},
/**
* @cfg {Ext.app.Application} application
* The {@link Ext.app.Application Application} instance to which this Profile is
* bound. This is set automatically.
* @accessor
* @readonly
*/
application: null,
// @cmd-auto-dependency {aliasPrefix: "controller.", profile: true, blame: "all"}
/**
* @cfg {String[]} controllers
* Any additional {@link Ext.app.Controller Controllers} to load for this profile.
* Note that each item here will be prepended with the Profile namespace when loaded.
*
* Example usage:
*
* controllers: [
* 'Users',
* 'MyApp.controller.Products'
* ]
*
* This will load *MyApp.controller.tablet.Users* and *MyApp.controller.Products*.
* @accessor
*/
controllers: [],
// @cmd-auto-dependency {aliasPrefix : "model.", profile: true, blame: "all"}
/**
* @cfg {String[]} models
* Any additional {@link Ext.app.Application#models Models} to load for this profile.
* Note that each item here will be prepended with the Profile namespace when loaded.
*
* Example usage:
*
* models: [
* 'Group',
* 'MyApp.model.User'
* ]
*
* This will load *MyApp.model.tablet.Group* and *MyApp.model.User*.
* @accessor
*/
models: [],
/* eslint-disable-next-line max-len */
// @cmd-auto-dependency { aliasPrefix: "view.", profile: true, isKeyedObject:true, blame: "all" }
/**
* @cfg {Object/String[]} views
* This config allows the active profile to define a set of `xtypes` and map them
* to desired classes and default configurations. Normally an `xtype` is statically
* declared by a {@link Ext.Component component} in its class definition. This
* mechanism allows the active profile to control a set of these types.
*
* Example:
*
* views: {
* // The "main" xtype maps to MyApp.view.tablet.Main
* //
* main: 'MyApp.view.tablet.Main',
*
* // The "inbox" xtype maps to a subclass of MyApp.view.Inbox (created
* // by this mechanism) that sets the "mode" config to "compact".
* //
* inbox: {
* xclass: 'MyApp.view.Inbox',
* mode: 'compact'
* }
* }
*
* Note that class names used in this form must be full class names, unlike the
* historical usage of `views`. Further, these views cannot be accessed using the
* `getView` method but rather via their assigned `xtype`.
*
* The historical usage of this config is enabled when an array is passed. In this
* case, these are simply additional {@link Ext.app.Application#views views} to
* load for this profile. Note that each item here will be prepended with the
* Profile namespace when loaded.
*
* Example usage:
*
* views: [
* 'Main',
* 'MyApp.view.Login'
* ]
*
* This will load *MyApp.view.tablet.Main* and *MyApp.view.Login*. While supported,
* this usage is discouraged in favor of `xtype` mapping.
* @accessor
*/
views: [],
// @cmd-auto-dependency {aliasPrefix: "store.", profile: true, blame: "all"}
/**
* @cfg {String[]} stores
* Any additional {@link Ext.app.Application#stores Stores} to load for this profile.
* Note that each item here will be prepended with the Profile namespace when loaded.
*
* Example usage:
*
* stores: [
* 'Users',
* 'MyApp.store.Products'
* ]
*
* This will load *MyApp.store.tablet.Users* and *MyApp.store.Products*.
* @accessor
*/
stores: []
},
/**
* Creates a new Profile instance
*/
constructor: function(config) {
this.initConfig(config);
this.mixins.observable.constructor.apply(this, arguments);
},
/**
* Determines whether or not this Profile is active on the device isActive is executed on.
* Should return true if this profile is meant to be active on this device, false otherwise.
* Each Profile should implement this function (the default implementation just returns false).
* @return {Boolean} True if this Profile should be activated on the device it is running on,
* false otherwise
*/
isActive: function() {
return false;
},
/**
* This method is called once the profile is determined to be the active profile. This
* initialization is performed before controllers are initialized and therefore also
* before launch.
* @protected
* @since 6.0.1
*/
init: function() {
var views = this.getViews(),
xtype;
if (views && !(views instanceof Array)) {
for (xtype in views) {
Ext.ClassManager.setXType(views[xtype], xtype);
}
}
},
/**
* @method
* The launch function is called by the {@link Ext.app.Application Application} if this
* Profile's {@link #isActive} function returned true. This is typically the best place to run
* any profile-specific app launch code. Example usage:
*
* launch: function() {
* Ext.create('MyApp.view.tablet.Main');
* }
*/
launch: Ext.emptyFn,
onClassExtended: function(cls, data, hooks) {
var onBeforeClassCreated = hooks.onBeforeCreated;
hooks.onBeforeCreated = function(cls, data) {
var Controller = Ext.app.Controller,
className = cls.$className,
requires = [],
proto = cls.prototype,
views = data.views,
name, namespace;
// Process name and namespace configs here since we need to use the namespace
// in the dependency calculation
name = data.name;
if (name) {
delete data.name;
}
else {
name = className.split('.');
name = name[name.length - 1];
}
cls._name = name;
cls._namespace = name = (data.namespace || name).toLowerCase();
delete data.namespace;
namespace = Controller.resolveNamespace(cls, data);
Controller.processDependencies(proto, requires, namespace, 'model', data.models, name);
Controller.processDependencies(proto, requires, namespace, 'store', data.stores, name);
Controller.processDependencies(proto, requires, namespace, 'controller',
data.controllers, name);
if (views) {
if (views instanceof Array) {
Controller.processDependencies(proto, requires, namespace, 'view', views, name);
}
else {
Ext.app.Profile.processViews(className, views, requires);
}
}
Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
};
},
getName: function() {
// This used to be a Config but is now processed in onClassExtended so we provide
// the getter for compat.
return this.self._name;
},
getNamespace: function() {
// This used to be a Config but is now processed in onClassExtended so we provide
// the getter for compat.
return this.self._namespace;
},
privates: {
statics: {
processViews: function(className, views, requires) {
var body, cls, s, xtype;
for (xtype in views) {
cls = views[xtype];
if (typeof cls !== 'string') {
s = cls.xclass;
//<debug>
if (!s) {
Ext.raise('Views must specify an xclass');
}
//</debug>
body = Ext.apply({
extend: s
}, cls);
delete body.xclass;
// Class names will be App.profile.Tablet$inbox for example
Ext.define(views[xtype] = className + '$' + xtype, body);
cls = s;
}
requires.push(cls);
}
}
}
}
});