You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
375 lines
12 KiB
375 lines
12 KiB
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); |
|
} |
|
} |
|
});
|
|
|