[483] | 1 | define(["require", "dojo/_base/kernel", "dojo/_base/lang", "dojo/_base/declare", "dojo/_base/config", |
---|
| 2 | "dojo/_base/window", "dojo/Evented", "dojo/Deferred", "dojo/when", "dojo/has", "dojo/on", "dojo/ready", |
---|
| 3 | "dojo/dom-construct", "dojo/dom-attr", "./utils/model", "./utils/nls", "./module/lifecycle", |
---|
| 4 | "./utils/hash", "./utils/constraints", "./utils/config"], |
---|
| 5 | function(require, kernel, lang, declare, config, win, Evented, Deferred, when, has, on, ready, domConstruct, domAttr, |
---|
| 6 | model, nls, lifecycle, hash, constraints, configUtils){ |
---|
| 7 | |
---|
| 8 | has.add("app-log-api", (config["app"] || {}).debugApp); |
---|
| 9 | |
---|
| 10 | var Application = declare(Evented, { |
---|
| 11 | constructor: function(params, node){ |
---|
| 12 | lang.mixin(this, params); |
---|
| 13 | this.params = params; |
---|
| 14 | this.id = params.id; |
---|
| 15 | this.defaultView = params.defaultView; |
---|
| 16 | this.controllers = []; |
---|
| 17 | this.children = {}; |
---|
| 18 | this.loadedModels = {}; |
---|
| 19 | this.loadedStores = {}; |
---|
| 20 | // Create a new domNode and append to body |
---|
| 21 | // Need to bind startTransition event on application domNode, |
---|
| 22 | // Because dojox/mobile/ViewController bind startTransition event on document.body |
---|
| 23 | // Make application's root domNode id unique because this id can be visited by window namespace on Chrome 18. |
---|
| 24 | this.setDomNode(domConstruct.create("div", { |
---|
| 25 | id: this.id+"_Root", |
---|
| 26 | style: "width:100%; height:100%; overflow-y:hidden; overflow-x:hidden;" |
---|
| 27 | })); |
---|
| 28 | node.appendChild(this.domNode); |
---|
| 29 | }, |
---|
| 30 | |
---|
| 31 | createDataStore: function(params){ |
---|
| 32 | // summary: |
---|
| 33 | // Create data store instance |
---|
| 34 | // |
---|
| 35 | // params: Object |
---|
| 36 | // data stores configuration. |
---|
| 37 | |
---|
| 38 | if(params.stores){ |
---|
| 39 | //create stores in the configuration. |
---|
| 40 | for(var item in params.stores){ |
---|
| 41 | if(item.charAt(0) !== "_"){//skip the private properties |
---|
| 42 | var type = params.stores[item].type ? params.stores[item].type : "dojo/store/Memory"; |
---|
| 43 | var config = {}; |
---|
| 44 | if(params.stores[item].params){ |
---|
| 45 | lang.mixin(config, params.stores[item].params); |
---|
| 46 | } |
---|
| 47 | // we assume the store is here through dependencies |
---|
| 48 | try{ |
---|
| 49 | var storeCtor = require(type); |
---|
| 50 | }catch(e){ |
---|
| 51 | throw new Error(type+" must be listed in the dependencies"); |
---|
| 52 | } |
---|
| 53 | if(config.data && lang.isString(config.data)){ |
---|
| 54 | //get the object specified by string value of data property |
---|
| 55 | //cannot assign object literal or reference to data property |
---|
| 56 | //because json.ref will generate __parent to point to its parent |
---|
| 57 | //and will cause infinitive loop when creating StatefulModel. |
---|
| 58 | config.data = lang.getObject(config.data); |
---|
| 59 | } |
---|
| 60 | if(params.stores[item].observable){ |
---|
| 61 | try{ |
---|
| 62 | var observableCtor = require("dojo/store/Observable"); |
---|
| 63 | }catch(e){ |
---|
| 64 | throw new Error("dojo/store/Observable must be listed in the dependencies"); |
---|
| 65 | } |
---|
| 66 | params.stores[item].store = observableCtor(new storeCtor(config)); |
---|
| 67 | }else{ |
---|
| 68 | params.stores[item].store = new storeCtor(config); |
---|
| 69 | } |
---|
| 70 | this.loadedStores[item] = params.stores[item].store; |
---|
| 71 | } |
---|
| 72 | } |
---|
| 73 | } |
---|
| 74 | }, |
---|
| 75 | |
---|
| 76 | createControllers: function(controllers){ |
---|
| 77 | // summary: |
---|
| 78 | // Create controller instance |
---|
| 79 | // |
---|
| 80 | // controllers: Array |
---|
| 81 | // controller configuration array. |
---|
| 82 | // returns: |
---|
| 83 | // controllerDeferred object |
---|
| 84 | |
---|
| 85 | if(controllers){ |
---|
| 86 | var requireItems = []; |
---|
| 87 | for(var i = 0; i < controllers.length; i++){ |
---|
| 88 | requireItems.push(controllers[i]); |
---|
| 89 | } |
---|
| 90 | |
---|
| 91 | var def = new Deferred(); |
---|
| 92 | var requireSignal; |
---|
| 93 | try{ |
---|
| 94 | requireSignal = require.on("error", function(error){ |
---|
| 95 | if(def.isResolved() || def.isRejected()){ |
---|
| 96 | return; |
---|
| 97 | } |
---|
| 98 | def.reject("load controllers error."); |
---|
| 99 | requireSignal.remove(); |
---|
| 100 | }); |
---|
| 101 | require(requireItems, function(){ |
---|
| 102 | def.resolve.call(def, arguments); |
---|
| 103 | requireSignal.remove(); |
---|
| 104 | }); |
---|
| 105 | }catch(e){ |
---|
| 106 | def.reject(e); |
---|
| 107 | if(requireSignal){ |
---|
| 108 | requireSignal.remove(); |
---|
| 109 | } |
---|
| 110 | } |
---|
| 111 | |
---|
| 112 | var controllerDef = new Deferred(); |
---|
| 113 | when(def, lang.hitch(this, function(){ |
---|
| 114 | for(var i = 0; i < arguments[0].length; i++){ |
---|
| 115 | // instantiate controllers, set Application object, and perform auto binding |
---|
| 116 | this.controllers.push((new arguments[0][i](this)).bind()); |
---|
| 117 | } |
---|
| 118 | controllerDef.resolve(this); |
---|
| 119 | }), function(){ |
---|
| 120 | //require def error, reject loadChildDeferred |
---|
| 121 | controllerDef.reject("load controllers error."); |
---|
| 122 | }); |
---|
| 123 | return controllerDef; |
---|
| 124 | } |
---|
| 125 | }, |
---|
| 126 | |
---|
| 127 | trigger: function(event, params){ |
---|
| 128 | // summary: |
---|
| 129 | // trigger an event. Deprecated, use emit instead. |
---|
| 130 | // |
---|
| 131 | // event: String |
---|
| 132 | // event name. The event is binded by controller.bind() method. |
---|
| 133 | // params: Object |
---|
| 134 | // event params. |
---|
| 135 | kernel.deprecated("dojox.app.Application.trigger", "Use dojox.app.Application.emit instead", "2.0"); |
---|
| 136 | this.emit(event, params); |
---|
| 137 | }, |
---|
| 138 | |
---|
| 139 | // setup default view and Controllers and startup the default view |
---|
| 140 | start: function(){ |
---|
| 141 | // |
---|
| 142 | //create application level data store |
---|
| 143 | this.createDataStore(this.params); |
---|
| 144 | |
---|
| 145 | // create application level data model |
---|
| 146 | var loadModelLoaderDeferred = new Deferred(); |
---|
| 147 | var createPromise; |
---|
| 148 | try{ |
---|
| 149 | createPromise = model(this.params.models, this, this); |
---|
| 150 | }catch(e){ |
---|
| 151 | loadModelLoaderDeferred.reject(e); |
---|
| 152 | return loadModelLoaderDeferred.promise; |
---|
| 153 | } |
---|
| 154 | when(createPromise, lang.hitch(this, function(models){ |
---|
| 155 | // if models is an array it comes from dojo/promise/all. Each array slot contains the same result object |
---|
| 156 | // so pick slot 0. |
---|
| 157 | this.loadedModels = lang.isArray(models)?models[0]:models; |
---|
| 158 | this.setupControllers(); |
---|
| 159 | // if available load root NLS |
---|
| 160 | when(nls(this.params), lang.hitch(this, function(nls){ |
---|
| 161 | if(nls){ |
---|
| 162 | lang.mixin(this.nls = {}, nls); |
---|
| 163 | } |
---|
| 164 | this.startup(); |
---|
| 165 | })); |
---|
| 166 | }), function(){ |
---|
| 167 | loadModelLoaderDeferred.reject("load model error.") |
---|
| 168 | }); |
---|
| 169 | }, |
---|
| 170 | |
---|
| 171 | setDomNode: function(domNode){ |
---|
| 172 | var oldNode = this.domNode; |
---|
| 173 | this.domNode = domNode; |
---|
| 174 | this.emit("app-domNode", { |
---|
| 175 | oldNode: oldNode, |
---|
| 176 | newNode: domNode |
---|
| 177 | }); |
---|
| 178 | }, |
---|
| 179 | |
---|
| 180 | setupControllers: function(){ |
---|
| 181 | // create application controller instance |
---|
| 182 | // move set _startView operation from history module to application |
---|
| 183 | var currentHash = window.location.hash; |
---|
| 184 | // this._startView = (((currentHash && currentHash.charAt(0) == "#") ? currentHash.substr(1) : currentHash) || this.defaultView).split('&')[0]; |
---|
| 185 | this._startView = hash.getTarget(currentHash, this.defaultView); |
---|
| 186 | this._startParams = hash.getParams(currentHash); |
---|
| 187 | }, |
---|
| 188 | |
---|
| 189 | startup: function(){ |
---|
| 190 | // load controllers and views |
---|
| 191 | // |
---|
| 192 | this.selectedChildren = {}; |
---|
| 193 | var controllers = this.createControllers(this.params.controllers); |
---|
| 194 | // constraint on app |
---|
| 195 | if(this.hasOwnProperty("constraint")){ |
---|
| 196 | constraints.register(this.params.constraints); |
---|
| 197 | }else{ |
---|
| 198 | this.constraint = "center"; |
---|
| 199 | } |
---|
| 200 | var emitLoad = function(){ |
---|
| 201 | // emit "app-load" event and let controller to load view. |
---|
| 202 | this.emit("app-load", { |
---|
| 203 | viewId: this.defaultView, |
---|
| 204 | initLoad: true, |
---|
| 205 | params: this._startParams, |
---|
| 206 | callback: lang.hitch(this, function (){ |
---|
| 207 | this.emit("app-transition", { |
---|
| 208 | viewId: this.defaultView, |
---|
| 209 | forceTransitionNone: true, // we want to avoid the transition on the first display for the defaultView |
---|
| 210 | opts: { params: this._startParams } |
---|
| 211 | }); |
---|
| 212 | if(this.defaultView !== this._startView){ |
---|
| 213 | // transition to startView. If startView==defaultView, that means initial the default view. |
---|
| 214 | this.emit("app-transition", { |
---|
| 215 | viewId: this._startView, |
---|
| 216 | opts: { params: this._startParams } |
---|
| 217 | }); |
---|
| 218 | } |
---|
| 219 | this.setStatus(this.lifecycle.STARTED); |
---|
| 220 | }) |
---|
| 221 | }); |
---|
| 222 | }; |
---|
| 223 | when(controllers, lang.hitch(this, function(){ |
---|
| 224 | if(this.template){ |
---|
| 225 | // emit "app-init" event so that the Load controller can initialize root view |
---|
| 226 | this.emit("app-init", { |
---|
| 227 | app: this, // pass the app into the View so it can have easy access to app |
---|
| 228 | name: this.name, |
---|
| 229 | type: this.type, |
---|
| 230 | parent: this, |
---|
| 231 | templateString: this.templateString, |
---|
| 232 | controller: this.controller, |
---|
| 233 | callback: lang.hitch(this, function(view){ |
---|
| 234 | this.setDomNode(view.domNode); |
---|
| 235 | emitLoad.call(this); |
---|
| 236 | }) |
---|
| 237 | }); |
---|
| 238 | }else{ |
---|
| 239 | emitLoad.call(this); |
---|
| 240 | } |
---|
| 241 | })); |
---|
| 242 | } |
---|
| 243 | }); |
---|
| 244 | |
---|
| 245 | function generateApp(config, node){ |
---|
| 246 | // summary: |
---|
| 247 | // generate the application |
---|
| 248 | // |
---|
| 249 | // config: Object |
---|
| 250 | // app config |
---|
| 251 | // node: domNode |
---|
| 252 | // domNode. |
---|
| 253 | var path; |
---|
| 254 | |
---|
| 255 | // call configProcessHas to process any has blocks in the config |
---|
| 256 | config = configUtils.configProcessHas(config); |
---|
| 257 | |
---|
| 258 | if(!config.loaderConfig){ |
---|
| 259 | config.loaderConfig = {}; |
---|
| 260 | } |
---|
| 261 | if(!config.loaderConfig.paths){ |
---|
| 262 | config.loaderConfig.paths = {}; |
---|
| 263 | } |
---|
| 264 | if(!config.loaderConfig.paths["app"]){ |
---|
| 265 | // Register application module path |
---|
| 266 | path = window.location.pathname; |
---|
| 267 | if(path.charAt(path.length) != "/"){ |
---|
| 268 | path = path.split("/"); |
---|
| 269 | path.pop(); |
---|
| 270 | path = path.join("/"); |
---|
| 271 | } |
---|
| 272 | config.loaderConfig.paths["app"] = path; |
---|
| 273 | } |
---|
| 274 | require(config.loaderConfig); |
---|
| 275 | |
---|
| 276 | if(!config.modules){ |
---|
| 277 | config.modules = []; |
---|
| 278 | } |
---|
| 279 | // add dojox/app lifecycle module by default |
---|
| 280 | config.modules.push("./module/lifecycle"); |
---|
| 281 | var modules = config.modules.concat(config.dependencies?config.dependencies:[]); |
---|
| 282 | |
---|
| 283 | if(config.template){ |
---|
| 284 | path = config.template; |
---|
| 285 | if(path.indexOf("./") == 0){ |
---|
| 286 | path = "app/"+path; |
---|
| 287 | } |
---|
| 288 | modules.push("dojo/text!" + path); |
---|
| 289 | } |
---|
| 290 | |
---|
| 291 | require(modules, function(){ |
---|
| 292 | var modules = [Application]; |
---|
| 293 | for(var i = 0; i < config.modules.length; i++){ |
---|
| 294 | modules.push(arguments[i]); |
---|
| 295 | } |
---|
| 296 | |
---|
| 297 | if(config.template){ |
---|
| 298 | var ext = { |
---|
| 299 | templateString: arguments[arguments.length - 1] |
---|
| 300 | } |
---|
| 301 | } |
---|
| 302 | App = declare(modules, ext); |
---|
| 303 | |
---|
| 304 | ready(function(){ |
---|
| 305 | var app = new App(config, node || win.body()); |
---|
| 306 | |
---|
| 307 | if(has("app-log-api")){ |
---|
| 308 | app.log = function(){ |
---|
| 309 | // summary: |
---|
| 310 | // If config is set to turn on app logging, then log msg to the console |
---|
| 311 | // |
---|
| 312 | // arguments: |
---|
| 313 | // the message to be logged, |
---|
| 314 | // all but the last argument will be treated as Strings and be concatenated together, |
---|
| 315 | // the last argument can be an object it will be added as an argument to the console.log |
---|
| 316 | var msg = ""; |
---|
| 317 | try{ |
---|
| 318 | for(var i = 0; i < arguments.length-1; i++){ |
---|
| 319 | msg = msg + arguments[i]; |
---|
| 320 | } |
---|
| 321 | console.log(msg,arguments[arguments.length-1]); |
---|
| 322 | }catch(e){} |
---|
| 323 | }; |
---|
| 324 | }else{ |
---|
| 325 | app.log = function(){}; // noop |
---|
| 326 | } |
---|
| 327 | |
---|
| 328 | app.transitionToView = function(/*DomNode*/target, /*Object*/transitionOptions, /*Event?*/triggerEvent){ |
---|
| 329 | // summary: |
---|
| 330 | // A convenience function to fire the transition event to transition to the view. |
---|
| 331 | // |
---|
| 332 | // target: |
---|
| 333 | // The DOM node that initiates the transition (for example a ListItem). |
---|
| 334 | // transitionOptions: |
---|
| 335 | // Contains the transition options. |
---|
| 336 | // triggerEvent: |
---|
| 337 | // The event that triggered the transition (for example a touch event on a ListItem). |
---|
| 338 | var opts = {bubbles:true, cancelable:true, detail: transitionOptions, triggerEvent: triggerEvent || null}; |
---|
| 339 | on.emit(target,"startTransition", opts); |
---|
| 340 | }; |
---|
| 341 | |
---|
| 342 | app.setStatus(app.lifecycle.STARTING); |
---|
| 343 | // Create global namespace for application. |
---|
| 344 | // The global name is application id. ie: modelApp |
---|
| 345 | var globalAppName = app.id; |
---|
| 346 | if(window[globalAppName]){ |
---|
| 347 | lang.mixin(app, window[globalAppName]); |
---|
| 348 | } |
---|
| 349 | window[globalAppName] = app; |
---|
| 350 | app.start(); |
---|
| 351 | }); |
---|
| 352 | }); |
---|
| 353 | } |
---|
| 354 | |
---|
| 355 | return function(config, node){ |
---|
| 356 | if(!config){ |
---|
| 357 | throw new Error("App Config Missing"); |
---|
| 358 | } |
---|
| 359 | |
---|
| 360 | if(config.validate){ |
---|
| 361 | require(["dojox/json/schema", "dojox/json/ref", "dojo/text!dojox/application/schema/application.json"], function(schema, appSchema){ |
---|
| 362 | schema = dojox.json.ref.resolveJson(schema); |
---|
| 363 | if(schema.validate(config, appSchema)){ |
---|
| 364 | generateApp(config, node); |
---|
| 365 | } |
---|
| 366 | }); |
---|
| 367 | }else{ |
---|
| 368 | generateApp(config, node); |
---|
| 369 | } |
---|
| 370 | } |
---|
| 371 | }); |
---|