3.0 source code

This commit is contained in:
agolybev
2015-04-28 17:59:00 +03:00
parent c69fd34bdd
commit 7b3b2248e5
16311 changed files with 1445974 additions and 3108429 deletions

136
OfficeWeb/vendor/touch/src/app/Action.js vendored Normal file
View File

@@ -0,0 +1,136 @@
/**
* @author Ed Spencer
* @private
*
* Represents a single action as {@link Ext.app.Application#dispatch dispatched} by an Application. This is typically
* generated as a result of a url change being matched by a Route, triggering Application's dispatch function.
*
* This is a private class and its functionality and existence may change in the future. Use at your own risk.
*
*/
Ext.define('Ext.app.Action', {
config: {
/**
* @cfg {Object} scope The scope in which the {@link #action} should be called.
*/
scope: null,
/**
* @cfg {Ext.app.Application} application The Application that this Action is bound to.
*/
application: null,
/**
* @cfg {Ext.app.Controller} controller The {@link Ext.app.Controller controller} whose {@link #action} should
* be called.
*/
controller: null,
/**
* @cfg {String} action The name of the action on the {@link #controller} that should be called.
*/
action: null,
/**
* @cfg {Array} args The set of arguments that will be passed to the controller's {@link #action}.
*/
args: [],
/**
* @cfg {String} url The url that was decoded into the controller/action/args in this Action.
*/
url: undefined,
data: {},
title: null,
/**
* @cfg {Array} beforeFilters The (optional) set of functions to call before the {@link #action} is called.
* This is usually handled directly by the Controller or Application when an Ext.app.Action instance is
* created, but is alterable before {@link #resume} is called.
* @accessor
*/
beforeFilters: [],
/**
* @private
* Keeps track of which before filter is currently being executed by {@link #resume}
*/
currentFilterIndex: -1
},
constructor: function(config) {
this.initConfig(config);
this.getUrl();
},
/**
* Starts execution of this Action by calling each of the {@link #beforeFilters} in turn (if any are specified),
* before calling the Controller {@link #action}. Same as calling {@link #resume}.
*/
execute: function() {
this.resume();
},
/**
* Resumes the execution of this Action (or starts it if it had not been started already). This iterates over all
* of the configured {@link #beforeFilters} and calls them. Each before filter is called with this Action as the
* sole argument, and is expected to call `action.resume()` in order to allow the next filter to be called, or if
* this is the final filter, the original {@link Ext.app.Controller Controller} function.
*/
resume: function() {
var index = this.getCurrentFilterIndex() + 1,
filters = this.getBeforeFilters(),
controller = this.getController(),
nextFilter = filters[index];
if (nextFilter) {
this.setCurrentFilterIndex(index);
nextFilter.call(controller, this);
} else {
controller[this.getAction()].apply(controller, this.getArgs());
}
},
/**
* @private
*/
applyUrl: function(url) {
if (url === null || url === undefined) {
url = this.urlEncode();
}
return url;
},
/**
* @private
* If the controller config is a string, swap it for a reference to the actual controller instance.
* @param {String} controller The controller name.
*/
applyController: function(controller) {
var app = this.getApplication(),
profile = app.getCurrentProfile();
if (Ext.isString(controller)) {
controller = app.getController(controller, profile ? profile.getNamespace() : null);
}
return controller;
},
/**
* @private
*/
urlEncode: function() {
var controller = this.getController(),
splits;
if (controller instanceof Ext.app.Controller) {
splits = controller.$className.split('.');
controller = splits[splits.length - 1];
}
return controller + "/" + this.getAction();
}
});

View File

@@ -0,0 +1,905 @@
/**
* @author Ed Spencer
*
* @aside guide apps_intro
* @aside guide first_app
* @aside video mvc-part-1
* @aside video mvc-part-2
*
* Ext.app.Application defines the set of {@link Ext.data.Model Models}, {@link Ext.app.Controller Controllers},
* {@link Ext.app.Profile Profiles}, {@link Ext.data.Store Stores} and {@link Ext.Component Views} that an application
* consists of. It automatically loads all of those dependencies and can optionally specify a {@link #launch} function
* that will be called when everything is ready.
*
* Sample usage:
*
* Ext.application({
* name: 'MyApp',
*
* models: ['User', 'Group'],
* stores: ['Users'],
* controllers: ['Users'],
* views: ['Main', 'ShowUser'],
*
* launch: function() {
* Ext.create('MyApp.view.Main');
* }
* });
*
* Creating an Application instance is the only time in Sencha Touch 2 that we don't use Ext.create to create the new
* instance. Instead, the {@link Ext#application} function instantiates an Ext.app.Application internally,
* automatically loading the Ext.app.Application class if it is not present on the page already and hooking in to
* {@link Ext#onReady} before creating the instance itself. An alternative is to use Ext.create inside an Ext.onReady
* callback, but Ext.application is preferred.
*
* ## Dependencies
*
* Application follows a simple convention when it comes to specifying the controllers, views, models, stores and
* profiles it requires. By default it expects each of them to be found inside the *app/controller*, *app/view*,
* *app/model*, *app/store* and *app/profile* directories in your app - if you follow this convention you can just
* specify the last part of each class name and Application will figure out the rest for you:
*
* Ext.application({
* name: 'MyApp',
*
* controllers: ['Users'],
* models: ['User', 'Group'],
* stores: ['Users'],
* views: ['Main', 'ShowUser']
* });
*
* The example above will load 6 files:
*
* - app/model/User.js
* - app/model/Group.js
* - app/store/Users.js
* - app/controller/Users.js
* - app/view/Main.js
* - app/view/ShowUser.js
*
* ### Nested Dependencies
*
* For larger apps it's common to split the models, views and controllers into subfolders so keep the project
* organized. This is especially true of views - it's not unheard of for large apps to have over a hundred separate
* view classes so organizing them into folders can make maintenance much simpler.
*
* To specify dependencies in subfolders just use a period (".") to specify the folder:
*
* Ext.application({
* name: 'MyApp',
*
* controllers: ['Users', 'nested.MyController'],
* views: ['products.Show', 'products.Edit', 'user.Login']
* });
*
* In this case these 5 files will be loaded:
*
* - app/controller/Users.js
* - app/controller/nested/MyController.js
* - app/view/products/Show.js
* - app/view/products/Edit.js
* - app/view/user/Login.js
*
* Note that we can mix and match within each configuration here - for each model, view, controller, profile or store
* you can specify either just the final part of the class name (if you follow the directory conventions), or the full
* class name.
*
* ### External Dependencies
*
* Finally, we can specify application dependencies from outside our application by fully-qualifying the classes we
* want to load. A common use case for this is sharing authentication logic between multiple applications. Perhaps you
* have several apps that login via a common user database and you want to share that code between them. An easy way to
* do this is to create a folder alongside your app folder and then add its contents as dependencies for your app.
*
* For example, let's say our shared login code contains a login controller, a user model and a login form view. We
* want to use all of these in our application:
*
* Ext.Loader.setPath({
* 'Auth': 'Auth'
* });
*
* Ext.application({
* views: ['Auth.view.LoginForm', 'Welcome'],
* controllers: ['Auth.controller.Sessions', 'Main'],
* models: ['Auth.model.User']
* });
*
* This will load the following files:
*
* - Auth/view/LoginForm.js
* - Auth/controller/Sessions.js
* - Auth/model/User.js
* - app/view/Welcome.js
* - app/controller/Main.js
*
* The first three were loaded from outside our application, the last two from the application itself. Note how we can
* still mix and match application files and external dependency files.
*
* Note that to enable the loading of external dependencies we just have to tell the Loader where to find those files,
* which is what we do with the Ext.Loader.setPath call above. In this case we're telling the Loader to find any class
* starting with the 'Auth' namespace inside our 'Auth' folder. This means we can drop our common Auth code into our
* application alongside the app folder and the framework will be able to figure out how to load everything.
*
* ## Launching
*
* Each Application can define a {@link Ext.app.Application#launch launch} function, which is called as soon as all of
* your app's classes have been loaded and the app is ready to be launched. This is usually the best place to put any
* application startup logic, typically creating the main view structure for your app.
*
* In addition to the Application launch function, there are two other places you can put app startup logic. Firstly,
* each Controller is able to define an {@link Ext.app.Controller#init init} function, which is called before the
* Application launch function. Secondly, if you are using Device Profiles, each Profile can define a
* {@link Ext.app.Profile#launch launch} function, which is called after the Controller init functions but before the
* Application launch function.
*
* Note that only the active Profile has its launch function called - for example if you define profiles for Phone and
* Tablet and then launch the app on a tablet, only the Tablet Profile's launch function is called.
*
* 1. Controller#init functions called
* 2. Profile#launch function called
* 3. Application#launch function called
* 4. Controller#launch functions called
*
* When using Profiles it is common to place most of the bootup logic inside the Profile launch function because each
* Profile has a different set of views that need to be constructed at startup.
*
* ## Adding to Home Screen
*
* iOS devices allow your users to add your app to their home screen for easy access. iOS allows you to customize
* several aspects of this, including the icon that will appear on the home screen and the startup image. These can be
* specified in the Ext.application setup block:
*
* Ext.application({
* name: 'MyApp',
*
* {@link #icon}: 'resources/img/icon.png',
* {@link #isIconPrecomposed}: false,
* {@link #startupImage}: {
* '320x460': 'resources/startup/320x460.jpg',
* '640x920': 'resources/startup/640x920.png',
* '640x1096': 'resources/startup/640x1096.png',
* '768x1004': 'resources/startup/768x1004.png',
* '748x1024': 'resources/startup/748x1024.png',
* '1536x2008': 'resources/startup/1536x2008.png',
* '1496x2048': 'resources/startup/1496x2048.png'
* }
* });
*
* When the user adds your app to the home screen, your resources/img/icon.png file will be used as the application
* {@link #icon}. We also used the {@link #isIconPrecomposed} configuration to turn off the gloss effect that is automatically added
* to icons in iOS. Finally we used the {@link #startupImage} configuration to provide the images that will be displayed
* while your application is starting up. See also {@link #statusBarStyle}.
*
* ## Find out more
*
* If you are not already familiar with writing applications with Sencha Touch 2 we recommend reading the
* [intro to applications guide](#!/guide/apps_intro), which lays out the core principles of writing apps
* with Sencha Touch 2.
*/
Ext.define('Ext.app.Application', {
extend: 'Ext.app.Controller',
requires: [
'Ext.app.History',
'Ext.app.Profile',
'Ext.app.Router',
'Ext.app.Action'
],
config: {
/**
* @cfg {String/Object} icon
* Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
* when the application is added to the device's Home Screen.
*
* Ext.setup({
* icon: {
* 57: 'resources/icons/Icon.png',
* 72: 'resources/icons/Icon~ipad.png',
* 114: 'resources/icons/Icon@2x.png',
* 144: 'resources/icons/Icon~ipad@2x.png'
* },
* onReady: function() {
* // ...
* }
* });
*
* Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
* icon image. Here is the breakdown of each dimension and its device target:
*
* - 57: Non-retina iPhone, iPod touch, and all Android devices
* - 72: Retina iPhone and iPod touch
* - 114: Non-retina iPad (first and second generation)
* - 144: Retina iPad (third generation)
*
* Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
*
* It is highly recommended that you provide all these different sizes to accommodate a full range of
* devices currently available. However if you only have one icon in one size, make it 57x57 in size and
* specify it as a string value. This same icon will be used on all supported devices.
*
* Ext.application({
* icon: 'resources/icons/Icon.png',
* launch: function() {
* // ...
* }
* });
*/
/**
* @cfg {Object} startupImage
* Specifies a set of URLs to the application startup images for different device form factors. This image is
* displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
* to iOS devices.
*
* Ext.application({
* startupImage: {
* '320x460': 'resources/startup/320x460.jpg',
* '640x920': 'resources/startup/640x920.png',
* '640x1096': 'resources/startup/640x1096.png',
* '768x1004': 'resources/startup/768x1004.png',
* '748x1024': 'resources/startup/748x1024.png',
* '1536x2008': 'resources/startup/1536x2008.png',
* '1496x2048': 'resources/startup/1496x2048.png'
* },
* launch: function() {
* // ...
* }
* });
*
* Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
* Here is the breakdown of each dimension and its device target:
*
* - 320x460: Non-retina iPhone, iPod touch, and all Android devices
* - 640x920: Retina iPhone and iPod touch
* - 640x1096: iPhone 5 and iPod touch (fifth generation)
* - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
* - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
* - 1536x2008: Retina iPad (third generation) in portrait orientation
* - 1496x2048: Retina iPad (third generation) in landscape orientation
*
* Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
* a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
*/
/**
* @cfg {Boolean} isIconPrecomposed
* `true` to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
* only applies to iOS devices.
*/
/**
* @cfg {String} [statusBarStyle='black'] Allows you to set the style of the status bar when your app is added to the
* home screen on iOS devices. Alternative is to set to 'black-translucent', which turns
* the status bar semi-transparent and overlaps the app content. This is usually not a good option for web apps
*/
/**
* @cfg {String} tabletIcon Path to the _.png_ image file to use when your app is added to the home screen on an
* iOS **tablet** device (iPad).
* @deprecated 2.0.0 Please use the {@link #icon} configuration instead.
*/
/**
* @cfg {String} phoneIcon Path to the _.png_ image file to use when your app is added to the home screen on an
* iOS **phone** device (iPhone or iPod).
* @deprecated 2.0.0 Please use the {@link #icon} configuration instead.
*/
/**
* @cfg {Boolean} glossOnIcon If set to `false`, the 'gloss' effect added to home screen {@link #icon icons} on
* iOS devices will be removed.
* @deprecated 2.0.0 Please use the {@link #isIconPrecomposed} configuration instead.
*/
/**
* @cfg {String} phoneStartupScreen Path to the _.png_ image file that will be displayed while the app is
* starting up once it has been added to the home screen of an iOS phone device (iPhone or iPod). This _.png_
* file should be 320px wide and 460px high.
* @deprecated 2.0.0 Please use the {@link #startupImage} configuration instead.
*/
/**
* @cfg {String} tabletStartupScreen Path to the _.png_ image file that will be displayed while the app is
* starting up once it has been added to the home screen of an iOS tablet device (iPad). This _.png_ file should
* be 768px wide and 1004px high.
* @deprecated 2.0.0 Please use the {@link #startupImage} configuration instead.
*/
/**
* @cfg {Array} profiles The set of profiles to load for this Application. Each profile is expected to
* exist inside the *app/profile* directory and define a class following the convention
* AppName.profile.ProfileName. For example, in the code below, the classes *AppName.profile.Phone*
* and *AppName.profile.Tablet* will be loaded. Note that we are able to specify
* either the full class name (as with *AppName.profile.Tablet*) or just the final part of the class name
* and leave Application to automatically prepend *AppName.profile.'* to each:
*
* profiles: [
* 'Phone',
* 'AppName.profile.Tablet',
* 'SomeCustomNamespace.profile.Desktop'
* ]
* @accessor
*/
profiles: [],
/**
* @cfg {Array} controllers The set of controllers to load for this Application. Each controller is expected to
* exist inside the *app/controller* directory and define a class following the convention
* AppName.controller.ControllerName. For example, in the code below, the classes *AppName.controller.Users*,
* *AppName.controller.Groups* and *AppName.controller.Products* will be loaded. Note that we are able to specify
* either the full class name (as with *AppName.controller.Products*) or just the final part of the class name
* and leave Application to automatically prepend *AppName.controller.'* to each:
*
* controllers: [
* 'Users',
* 'Groups',
* 'AppName.controller.Products',
* 'SomeCustomNamespace.controller.Orders'
* ]
* @accessor
*/
controllers: [],
/**
* @cfg {Ext.app.History} history The global {@link Ext.app.History History} instance attached to this
* Application.
* @accessor
* @readonly
*/
history: {},
/**
* @cfg {String} name The name of the Application. This should be a single word without spaces or periods
* because it is used as the Application's global namespace. All classes in your application should be
* namespaced undef the Application's name - for example if your application name is 'MyApp', your classes
* should be named 'MyApp.model.User', 'MyApp.controller.Users', 'MyApp.view.Main' etc
* @accessor
*/
name: null,
/**
* @cfg {String} appFolder 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.
* @accessor
*/
appFolder : 'app',
/**
* @cfg {Ext.app.Router} router The global {@link Ext.app.Router Router} instance attached to this Application.
* @accessor
* @readonly
*/
router: {},
/**
* @cfg {Array} controllerInstances Used internally as the collection of instantiated controllers. Use {@link #getController} instead.
* @private
* @accessor
*/
controllerInstances: [],
/**
* @cfg {Array} profileInstances Used internally as the collection of instantiated profiles.
* @private
* @accessor
*/
profileInstances: [],
/**
* @cfg {Ext.app.Profile} currentProfile The {@link Ext.app.Profile Profile} that is currently active for the
* Application. This is set once, automatically by the Application before launch.
* @accessor
* @readonly
*/
currentProfile: null,
/**
* @cfg {Function} launch An optional function that will be called when the Application is ready to be
* launched. This is normally used to render any initial UI required by your application
* @accessor
*/
launch: Ext.emptyFn,
/**
* @private
* @cfg {Boolean} enableLoader Private config to disable loading of Profiles at application construct time.
* This is used by Sencha's unit test suite to test _Application.js_ in isolation and is likely to be removed
* in favor of a more pleasing solution by the time you use it.
* @accessor
*/
enableLoader: true,
/**
* @private
* @cfg {String[]} requires An array of extra dependencies, to be required after this application's {@link #name} config
* has been processed properly, but before anything else to ensure overrides get executed first.
* @accessor
*/
requires: []
},
/**
* Constructs a new Application instance.
*/
constructor: function(config) {
config = config || {};
Ext.applyIf(config, {
application: this
});
this.initConfig(config);
//it's common to pass in functions to an application but because they are not predictable config names they
//aren't ordinarily placed onto this so we need to do it manually
for (var key in config) {
this[key] = config[key];
}
// <deprecated product=touch since=2.0>
if (config.autoCreateViewport) {
Ext.Logger.deprecate(
'[Ext.app.Application] autoCreateViewport has been deprecated in Sencha Touch 2. Please implement a ' +
'launch function on your Application instead and use Ext.create("MyApp.view.Main") to create your initial UI.'
);
}
// </deprecated>
//<debug>
Ext.Loader.setConfig({ enabled: true });
//</debug>
Ext.require(this.getRequires(), function() {
if (this.getEnableLoader() !== false) {
Ext.require(this.getProfiles(), this.onProfilesLoaded, this);
}
}, this);
},
/**
* Dispatches a given {@link Ext.app.Action} to the relevant Controller instance. This is not usually called
* directly by the developer, instead Sencha Touch's History support picks up on changes to the browser's url
* and calls dispatch automatically.
* @param {Ext.app.Action} action The action to dispatch.
* @param {Boolean} [addToHistory=true] Sets the browser's url to the action's url.
*/
dispatch: function(action, addToHistory) {
action = action || {};
Ext.applyIf(action, {
application: this
});
action = Ext.factory(action, Ext.app.Action);
if (action) {
var profile = this.getCurrentProfile(),
profileNS = profile ? profile.getNamespace() : undefined,
controller = this.getController(action.getController(), profileNS);
if (controller) {
if (addToHistory !== false) {
this.getHistory().add(action, true);
}
controller.execute(action);
}
}
},
/**
* Redirects the browser to the given url. This only affects the url after the '#'. You can pass in either a String
* or a Model instance - if a Model instance is defined its {@link Ext.data.Model#toUrl toUrl} function is called,
* which returns a string representing the url for that model. Internally, this uses your application's
* {@link Ext.app.Router Router} to decode the url into a matching controller action and then calls
* {@link #dispatch}.
* @param {String/Ext.data.Model} url The String url to redirect to.
*/
redirectTo: function(url) {
if (Ext.data && Ext.data.Model && url instanceof Ext.data.Model) {
var record = url;
url = record.toUrl();
}
var decoded = this.getRouter().recognize(url);
if (decoded) {
decoded.url = url;
if (record) {
decoded.data = {};
decoded.data.record = record;
}
return this.dispatch(decoded);
}
},
/**
* @private
* (documented on Controller's control config)
*/
control: function(selectors, controller) {
//if the controller is not defined, use this instead (the application instance)
controller = controller || this;
var dispatcher = this.getEventDispatcher(),
refs = (controller) ? controller.getRefs() : {},
selector, eventName, listener, listeners, ref;
for (selector in selectors) {
if (selectors.hasOwnProperty(selector)) {
listeners = selectors[selector];
ref = refs[selector];
//refs can be used in place of selectors
if (ref) {
selector = ref.selector || ref;
}
for (eventName in listeners) {
listener = listeners[eventName];
if (Ext.isString(listener)) {
listener = controller[listener];
}
dispatcher.addListener('component', selector, eventName, listener, controller);
}
}
}
},
/**
* Returns the Controller instance for the given controller name.
* @param {String} name The name of the Controller.
* @param {String} [profileName] Optional profile name. If passed, this is the same as calling
* `getController('profileName.controllerName')`.
*/
getController: function(name, profileName) {
var instances = this.getControllerInstances(),
appName = this.getName(),
format = Ext.String.format,
topLevelName;
if (name instanceof Ext.app.Controller) {
return name;
}
if (instances[name]) {
return instances[name];
} else {
topLevelName = format("{0}.controller.{1}", appName, name);
profileName = format("{0}.controller.{1}.{2}", appName, profileName, name);
return instances[profileName] || instances[topLevelName];
}
},
/**
* @private
* Callback that is invoked when all of the configured Profiles have been loaded. Detects the current profile and
* gathers any additional dependencies from that profile, then loads all of those dependencies.
*/
onProfilesLoaded: function() {
var profiles = this.getProfiles(),
length = profiles.length,
instances = [],
requires = this.gatherDependencies(),
current, i, profileDeps;
for (i = 0; i < length; i++) {
instances[i] = Ext.create(profiles[i], {
application: this
});
/*
* Note that we actually require all of the dependencies for all Profiles - this is so that we can produce
* a single build file that will work on all defined Profiles. Although the other classes will be loaded,
* the correct Profile will still be identified and the other classes ignored. While this feels somewhat
* inefficient, the majority of the bulk of an application is likely to be the framework itself. The bigger
* the app though, the bigger the effect of this inefficiency so ideally we will create a way to create and
* load Profile-specific builds in a future release.
*/
profileDeps = instances[i].getDependencies();
requires = requires.concat(profileDeps.all);
if (instances[i].isActive() && !current) {
current = instances[i];
this.setCurrentProfile(current);
this.setControllers(this.getControllers().concat(profileDeps.controller));
this.setModels(this.getModels().concat(profileDeps.model));
this.setViews(this.getViews().concat(profileDeps.view));
this.setStores(this.getStores().concat(profileDeps.store));
}
}
this.setProfileInstances(instances);
Ext.require(requires, this.loadControllerDependencies, this);
},
/**
* @private
* Controllers can also specify dependencies, so we grab them all here and require them.
*/
loadControllerDependencies: function() {
this.instantiateControllers();
var controllers = this.getControllerInstances(),
classes = [],
stores = [],
i, controller, controllerStores, name;
for (name in controllers) {
controller = controllers[name];
controllerStores = controller.getStores();
stores = stores.concat(controllerStores);
classes = classes.concat(controller.getModels().concat(controller.getViews()).concat(controllerStores));
}
this.setStores(this.getStores().concat(stores));
Ext.require(classes, this.onDependenciesLoaded, this);
},
/**
* @private
* Callback that is invoked when all of the Application, Controller and Profile dependencies have been loaded.
* Launches the controllers, then the profile and application.
*/
onDependenciesLoaded: function() {
var me = this,
profile = this.getCurrentProfile(),
launcher = this.getLaunch(),
controllers, name;
this.instantiateStores();
//<deprecated product=touch since=2.0>
Ext.app.Application.appInstance = this;
if (Ext.Router) {
Ext.Router.setAppInstance(this);
}
//</deprecated>
controllers = this.getControllerInstances();
for (name in controllers) {
controllers[name].init(this);
}
if (profile) {
profile.launch();
}
launcher.call(me);
for (name in controllers) {
//<debug warn>
if (controllers[name] && !(controllers[name] instanceof Ext.app.Controller)) {
Ext.Logger.warn("The controller '" + name + "' doesn't have a launch method. Are you sure it extends from Ext.app.Controller?");
} else {
//</debug>
controllers[name].launch(this);
//<debug warn>
}
//</debug>
}
me.redirectTo(window.location.hash.substr(1));
},
/**
* @private
* Gathers up all of the previously computed MVCS dependencies into a single array that we can pass to {@link Ext#require}.
*/
gatherDependencies: function() {
var classes = this.getModels().concat(this.getViews()).concat(this.getControllers());
Ext.each(this.getStores(), function(storeName) {
if (Ext.isString(storeName)) {
classes.push(storeName);
}
}, this);
return classes;
},
/**
* @private
* Should be called after dependencies are loaded, instantiates all of the Stores specified in the {@link #stores}
* config. For each item in the stores array we make sure the Store is instantiated. When strings are specified,
* the corresponding _app/store/StoreName.js_ was loaded so we now instantiate a `MyApp.store.StoreName`, giving it the
* id `StoreName`.
*/
instantiateStores: function() {
var stores = this.getStores(),
length = stores.length,
store, storeClass, storeName, splits, i;
for (i = 0; i < length; i++) {
store = stores[i];
if (Ext.data && Ext.data.Store && !(store instanceof Ext.data.Store)) {
if (Ext.isString(store)) {
storeName = store;
storeClass = Ext.ClassManager.classes[store];
store = {
xclass: store
};
//we don't want to wipe out a configured storeId in the app's Store subclass so need
//to check for this first
if (storeClass.prototype.defaultConfig.storeId === undefined) {
splits = storeName.split('.');
store.id = splits[splits.length - 1];
}
}
stores[i] = Ext.factory(store, Ext.data.Store);
}
}
this.setStores(stores);
},
/**
* @private
* Called once all of our controllers have been loaded
*/
instantiateControllers: function() {
var controllerNames = this.getControllers(),
instances = {},
length = controllerNames.length,
name, i;
for (i = 0; i < length; i++) {
name = controllerNames[i];
instances[name] = Ext.create(name, {
application: this
});
}
return this.setControllerInstances(instances);
},
/**
* @private
* As a convenience developers can locally qualify controller names (e.g. 'MyController' vs
* 'MyApp.controller.MyController'). This just makes sure everything ends up fully qualified
*/
applyControllers: function(controllers) {
return this.getFullyQualified(controllers, 'controller');
},
/**
* @private
* 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
*/
applyProfiles: function(profiles) {
return this.getFullyQualified(profiles, 'profile');
},
/**
* @private
* Checks that the name configuration has any whitespace, and trims them if found.
*/
applyName: function(name) {
var oldName;
if (name && name.match(/ /g)) {
oldName = name;
name = name.replace(/ /g, "");
// <debug>
Ext.Logger.warn('Attempting to create an application with a name which contains whitespace ("' + oldName + '"). Renamed to "' + name + '".');
// </debug>
}
return name;
},
/**
* @private
* Makes sure the app namespace exists, sets the `app` property of the namespace to this application and sets its
* loading path (checks to make sure the path hadn't already been set via Ext.Loader.setPath)
*/
updateName: function(newName) {
Ext.ClassManager.setNamespace(newName + '.app', this);
if (!Ext.Loader.config.paths[newName]) {
Ext.Loader.setPath(newName, this.getAppFolder());
}
},
/**
* @private
*/
applyRouter: function(config) {
return Ext.factory(config, Ext.app.Router, this.getRouter());
},
/**
* @private
*/
applyHistory: function(config) {
var history = Ext.factory(config, Ext.app.History, this.getHistory());
history.on('change', this.onHistoryChange, this);
return history;
},
/**
* @private
*/
onHistoryChange: function(url) {
this.dispatch(this.getRouter().recognize(url), false);
}
}, function() {
// <deprecated product=touch since=2.0>
Ext.regApplication = function(config) {
Ext.Logger.deprecate(
'[Ext.app.Application] Ext.regApplication() is deprecated, please replace it with Ext.application()'
);
var appName = config.name,
format = Ext.String.format;
Ext.ns(
appName,
format("{0}.controllers", appName),
format("{0}.models", appName),
format("{0}.views", appName)
);
Ext.application(config);
};
Ext.define('Ext.data.ProxyMgr', {
singleton: true,
registerType: function(name, cls) {
Ext.Logger.deprecate(
'Ext.data.ProxyMgr no longer exists - instead of calling Ext.data.ProxyMgr.registerType just update ' +
'your custom Proxy class to set alias: "proxy.' + name + '"'
);
Ext.ClassManager.setAlias(cls, "proxy." + name);
}
});
Ext.reg = function(alias, cls) {
Ext.Logger.deprecate(
'Ext.reg is deprecated, please set xtype: "' + alias + '" directly in your subclass instead'
);
Ext.ClassManager.setAlias(cls, alias);
};
Ext.redirect = function() {
var app = Ext.app.Application.appInstance;
Ext.Logger.deprecate('[Ext.app.Application] Ext.redirect is deprecated, please use YourApp.redirectTo instead');
if (app) {
app.redirectTo.apply(app, arguments);
}
};
Ext.dispatch = function() {
var app = Ext.app.Application.appInstance;
Ext.Logger.deprecate('[Ext.app.Application] Ext.dispatch is deprecated, please use YourApp.dispatch instead');
if (app) {
app.dispatch.apply(app, arguments);
}
};
// </deprecated>
});

View File

@@ -0,0 +1,747 @@
/**
* @author Ed Spencer
*
* @aside guide controllers
* @aside guide apps_intro
* @aside guide history_support
* @aside video mvc-part-1
* @aside video mvc-part-2
*
* Controllers are responsible for responding to events that occur within your app. If your app contains a Logout
* {@link Ext.Button button} that your user can tap on, a Controller would listen to the Button's tap event and take
* the appropriate action. It allows the View classes to handle the display of data and the Model classes to handle the
* loading and saving of data - the Controller is the glue that binds them together.
*
* ## Relation to Ext.app.Application
*
* Controllers exist within the context of an {@link Ext.app.Application Application}. An Application usually consists
* of a number of Controllers, each of which handle a specific part of the app. For example, an Application that
* handles the orders for an online shopping site might have controllers for Orders, Customers and Products.
*
* All of the Controllers that an Application uses are specified in the Application's
* {@link Ext.app.Application#controllers} config. The Application automatically instantiates each Controller and keeps
* references to each, so it is unusual to need to instantiate Controllers directly. By convention each Controller is
* named after the thing (usually the Model) that it deals with primarily, usually in the plural - for example if your
* app is called 'MyApp' and you have a Controller that manages Products, convention is to create a
* MyApp.controller.Products class in the file app/controller/Products.js.
*
* ## Refs and Control
*
* The centerpiece of Controllers is the twin configurations {@link #refs} and {@link #cfg-control}. These are used to
* easily gain references to Components inside your app and to take action on them based on events that they fire.
* Let's look at {@link #refs} first:
*
* ### Refs
*
* Refs leverage the powerful {@link Ext.ComponentQuery ComponentQuery} syntax to easily locate Components on your
* page. We can define as many refs as we like for each Controller, for example here we define a ref called 'nav' that
* finds a Component on the page with the ID 'mainNav'. We then use that ref in the addLogoutButton beneath it:
*
* Ext.define('MyApp.controller.Main', {
* extend: 'Ext.app.Controller',
*
* config: {
* refs: {
* nav: '#mainNav'
* }
* },
*
* addLogoutButton: function() {
* this.getNav().add({
* text: 'Logout'
* });
* }
* });
*
* Usually, a ref is just a key/value pair - the key ('nav' in this case) is the name of the reference that will be
* generated, the value ('#mainNav' in this case) is the {@link Ext.ComponentQuery ComponentQuery} selector that will
* be used to find the Component.
*
* Underneath that, we have created a simple function called addLogoutButton which uses this ref via its generated
* 'getNav' function. These getter functions are generated based on the refs you define and always follow the same
* format - 'get' followed by the capitalized ref name. In this case we're treating the nav reference as though it's a
* {@link Ext.Toolbar Toolbar}, and adding a Logout button to it when our function is called. This ref would recognize
* a Toolbar like this:
*
* Ext.create('Ext.Toolbar', {
* id: 'mainNav',
*
* items: [
* {
* text: 'Some Button'
* }
* ]
* });
*
* Assuming this Toolbar has already been created by the time we run our 'addLogoutButton' function (we'll see how that
* is invoked later), it will get the second button added to it.
*
* ### Advanced Refs
*
* Refs can also be passed a couple of additional options, beyond name and selector. These are autoCreate and xtype,
* which are almost always used together:
*
* Ext.define('MyApp.controller.Main', {
* extend: 'Ext.app.Controller',
*
* config: {
* refs: {
* nav: '#mainNav',
*
* infoPanel: {
* selector: 'tabpanel panel[name=fish] infopanel',
* xtype: 'infopanel',
* autoCreate: true
* }
* }
* }
* });
*
* We've added a second ref to our Controller. Again the name is the key, 'infoPanel' in this case, but this time we've
* passed an object as the value instead. This time we've used a slightly more complex selector query - in this example
* imagine that your app contains a {@link Ext.tab.Panel tab panel} and that one of the items in the tab panel has been
* given the name 'fish'. Our selector matches any Component with the xtype 'infopanel' inside that tab panel item.
*
* The difference here is that if that infopanel does not exist already inside the 'fish' panel, it will be
* automatically created when you call this.getInfoPanel inside your Controller. The Controller is able to do this
* because we provided the xtype to instantiate with in the event that the selector did not return anything.
*
* ### Control
*
* The sister config to {@link #refs} is {@link #cfg-control}. {@link #cfg-control Control} is the means by which your listen
* to events fired by Components and have your Controller react in some way. Control accepts both ComponentQuery
* selectors and refs as its keys, and listener objects as values - for example:
*
* Ext.define('MyApp.controller.Main', {
* extend: 'Ext.app.Controller',
*
* config: {
* control: {
* loginButton: {
* tap: 'doLogin'
* },
* 'button[action=logout]': {
* tap: 'doLogout'
* }
* },
*
* refs: {
* loginButton: 'button[action=login]'
* }
* },
*
* doLogin: function() {
* //called whenever the Login button is tapped
* },
*
* doLogout: function() {
* //called whenever any Button with action=logout is tapped
* }
* });
*
* Here we have set up two control declarations - one for our loginButton ref and the other for any Button on the page
* that has been given the action 'logout'. For each declaration we passed in a single event handler - in each case
* listening for the 'tap' event, specifying the action that should be called when that Button fires the tap event.
* Note that we specified the 'doLogin' and 'doLogout' methods as strings inside the control block - this is important.
*
* You can listen to as many events as you like in each control declaration, and mix and match ComponentQuery selectors
* and refs as the keys.
*
* ## Routes
*
* As of Sencha Touch 2, Controllers can now directly specify which routes they are interested in. This enables us to
* provide history support within our app, as well as the ability to deeply link to any part of the application that we
* provide a route for.
*
* For example, let's say we have a Controller responsible for logging in and viewing user profiles, and want to make
* those screens accessible via urls. We could achieve that like this:
*
* Ext.define('MyApp.controller.Users', {
* extend: 'Ext.app.Controller',
*
* config: {
* routes: {
* 'login': 'showLogin',
* 'user/:id': 'showUserById'
* },
*
* refs: {
* main: '#mainTabPanel'
* }
* },
*
* //uses our 'main' ref above to add a loginpanel to our main TabPanel (note that
* //'loginpanel' is a custom xtype created for this application)
* showLogin: function() {
* this.getMain().add({
* xtype: 'loginpanel'
* });
* },
*
* //Loads the User then adds a 'userprofile' view to the main TabPanel
* showUserById: function(id) {
* MyApp.model.User.load(id, {
* scope: this,
* success: function(user) {
* this.getMain().add({
* xtype: 'userprofile',
* user: user
* });
* }
* });
* }
* });
*
* The routes we specified above simply map the contents of the browser address bar to a Controller function to call
* when that route is matched. The routes can be simple text like the login route, which matches against
* http://myapp.com/#login, or contain wildcards like the 'user/:id' route, which matches urls like
* http://myapp.com/#user/123. Whenever the address changes the Controller automatically calls the function specified.
*
* Note that in the showUserById function we had to first load the User instance. Whenever you use a route, the
* function that is called by that route is completely responsible for loading its data and restoring state. This is
* because your user could either send that url to another person or simply refresh the page, which we wipe clear any
* cached data you had already loaded. There is a more thorough discussion of restoring state with routes in the
* application architecture guides.
*
* ## Advanced Usage
*
* See [the Controllers guide](#!/guide/controllers) for advanced Controller usage including before filters
* and customizing for different devices.
*/
Ext.define('Ext.app.Controller', {
mixins: {
observable: "Ext.mixin.Observable"
},
config: {
/**
* @cfg {Object} refs A collection of named {@link Ext.ComponentQuery ComponentQuery} selectors that makes it
* easy to get references to key Components on your page. Example usage:
*
* refs: {
* main: '#mainTabPanel',
* loginButton: '#loginWindow button[action=login]',
*
* infoPanel: {
* selector: 'infopanel',
* xtype: 'infopanel',
* autoCreate: true
* }
* }
*
* The first two are simple ComponentQuery selectors, the third (infoPanel) also passes in the autoCreate and
* xtype options, which will first run the ComponentQuery to see if a Component matching that selector exists
* on the page. If not, it will automatically create one using the xtype provided:
*
* someControllerFunction: function() {
* //if the info panel didn't exist before, calling its getter will instantiate
* //it automatically and return the new instance
* this.getInfoPanel().show();
* }
*
* @accessor
*/
refs: {},
/**
* @cfg {Object} routes Provides a mapping of urls to Controller actions. Whenever the specified url is matched
* in the address bar, the specified Controller action is called. Example usage:
*
* routes: {
* 'login': 'showLogin',
* 'users/:id': 'showUserById'
* }
*
* The first route will match against http://myapp.com/#login and call the Controller's showLogin function. The
* second route contains a wildcard (':id') and will match all urls like http://myapp.com/#users/123, calling
* the showUserById function with the matched ID as the first argument.
*
* @accessor
*/
routes: {},
/**
* @cfg {Object} control Provides a mapping of Controller functions that should be called whenever certain
* Component events are fired. The Components can be specified using {@link Ext.ComponentQuery ComponentQuery}
* selectors or {@link #refs}. Example usage:
*
* control: {
* 'button[action=logout]': {
* tap: 'doLogout'
* },
* main: {
* activeitemchange: 'doUpdate'
* }
* }
*
* The first item uses a ComponentQuery selector to run the Controller's doLogout function whenever any Button
* with action=logout is tapped on. The second calls the Controller's doUpdate function whenever the
* activeitemchange event is fired by the Component referenced by our 'main' ref. In this case main is a tab
* panel (see {@link #refs} for how to set that reference up).
*
* @accessor
*/
control: {},
/**
* @cfg {Object} before Provides a mapping of Controller functions to filter functions that are run before them
* when dispatched to from a route. These are usually used to run pre-processing functions like authentication
* before a certain function is executed. They are only called when dispatching from a route. Example usage:
*
* Ext.define('MyApp.controller.Products', {
* config: {
* before: {
* editProduct: 'authenticate'
* },
*
* routes: {
* 'product/edit/:id': 'editProduct'
* }
* },
*
* //this is not directly because our before filter is called first
* editProduct: function() {
* //... performs the product editing logic
* },
*
* //this is run before editProduct
* authenticate: function(action) {
* MyApp.authenticate({
* success: function() {
* action.resume();
* },
* failure: function() {
* Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
* }
* });
* }
* });
*
* @accessor
*/
before: {},
/**
* @cfg {Ext.app.Application} application The Application instance this Controller is attached to. This is
* automatically provided when using the MVC architecture so should rarely need to be set directly.
* @accessor
*/
application: {},
/**
* @cfg {String[]} stores The set of stores to load for this Application. Each store is expected to
* exist inside the *app/store* directory and define a class following the convention
* AppName.store.StoreName. For example, in the code below, the *AppName.store.Users* class will be loaded.
* Note that we are able to specify either the full class name (as with *AppName.store.Groups*) or just the
* final part of the class name and leave Application to automatically prepend *AppName.store.'* to each:
*
* stores: [
* 'Users',
* 'AppName.store.Groups',
* 'SomeCustomNamespace.store.Orders'
* ]
* @accessor
*/
stores: [],
/**
* @cfg {String[]} models The set of models to load for this Application. Each model is expected to exist inside the
* *app/model* directory and define a class following the convention AppName.model.ModelName. For example, in the
* code below, the classes *AppName.model.User*, *AppName.model.Group* and *AppName.model.Product* will be loaded.
* Note that we are able to specify either the full class name (as with *AppName.model.Product*) or just the
* final part of the class name and leave Application to automatically prepend *AppName.model.* to each:
*
* models: [
* 'User',
* 'Group',
* 'AppName.model.Product',
* 'SomeCustomNamespace.model.Order'
* ]
* @accessor
*/
models: [],
/**
* @cfg {Array} views The set of views to load for this Application. Each view is expected to exist inside the
* *app/view* directory and define a class following the convention AppName.view.ViewName. For example, in the
* code below, the classes *AppName.view.Users*, *AppName.view.Groups* and *AppName.view.Products* will be loaded.
* Note that we are able to specify either the full class name (as with *AppName.view.Products*) or just the
* final part of the class name and leave Application to automatically prepend *AppName.view.* to each:
*
* views: [
* 'Users',
* 'Groups',
* 'AppName.view.Products',
* 'SomeCustomNamespace.view.Orders'
* ]
* @accessor
*/
views: []
},
/**
* Constructs a new Controller instance
*/
constructor: function(config) {
this.initConfig(config);
this.mixins.observable.constructor.call(this, config);
},
/**
* @cfg
* Called by the Controller's {@link #application} to initialize the Controller. This is always called before the
* {@link Ext.app.Application Application} launches, giving the Controller a chance to run any pre-launch logic.
* See also {@link #launch}, which is called after the {@link Ext.app.Application#launch Application's launch function}
*/
init: Ext.emptyFn,
/**
* @cfg
* Called by the Controller's {@link #application} immediately after the Application's own
* {@link Ext.app.Application#launch launch function} has been called. This is usually a good place to run any
* logic that has to run after the app UI is initialized. See also {@link #init}, which is called before the
* {@link Ext.app.Application#launch Application's launch function}.
*/
launch: Ext.emptyFn,
/**
* Convenient way to redirect to a new url. See {@link Ext.app.Application#redirectTo} for full usage information.
* @return {Object}
*/
redirectTo: function(place) {
return this.getApplication().redirectTo(place);
},
/**
* @private
* Executes an Ext.app.Action by giving it the correct before filters and kicking off execution
*/
execute: function(action, skipFilters) {
action.setBeforeFilters(this.getBefore()[action.getAction()]);
action.execute();
},
/**
* @private
* Massages the before filters into an array of function references for each controller action
*/
applyBefore: function(before) {
var filters, name, length, i;
for (name in before) {
filters = Ext.Array.from(before[name]);
length = filters.length;
for (i = 0; i < length; i++) {
filters[i] = this[filters[i]];
}
before[name] = filters;
}
return before;
},
/**
* @private
*/
applyControl: function(config) {
this.control(config, this);
return config;
},
/**
* @private
*/
applyRefs: function(refs) {
//<debug>
if (Ext.isArray(refs)) {
Ext.Logger.deprecate("In Sencha Touch 2 the refs config accepts an object but you have passed it an array.");
}
//</debug>
this.ref(refs);
return refs;
},
/**
* @private
* Adds any routes specified in this Controller to the global Application router
*/
applyRoutes: function(routes) {
var app = this instanceof Ext.app.Application ? this : this.getApplication(),
router = app.getRouter(),
route, url, config;
for (url in routes) {
route = routes[url];
config = {
controller: this.$className
};
if (Ext.isString(route)) {
config.action = route;
} else {
Ext.apply(config, route);
}
router.connect(url, config);
}
return routes;
},
/**
* @private
* As a convenience developers can locally qualify store names (e.g. 'MyStore' vs
* 'MyApp.store.MyStore'). This just makes sure everything ends up fully qualified
*/
applyStores: function(stores) {
return this.getFullyQualified(stores, 'store');
},
/**
* @private
* As a convenience developers can locally qualify model names (e.g. 'MyModel' vs
* 'MyApp.model.MyModel'). This just makes sure everything ends up fully qualified
*/
applyModels: function(models) {
return this.getFullyQualified(models, 'model');
},
/**
* @private
* As a convenience developers can locally qualify view names (e.g. 'MyView' vs
* 'MyApp.view.MyView'). This just makes sure everything ends up fully qualified
*/
applyViews: function(views) {
return this.getFullyQualified(views, 'view');
},
/**
* @private
* Returns the fully qualified name for any class name variant. This is used to find the FQ name for the model,
* view, controller, store and profiles listed in a Controller or Application.
* @param {String[]} items The array of strings to get the FQ name for
* @param {String} namespace If the name happens to be an application class, add it to this namespace
* @return {String} The fully-qualified name of the class
*/
getFullyQualified: function(items, namespace) {
var length = items.length,
appName = this.getApplication().getName(),
name, i;
for (i = 0; i < length; i++) {
name = items[i];
//we check name === appName to allow MyApp.profile.MyApp to exist
if (Ext.isString(name) && (Ext.Loader.getPrefix(name) === "" || name === appName)) {
items[i] = appName + '.' + namespace + '.' + name;
}
}
return items;
},
/**
* @private
*/
control: function(selectors) {
this.getApplication().control(selectors, this);
},
/**
* @private
* 1.x-inspired ref implementation
*/
ref: function(refs) {
var me = this,
refName, getterName, selector, info;
for (refName in refs) {
selector = refs[refName];
getterName = "get" + Ext.String.capitalize(refName);
if (!this[getterName]) {
if (Ext.isString(refs[refName])) {
info = {
ref: refName,
selector: selector
};
} else {
info = refs[refName];
}
this[getterName] = function(refName, info) {
var args = [refName, info];
return function() {
return me.getRef.apply(me, args.concat.apply(args, arguments));
};
}(refName, info);
}
this.references = this.references || [];
this.references.push(refName.toLowerCase());
}
},
/**
* @private
*/
getRef: function(ref, info, config) {
this.refCache = this.refCache || {};
info = info || {};
config = config || {};
Ext.apply(info, config);
if (info.forceCreate) {
return Ext.ComponentManager.create(info, 'component');
}
var me = this,
cached = me.refCache[ref];
if (!cached) {
me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
if (!cached && info.autoCreate) {
me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
}
if (cached) {
cached.on('destroy', function() {
me.refCache[ref] = null;
});
}
}
return cached;
},
/**
* @private
*/
hasRef: function(ref) {
return this.references && this.references.indexOf(ref.toLowerCase()) !== -1;
}
// <deprecated product=touch since=2.0>
,onClassExtended: function(cls, members) {
var prototype = this.prototype,
defaultConfig = prototype.config,
config = members.config || {},
arrayRefs = members.refs,
objectRefs = {},
stores = members.stores,
views = members.views,
format = Ext.String.format,
refItem, key, length, i, functionName;
// Convert deprecated properties in application into a config object
for (key in defaultConfig) {
if (key in members && key != "control") {
if (key == "refs") {
//we need to convert refs from the 1.x array-style to 2.x object-style
for (i = 0; i < arrayRefs.length; i++) {
refItem = arrayRefs[i];
objectRefs[refItem.ref] = refItem;
}
config.refs = objectRefs;
} else {
config[key] = members[key];
}
delete members[key];
// <debug warn>
Ext.Logger.deprecate(key + ' is deprecated as a property directly on the ' + this.$className + ' prototype. Please put it inside the config object.');
// </debug>
}
}
if (stores) {
length = stores.length;
config.stores = stores;
for (i = 0; i < length; i++) {
functionName = format("get{0}Store", Ext.String.capitalize(stores[i]));
prototype[functionName] = function(name) {
return function() {
return Ext.StoreManager.lookup(name);
};
}(stores[i]);
}
}
if (views) {
length = views.length;
config.views = views;
for (i = 0; i < length; i++) {
functionName = format("get{0}View", views[i]);
prototype[functionName] = function(name) {
return function() {
return Ext.ClassManager.classes[format("{0}.view.{1}", this.getApplication().getName(), name)];
};
}(views[i]);
}
}
members.config = config;
},
/**
* Returns a reference to a Model.
* @param modelName
* @return {Object}
* @deprecated 2.0.0 Considered bad practice - please just use the Model name instead
* (e.g. `MyApp.model.User` vs `this.getModel('User')`).
*/
getModel: function(modelName) {
//<debug warn>
Ext.Logger.deprecate("getModel() is deprecated and considered bad practice - please just use the Model " +
"name instead (e.g. MyApp.model.User vs this.getModel('User'))");
//</debug>
var appName = this.getApplication().getName(),
classes = Ext.ClassManager.classes;
return classes[appName + '.model.' + modelName];
},
/**
* Returns a reference to another Controller.
* @param controllerName
* @param profile
* @return {Object}
* @deprecated 2.0.0 Considered bad practice - if you need to do this
* please use this.getApplication().getController() instead
*/
getController: function(controllerName, profile) {
//<debug warn>
Ext.Logger.deprecate("Ext.app.Controller#getController is deprecated and considered bad practice - " +
"please use this.getApplication().getController('someController') instead");
//</debug>
return this.getApplication().getController(controllerName, profile);
}
// </deprecated>
}, function() {
// <deprecated product=touch since=2.0>
Ext.regController = function(name, config) {
Ext.apply(config, {
extend: 'Ext.app.Controller'
});
Ext.Logger.deprecate(
'[Ext.app.Controller] Ext.regController is deprecated, please use Ext.define to define a Controller as ' +
'with any other class. For more information see the Touch 1.x -> 2.x migration guide'
);
Ext.define('controller.' + name, config);
};
// </deprecated>
});

View File

@@ -0,0 +1,112 @@
/**
* @author Ed Spencer
* @private
*
* Manages the stack of {@link Ext.app.Action} instances that have been decoded, pushes new urls into the browser's
* location object and listens for changes in url, firing the {@link #change} event when a change is detected.
*
* This is tied to an {@link Ext.app.Application Application} instance. The Application performs all of the
* interactions with the History object, no additional integration should be required.
*/
Ext.define('Ext.app.History', {
mixins: ['Ext.mixin.Observable'],
/**
* @event change
* Fires when a change in browser url is detected
* @param {String} url The new url, after the hash (e.g. http://myapp.com/#someUrl returns 'someUrl')
*/
config: {
/**
* @cfg {Array} actions The stack of {@link Ext.app.Action action} instances that have occurred so far
*/
actions: [],
/**
* @cfg {Boolean} updateUrl `true` to automatically update the browser's url when {@link #add} is called.
*/
updateUrl: true,
/**
* @cfg {String} token The current token as read from the browser's location object.
*/
token: ''
},
constructor: function(config) {
if (Ext.feature.has.History) {
window.addEventListener('hashchange', Ext.bind(this.detectStateChange, this));
}
else {
this.setToken(window.location.hash.substr(1));
setInterval(Ext.bind(this.detectStateChange, this), 100);
}
this.initConfig(config);
},
/**
* Adds an {@link Ext.app.Action Action} to the stack, optionally updating the browser's url and firing the
* {@link #change} event.
* @param {Ext.app.Action} action The Action to add to the stack.
* @param {Boolean} silent Cancels the firing of the {@link #change} event if `true`.
*/
add: function(action, silent) {
this.getActions().push(Ext.factory(action, Ext.app.Action));
var url = action.getUrl();
if (this.getUpdateUrl()) {
// history.pushState({}, action.getTitle(), "#" + action.getUrl());
this.setToken(url);
window.location.hash = url;
}
if (silent !== true) {
this.fireEvent('change', url);
}
this.setToken(url);
},
/**
* Navigate to the previous active action. This changes the page url.
*/
back: function() {
var actions = this.getActions(),
previousAction = actions[actions.length - 2],
app = previousAction.getController().getApplication();
actions.pop();
app.redirectTo(previousAction.getUrl());
},
/**
* @private
*/
applyToken: function(token) {
return token[0] == '#' ? token.substr(1) : token;
},
/**
* @private
*/
detectStateChange: function() {
var newToken = this.applyToken(window.location.hash),
oldToken = this.getToken();
if (newToken != oldToken) {
this.onStateChange();
this.setToken(newToken);
}
},
/**
* @private
*/
onStateChange: function() {
this.fireEvent('change', window.location.hash.substr(1));
}
});

View File

@@ -0,0 +1,251 @@
/**
* @author Ed Spencer
*
* 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](#!/guide/profiles).
*
* @aside guide profiles
*/
Ext.define('Ext.app.Profile', {
mixins: {
observable: "Ext.mixin.Observable"
},
config: {
/**
* @cfg {String} namespace The namespace that this Profile's classes can be found in. Defaults to the lowercased
* 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
*/
namespace: 'auto',
/**
* @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
*/
name: 'auto',
/**
* @cfg {Array} controllers Any additional {@link Ext.app.Application#controllers 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: [],
/**
* @cfg {Array} 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: [],
/**
* @cfg {Array} views Any 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*.
* @accessor
*/
views: [],
/**
* @cfg {Array} 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: [],
/**
* @cfg {Ext.app.Application} application The {@link Ext.app.Application Application} instance that this
* Profile is bound to. This is set automatically.
* @accessor
* @readonly
*/
application: null
},
/**
* 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;
},
/**
* @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,
/**
* @private
*/
applyNamespace: function(name) {
if (name == 'auto') {
name = this.getName();
}
return name.toLowerCase();
},
/**
* @private
*/
applyName: function(name) {
if (name == 'auto') {
var pieces = this.$className.split('.');
name = pieces[pieces.length - 1];
}
return name;
},
/**
* @private
* Computes the full class names of any specified model, view, controller and store dependencies, returns them in
* an object map for easy loading
*/
getDependencies: function() {
var allClasses = [],
format = Ext.String.format,
appName = this.getApplication().getName(),
namespace = this.getNamespace(),
map = {
model: this.getModels(),
view: this.getViews(),
controller: this.getControllers(),
store: this.getStores()
},
classType, classNames, fullyQualified;
for (classType in map) {
classNames = [];
Ext.each(map[classType], function(className) {
if (Ext.isString(className)) {
//we check name === appName to allow MyApp.profile.MyApp to exist
if (Ext.isString(className) && (Ext.Loader.getPrefix(className) === "" || className === appName)) {
className = appName + '.' + classType + '.' + namespace + '.' + className;
}
classNames.push(className);
allClasses.push(className);
}
}, this);
map[classType] = classNames;
}
map.all = allClasses;
return map;
}
});

208
OfficeWeb/vendor/touch/src/app/Route.js vendored Normal file
View File

@@ -0,0 +1,208 @@
/**
* @author Ed Spencer
* @private
*
* Represents a mapping between a url and a controller/action pair. May also contain additional params. This is a
* private internal class that should not need to be used by end-developer code. Its API and existence are subject to
* change so use at your own risk.
*
* For information on how to use routes we suggest reading the following guides:
*
* - [Using History Support](#!/guide/history_support)
* - [Intro to Applications](#!/guide/apps_intro)
* - [Using Controllers](#!/guide/controllers)
*
*/
Ext.define('Ext.app.Route', {
config: {
/**
* @cfg {Object} conditions Optional set of conditions for each token in the url string. Each key should be one
* of the tokens, each value should be a regex that the token should accept. For example, if you have a Route
* with a url like "files/:fileName" and you want it to match urls like "files/someImage.jpg" then you can set
* these conditions to allow the :fileName token to accept strings containing a period ("."):
*
* conditions: {
* ':fileName': "[0-9a-zA-Z\.]+"
* }
*
*/
conditions: {},
/**
* @cfg {String} url (required) The url regex to match against.
*/
url: null,
/**
* @cfg {String} controller The name of the Controller whose {@link #action} will be called if this route is
* matched.
*/
controller: null,
/**
* @cfg {String} action The name of the action that will be called on the {@link #controller} if this route is
* matched.
*/
action: null,
/**
* @private
* @cfg {Boolean} initialized Indicates whether or not this Route has been initialized. We don't initialize
* straight away so as to save unnecessary processing.
*/
initialized: false
},
constructor: function(config) {
this.initConfig(config);
},
/**
* Attempts to recognize a given url string and return controller/action pair for it.
* @param {String} url The url to recognize.
* @return {Object/Boolean} The matched data, or `false` if no match.
*/
recognize: function(url) {
if (!this.getInitialized()) {
this.initialize();
}
if (this.recognizes(url)) {
var matches = this.matchesFor(url),
args = url.match(this.matcherRegex);
args.shift();
return Ext.applyIf(matches, {
controller: this.getController(),
action : this.getAction(),
historyUrl: url,
args : args
});
}
},
/**
* @private
* Sets up the relevant regular expressions used to match against this route.
*/
initialize: function() {
/*
* The regular expression we use to match a segment of a route mapping
* this will recognize segments starting with a colon,
* e.g. on 'namespace/:controller/:action', :controller and :action will be recognized
*/
this.paramMatchingRegex = new RegExp(/:([0-9A-Za-z\_]*)/g);
/*
* Converts a route string into an array of symbols starting with a colon. e.g.
* ":controller/:action/:id" => [':controller', ':action', ':id']
*/
this.paramsInMatchString = this.getUrl().match(this.paramMatchingRegex) || [];
this.matcherRegex = this.createMatcherRegex(this.getUrl());
this.setInitialized(true);
},
/**
* @private
* Returns true if this Route matches the given url string
* @param {String} url The url to test
* @return {Boolean} True if this Route recognizes the url
*/
recognizes: function(url) {
return this.matcherRegex.test(url);
},
/**
* @private
* Returns a hash of matching url segments for the given url.
* @param {String} url The url to extract matches for
* @return {Object} matching url segments
*/
matchesFor: function(url) {
var params = {},
keys = this.paramsInMatchString,
values = url.match(this.matcherRegex),
length = keys.length,
i;
//first value is the entire match so reject
values.shift();
for (i = 0; i < length; i++) {
params[keys[i].replace(":", "")] = values[i];
}
return params;
},
/**
* @private
* Returns an array of matching url segments for the given url.
* @param {String} url The url to extract matches for
* @return {Array} matching url segments
*/
argsFor: function(url) {
var args = [],
keys = this.paramsInMatchString,
values = url.match(this.matcherRegex),
length = keys.length,
i;
//first value is the entire match so reject
values.shift();
for (i = 0; i < length; i++) {
args.push(keys[i].replace(':', ""));
params[keys[i].replace(":", "")] = values[i];
}
return params;
},
/**
* @private
* Constructs a url for the given config object by replacing wildcard placeholders in the Route's url
* @param {Object} config The config object
* @return {String} The constructed url
*/
urlFor: function(config) {
var url = this.getUrl();
for (var key in config) {
url = url.replace(":" + key, config[key]);
}
return url;
},
/**
* @private
* Takes the configured url string including wildcards and returns a regex that can be used to match
* against a url
* @param {String} url The url string
* @return {RegExp} The matcher regex
*/
createMatcherRegex: function(url) {
/**
* Converts a route string into an array of symbols starting with a colon. e.g.
* ":controller/:action/:id" => [':controller', ':action', ':id']
*/
var paramsInMatchString = this.paramsInMatchString,
length = paramsInMatchString.length,
i, cond, matcher;
for (i = 0; i < length; i++) {
cond = this.getConditions()[paramsInMatchString[i]];
matcher = Ext.util.Format.format("({0})", cond || "[%a-zA-Z0-9\-\\_\\s,]+");
url = url.replace(new RegExp(paramsInMatchString[i]), matcher);
}
//we want to match the whole string, so include the anchors
return new RegExp("^" + url + "$");
}
});

137
OfficeWeb/vendor/touch/src/app/Router.js vendored Normal file
View File

@@ -0,0 +1,137 @@
/**
* @author Ed Spencer
* @private
*
* The Router is an ordered set of route definitions that decode a url into a controller function to execute. Each
* route defines a type of url to match, along with the controller function to call if it is matched. The Router is
* usually managed exclusively by an {@link Ext.app.Application Application}, which also uses a
* {@link Ext.app.History History} instance to find out when the browser's url has changed.
*
* Routes are almost always defined inside a {@link Ext.app.Controller Controller}, as opposed to on the Router itself.
* End-developers should not usually need to interact directly with the Router as the Application and Controller
* classes manage everything automatically. See the {@link Ext.app.Controller Controller documentation} for more
* information on specifying routes.
*/
Ext.define('Ext.app.Router', {
requires: ['Ext.app.Route'],
config: {
/**
* @cfg {Array} routes The set of routes contained within this Router.
* @readonly
*/
routes: [],
/**
* @cfg {Object} defaults Default configuration options for each Route connected to this Router.
*/
defaults: {
action: 'index'
}
},
constructor: function(config) {
this.initConfig(config);
},
/**
* Connects a url-based route to a controller/action pair plus additional params.
* @param {String} url The url to recognize.
*/
connect: function(url, params) {
params = Ext.apply({url: url}, params || {}, this.getDefaults());
var route = Ext.create('Ext.app.Route', params);
this.getRoutes().push(route);
return route;
},
/**
* Recognizes a url string connected to the Router, return the controller/action pair plus any additional
* config associated with it.
* @param {String} url The url to recognize.
* @return {Object/undefined} If the url was recognized, the controller and action to call, else `undefined`.
*/
recognize: function(url) {
var routes = this.getRoutes(),
length = routes.length,
i, result;
for (i = 0; i < length; i++) {
result = routes[i].recognize(url);
if (result !== undefined) {
return result;
}
}
return undefined;
},
/**
* Convenience method which just calls the supplied function with the Router instance. Example usage:
*
* Ext.Router.draw(function(map) {
* map.connect('activate/:token', {controller: 'users', action: 'activate'});
* map.connect('home', {controller: 'index', action: 'home'});
* });
*
* @param {Function} fn The fn to call
*/
draw: function(fn) {
fn.call(this, this);
},
/**
* @private
*/
clear: function() {
this.setRoutes([]);
}
}, function() {
//<deprecated product=touch since=2.0>
/**
* Restores compatibility for the old `Ext.Router.draw` syntax. This needs to be here because apps often include
* _routes.js_ just after _app.js_, so this is our only opportunity to hook this in. There is a small piece of code
* inside Application's {@link Ext.app.Application#onDependenciesLoaded onDependenciesLoaded} that sets up the other end of this.
* @singleton
* @private
*/
Ext.Router = {};
var drawStack = [];
/**
* Application's {@link Ext.app.Application#onDependenciesLoaded onDependenciesLoaded} has a deprecated-wrapped line that calls this. Basic idea is that once an
* app has been instantiated we set that at Ext.Router's `appInstance` and then redirect any calls to
* {@link Ext.app.Router#draw Ext.Router.draw} to that app's Router. We keep a `drawStack` above so that we can call {@link Ext.app.Router#draw Ext.Router.draw} one or
* more times before the application is even instantiated and it will simply link it up once everything is
* present.
*/
Ext.Router.setAppInstance = function(app) {
Ext.Router.appInstance = app;
if (drawStack.length > 0) {
Ext.each(drawStack, Ext.Router.draw);
}
};
Ext.Router.draw = function(mapperFn) {
Ext.Logger.deprecate(
'Ext.Router.map is deprecated, please define your routes inline inside each Controller. ' +
'Please see the 1.x -> 2.x migration guide for more details.'
);
var app = Ext.Router.appInstance,
router;
if (app) {
router = app.getRouter();
mapperFn(router);
} else {
drawStack.push(mapperFn);
}
};
//</deprecated>
});