dojox
Version:
Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.
376 lines (348 loc) • 11.8 kB
JavaScript
define(["require", "dojo/_base/kernel", "dojo/_base/lang", "dojo/_base/declare", "dojo/_base/config",
"dojo/_base/window", "dojo/Evented", "dojo/Deferred", "dojo/when", "dojo/has", "dojo/on", "dojo/ready",
"dojo/dom-construct", "dojo/dom-attr", "./utils/model", "./utils/nls", "./module/lifecycle",
"./utils/hash", "./utils/constraints", "./utils/config"],
function(require, kernel, lang, declare, config, win, Evented, Deferred, when, has, on, ready, domConstruct, domAttr,
model, nls, lifecycle, hash, constraints, configUtils){
has.add("app-log-api", (config["app"] || {}).debugApp);
var Application = declare(Evented, {
constructor: function(params, node){
declare.safeMixin(this, params);
this.params = params;
this.id = params.id;
this.defaultView = params.defaultView;
this.controllers = [];
this.children = {};
this.loadedModels = {};
this.loadedStores = {};
// Create a new domNode and append to body
// Need to bind startTransition event on application domNode,
// Because dojox/mobile/ViewController bind startTransition event on document.body
// Make application's root domNode id unique because this id can be visited by window namespace on Chrome 18.
this.setDomNode(domConstruct.create("div", {
id: this.id+"_Root",
style: "width:100%; height:100%; overflow-y:hidden; overflow-x:hidden;"
}));
node.appendChild(this.domNode);
},
createDataStore: function(params){
// summary:
// Create data store instance
//
// params: Object
// data stores configuration.
if(params.stores){
//create stores in the configuration.
for(var item in params.stores){
if(item.charAt(0) !== "_"){//skip the private properties
var type = params.stores[item].type ? params.stores[item].type : "dojo/store/Memory";
var config = {};
if(params.stores[item].params){
lang.mixin(config, params.stores[item].params);
}
// we assume the store is here through dependencies
try{
var storeCtor = require(type);
}catch(e){
throw new Error(type+" must be listed in the dependencies");
}
if(config.data && lang.isString(config.data)){
//get the object specified by string value of data property
//cannot assign object literal or reference to data property
//because json.ref will generate __parent to point to its parent
//and will cause infinitive loop when creating StatefulModel.
config.data = lang.getObject(config.data);
}
if(params.stores[item].observable){
try{
var observableCtor = require("dojo/store/Observable");
}catch(e){
throw new Error("dojo/store/Observable must be listed in the dependencies");
}
params.stores[item].store = observableCtor(new storeCtor(config));
}else{
params.stores[item].store = new storeCtor(config);
}
this.loadedStores[item] = params.stores[item].store;
}
}
}
},
createControllers: function(controllers){
// summary:
// Create controller instance
//
// controllers: Array
// controller configuration array.
// returns:
// controllerDeferred object
if(controllers){
var requireItems = [];
for(var i = 0; i < controllers.length; i++){
requireItems.push(controllers[i]);
}
var def = new Deferred();
var requireSignal;
try{
requireSignal = require.on ? require.on("error", function(error){
if(def.isResolved() || def.isRejected()){
return;
}
def.reject("load controllers error.");
if(requireSignal){
requireSignal.remove();
}
}) : null;
require(requireItems, function(){
def.resolve.call(def, arguments);
if(requireSignal){
requireSignal.remove();
}
});
}catch(e){
def.reject(e);
if(requireSignal){
requireSignal.remove();
}
}
var controllerDef = new Deferred();
when(def, lang.hitch(this, function(){
for(var i = 0; i < arguments[0].length; i++){
// instantiate controllers, set Application object, and perform auto binding
this.controllers.push((new arguments[0][i](this)).bind());
}
controllerDef.resolve(this);
}), function(){
//require def error, reject loadChildDeferred
controllerDef.reject("load controllers error.");
});
return controllerDef;
}
},
trigger: function(event, params){
// summary:
// trigger an event. Deprecated, use emit instead.
//
// event: String
// event name. The event is binded by controller.bind() method.
// params: Object
// event params.
kernel.deprecated("dojox.app.Application.trigger", "Use dojox.app.Application.emit instead", "2.0");
this.emit(event, params);
},
// setup default view and Controllers and startup the default view
start: function(){
//
//create application level data store
this.createDataStore(this.params);
// create application level data model
var loadModelLoaderDeferred = new Deferred();
var createPromise;
try{
createPromise = model(this.params.models, this, this);
}catch(e){
loadModelLoaderDeferred.reject(e);
return loadModelLoaderDeferred.promise;
}
when(createPromise, lang.hitch(this, function(models){
// if models is an array it comes from dojo/promise/all. Each array slot contains the same result object
// so pick slot 0.
this.loadedModels = lang.isArray(models)?models[0]:models;
this.setupControllers();
// if available load root NLS
when(nls(this.params), lang.hitch(this, function(nls){
if(nls){
lang.mixin(this.nls = {}, nls);
}
this.startup();
}));
}), function(){
loadModelLoaderDeferred.reject("load model error.")
});
},
setDomNode: function(domNode){
var oldNode = this.domNode;
this.domNode = domNode;
this.emit("app-domNode", {
oldNode: oldNode,
newNode: domNode
});
},
setupControllers: function(){
// create application controller instance
// move set _startView operation from history module to application
var currentHash = window.location.hash;
// this._startView = (((currentHash && currentHash.charAt(0) == "#") ? currentHash.substr(1) : currentHash) || this.defaultView).split('&')[0];
this._startView = hash.getTarget(currentHash, this.defaultView);
this._startParams = hash.getParams(currentHash);
},
startup: function(){
// load controllers and views
//
this.selectedChildren = {};
var controllers = this.createControllers(this.params.controllers);
// constraint on app
if(this.hasOwnProperty("constraint")){
constraints.register(this.params.constraints);
}else{
this.constraint = "center";
}
var emitLoad = function(){
// emit "app-load" event and let controller to load view.
this.emit("app-load", {
viewId: this.defaultView,
initLoad: true,
params: this._startParams,
callback: lang.hitch(this, function (){
this.emit("app-transition", {
viewId: this.defaultView,
forceTransitionNone: true, // we want to avoid the transition on the first display for the defaultView
opts: { params: this._startParams }
});
if(this.defaultView !== this._startView){
// transition to startView. If startView==defaultView, that means initial the default view.
this.emit("app-transition", {
viewId: this._startView,
opts: { params: this._startParams }
});
}
this.setStatus(this.lifecycle.STARTED);
})
});
};
when(controllers, lang.hitch(this, function(){
if(this.template){
// emit "app-init" event so that the Load controller can initialize root view
this.emit("app-init", {
app: this, // pass the app into the View so it can have easy access to app
name: this.name,
type: this.type,
parent: this,
templateString: this.templateString,
controller: this.controller,
callback: lang.hitch(this, function(view){
this.setDomNode(view.domNode);
emitLoad.call(this);
})
});
}else{
emitLoad.call(this);
}
}));
}
});
function generateApp(config, node){
// summary:
// generate the application
//
// config: Object
// app config
// node: domNode
// domNode.
var path;
// call configProcessHas to process any has blocks in the config
config = configUtils.configProcessHas(config);
if(!config.loaderConfig){
config.loaderConfig = {};
}
if(!config.loaderConfig.paths){
config.loaderConfig.paths = {};
}
if(!config.loaderConfig.paths["app"]){
// Register application module path
path = window.location.pathname;
if(path.charAt(path.length) != "/"){
path = path.split("/");
path.pop();
path = path.join("/");
}
config.loaderConfig.paths["app"] = path;
}
require(config.loaderConfig);
if(!config.modules){
config.modules = [];
}
// add dojox/app lifecycle module by default
config.modules.push("./module/lifecycle");
var modules = config.modules.concat(config.dependencies?config.dependencies:[]);
if(config.template){
path = config.template;
if(path.indexOf("./") == 0){
path = "app/"+path;
}
modules.push("dojo/text!" + path);
}
require(modules, function(){
var modules = [Application];
for(var i = 0; i < config.modules.length; i++){
modules.push(arguments[i]);
}
if(config.template){
var ext = {
templateString: arguments[arguments.length - 1]
}
}
App = declare(modules, ext);
ready(function(){
var app = new App(config, node || win.body());
if(has("app-log-api")){
app.log = function(){
// summary:
// If config is set to turn on app logging, then log msg to the console
//
// arguments:
// the message to be logged,
// all but the last argument will be treated as Strings and be concatenated together,
// the last argument can be an object it will be added as an argument to the console.log
var msg = "";
try{
for(var i = 0; i < arguments.length-1; i++){
msg = msg + arguments[i];
}
console.log(msg,arguments[arguments.length-1]);
}catch(e){}
};
}else{
app.log = function(){}; // noop
}
app.transitionToView = function(/*DomNode*/target, /*Object*/transitionOptions, /*Event?*/triggerEvent){
// summary:
// A convenience function to fire the transition event to transition to the view.
//
// target:
// The DOM node that initiates the transition (for example a ListItem).
// transitionOptions:
// Contains the transition options.
// triggerEvent:
// The event that triggered the transition (for example a touch event on a ListItem).
var opts = {bubbles:true, cancelable:true, detail: transitionOptions, triggerEvent: triggerEvent || null};
on.emit(target,"startTransition", opts);
};
app.setStatus(app.lifecycle.STARTING);
// Create global namespace for application.
// The global name is application id. ie: modelApp
var globalAppName = app.id;
if(window[globalAppName]){
declare.safeMixin(app, window[globalAppName]);
}
window[globalAppName] = app;
app.start();
});
});
}
return function(config, node){
if(!config){
throw new Error("App Config Missing");
}
if(config.validate){
require(["dojox/json/schema", "dojox/json/ref", "dojo/text!dojox/application/schema/application.json"], function(schema, appSchema){
schema = dojox.json.ref.resolveJson(schema);
if(schema.validate(config, appSchema)){
generateApp(config, node);
}
});
}else{
generateApp(config, node);
}
}
});