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 | }); |
---|