1 (
  2 /**
  3  * Main module / namespace for the MMIR framework.
  4  * 
  5  * On initialization, a global module <code>window.mmir</code> is created.
  6  * 
  7  * If called multiple times, the existing module instance is returned.
  8  * 
  9  * If a function <code>require</code> exists, the module tries to registers itself
 10  * according to the <em>RequireJS</em> interface (using the default as its module name, i.e. "core").
 11  * 
 12  * @name mmir
 13  * @export initMmir as mmir
 14  * @class
 15  * @namespace
 16  * 
 17  * @returns the module instance <code>mmir</code>
 18  * 
 19  */
 20 function initMmir() {
 21 	
 22 	/**
 23 	 * the name of the global variable which will hold the core-module
 24 	 * @memberOf mmir.internal
 25 	 * @private
 26 	 */	
 27 	var CORE_NAME = typeof MMIR_CORE_NAME === 'string'? MMIR_CORE_NAME : 'mmir';
 28     
 29 	if(window[CORE_NAME]){
 30 		
 31 		//if window[CORE_NAME] is the core-module: register it and return
 32 		//(note: if it is not the core-module, its properties will be merged/copied to the core-module -> see below)
 33 		if(typeof window[CORE_NAME].startModule === 'string' && typeof define === 'function'){
 34 			define(function(){ return window[CORE_NAME]; });
 35 			return window[CORE_NAME];
 36 		}
 37 	}
 38 	
 39     
 40 	/**
 41 	 * STATE: state variable for indicating "doc is already loaded" (this needs to be set/reset manually)
 42 	 * @memberOf mmir.internal
 43 	 * @private
 44 	 */	
 45 	var _isReady = false;
 46 	/**
 47 	 * @memberOf mmir.internal
 48 	 * @private
 49 	 */
 50 	var _funcList = [];
 51 	/**
 52 	 * @memberOf mmir.internal
 53 	 * @private
 54 	 */
 55 	function dequeue () { return _funcList.shift(); };
 56 	/**
 57 	 * @memberOf mmir.internal
 58 	 * @private
 59 	 */
 60 	function isEmpty () { return _funcList.length === 0; };
 61 	/**
 62 	 * @param {Function} [func] OPTIONAL
 63 	 *			if func is present, func will be used instead of dequeueing a callback from the queue
 64 	 * @memberOf mmir.internal
 65 	 * @private
 66 	 */
 67 	function deqExex (func) {
 68 		if(!func){
 69 			func = dequeue();
 70 		}
 71 
 72 		//run function in context of the document (whith jQuery as argument)
 73 		func.call(mmir);
 74 	};
 75 	
 76 	/**
 77 	 * STATE: state variable for indicating "configs for requirejs are already applied"
 78 	 * @memberOf mmir.internal
 79 	 * @private
 80 	 */
 81 	var _isApplied = false;
 82 	/**
 83 	 * @memberOf mmir.internal
 84 	 * @private
 85 	 */
 86 	var _configList = [];
 87 	/**
 88 	 * Applies all <code>config</code>s (that were added by
 89 	 * {@link mmir.config}) to the requirejs instance.
 90 	 * 
 91 	 * @param {PlainObject} mainConfig
 92 	 * 			the main configuration for the framework
 93 	 * 			(this is used as reference for merging config options if necessary - see also mainConfig.js)
 94 	 * 
 95 	 * @memberOf mmir.internal
 96 	 * @private
 97 	 * 
 98 	 * @see #mergeModuleConfigs
 99 	 */
100 	function applyConfigs(mainConfig){
101 		
102 		if(typeof require === 'undefined'){
103 			return;
104 		}
105 		
106 		
107 		_isApplied = true;
108 		var conf;
109 		var confConfig, p;
110 		while(_configList.length > 0){
111 			
112 			conf = mergeModuleConfigs(_configList.shift(), mainConfig);
113 			
114 			//copy/remember all conf.config values that were not merged
115 			if(conf.config){
116 				for(p in conf.config){
117 					if(conf.config.hasOwnProperty(p) && typeof conf.config[p] !== 'undefined'){
118 						if(!confConfig){
119 							confConfig = {};
120 						}
121 						confConfig[p] = conf.config[p];
122 						//remove them from the conf, so that they do not overwrite anything in mainConfig.config:
123 						conf.config[p] = void(0);
124 					}
125 				}
126 			}
127 			
128 			require.config( conf );
129 		}
130 		
131 		//if there were non-merged conf.config-values:
132 		//   we cannot just apply these, since they would overwrite the mainConfig.config
133 		//   -> so we copy all (possibly) merged values from the mainConfig.config over
134 		//      and then apply all the conf.config-values at once here
135 		if(confConfig){
136 			
137 			for(p in mainConfig.config){
138 				if(mainConfig.config.hasOwnProperty(p) && typeof mainConfig.config[p] !== 'undefined'){
139 					confConfig[p] = mainConfig.config[p];
140 				}
141 			}
142 			
143 			//now apply all conf.config-values (including mainConfig.config-values):
144 			require.config( {config: confConfig} );
145 		}
146 		
147 	}
148 	
149 	/**
150 	 * Helper for merging additional module-configurations with the (requirejs) main-config
151 	 * of the framework.
152 	 * 
153 	 * <p>
154 	 * This allows to add module configurations outside the main-configuration (otherwise: 
155 	 * requirejs by default overwrites additional module-config settings).
156 	 * 
157 	 * <p>
158 	 * Merge behavior: if values in <code>mainConfig.config</code> exists, the primitive values
159 	 * 				   are overwritten with values from <code>conf.config</code> and object-values
160 	 * 				   are merged (recursively). Arrays are treated as primitive values (i.e. 
161 	 * 				   overwritten, not merged/extended).
162 	 * 
163 	 * <p>
164 	 * Note: removes <code>conf.config</code> if present and merges the values
165 	 *       into <code>mainConfig.config</code>.
166 	 * 
167 	 * @param {PlainObject} conf
168 	 * 			the additional configuration options
169 	 * @param {PlainObject} mainConfig
170 	 * 			the main configuration for the framework
171 	 * 			(this is used as reference for merging config options if necessary - see mainConfig.js)
172 	 * 
173 	 * @return {PlainObject} the <code>conf</code> setting.
174 	 * 			If necessary (i.e. if <code>conf.config</code> was present), the module-configuration
175 	 * 			was merged with the main-configuration
176 	 * 
177 	 * @memberOf mmir.internal
178 	 * @private
179 	 */
180 	function mergeModuleConfigs(conf, mainConfig){
181 		if(!mainConfig || !conf || !mainConfig.config || !conf.config || typeof mainConfig.config !== 'object' || typeof conf.config !== 'object'){
182 			return conf;
183 		}
184 		//ASSERT mainConfig.config and conf.config exist
185 		
186 		var count = 0, merged = 0;
187 		for(var cname in conf.config){
188 			if(conf.config.hasOwnProperty(cname)){
189 				
190 				++count;
191 				
192 			    //merge property cname into mainConfig
193 				if(doMergeInto(conf.config, mainConfig.config, cname)){
194 					//remove merge property from conf.config
195 					conf.config[cname] = void(0);
196 					++merged;
197 				}
198 			}
199 		}
200 		
201 		//lastly: remove the conf.config property itself, if
202 		//        all of its properties were merged
203 		if(count === merged){
204 			conf.config = void(0);
205 		}
206 
207 		return conf;
208 	}
209 	
210 	/**
211 	 * Helper for recursively merging config values from <code>conf1</code> into
212 	 * <code>conf2</code> (and removing merged values from <code>conf1</code>) IF
213 	 * an object-property <code>name</code> already exists in <code>conf2</code>.
214 	 * 
215 	 * @param {PlainObject} conf1
216 	 * 			the configuration object from which to take values (and removing them after merging)
217 	 * @param {PlainObject} conf2
218 	 * 			the configuration object to which values are merged
219 	 * @param {String} name
220 	 * 			the name of the property in <code>conf1</code> that should be merged into <code>conf2</code>
221 	 * @param {Boolean} [isNotRoot] OPTIONAL
222 	 * 			when cursively called, this should be TRUE, otherwise FALSE
223 	 * 			(i.e. this should only be used in the function's internal invocation)
224 	 * 
225 	 * @return {Boolean} <code>true</code> if property <code>name</code> was merged into conf2.
226 	 * 
227 	 * @memberOf mmir.internal
228 	 * @private
229 	 */
230 	function doMergeInto(conf1, conf2, name, isNotRoot){
231 		
232 		var v = conf1[name];
233 		
234 		if(typeof conf2[name] === 'undefined' || typeof v !== typeof conf2[name] || typeof v !== 'object'){
235 			
236 			//if not set in conf2 OR types differ OR value is primitive:
237 			if( ! isNotRoot){
238 				//... if it is at the root-level of the config-value:
239 				//  let requirejs.config() take care of it (-> will overwrite value in conf2 by applying conf1)
240 				//  -> signal that it was not merged, and should not be removed
241 				return false; ////////////////////////// EARLY EXIT ////////////
242 			} else {
243 				//... if not at root-level, we must move the property over to conf2, 
244 				//    otherwise requirejs.config() would overwrite the entire property in conf2 with the one from conf1
245 				//    -> move property (copy to conf2, remove from conf1)
246 				//    -> signal that we merge the property
247 				conf2[name] = v;
248 				conf1[name] = void(0);
249 				return true; ////////////////////////// EARLY EXIT ////////////
250 			}
251 		}
252 		
253 		//ASSERT v has type object AND conf2 has an object value too
254 		
255 		//-> recursively merge
256 		for(var cname in conf1[name]){
257 			if(conf1[name].hasOwnProperty(cname)){
258 			    //merge cname into conf2
259 				doMergeInto(conf1[name], conf2[name], cname, true);
260 			}
261 		}
262 
263         return true;
264 	}
265 	
266 	//DISABLED: un-used for now
267 //	/**
268 //	 * Helper for detecting array type.
269 //	 * 
270 //	 * @param {any} obj
271 //	 * 			the object which should be checked
272 //	 * 
273 //	 * @return {Boolean} <code>true</code> if <code>obj</code> is an Array
274 //	 * 
275 //	 * @memberOf mmir.internal
276 //	 * @private
277 //	 */
278 //	var isArray = (function(){
279 //		if(typeof Array.isArray === 'function'){
280 //			return Array.isArray;
281 //		}
282 //		else {
283 //			return function(arr){
284 //				//workaround if Array.isArray is not available: use specified result for arrays of Object's toString() function
285 //				Object.prototype.toString.call(arr,arr) === '[object Array]';
286 //			};
287 //		}
288 //	})();
289 	
290 	var mmir = {
291 			
292 			/**
293 			 * Set the framework to "initialized" status (i.e. will
294 			 * trigger the "ready" event/callbacks)
295 			 * 
296 			 * <p>
297 			 * WARNING: use this only, if you know what
298 			 *          you are doing -- normally this
299 			 *          functions is only called once
300 			 *          during initialization by the
301 			 *          framework to signal that all
302 			 *          settings, classes, set-up etc
303 			 *          for the framework are now 
304 			 *          initialized.
305 			 * <p>
306 			 * 
307 			 * NOTE: this is a semi-private function that 
308 			 *          should only be used by the initialization
309 			 *          process.
310 			 * 
311 			 * @memberOf mmir
312 			 * @name setInitialized
313 			 * @function
314 			 * @private
315 			 */
316 			setInitialized : function() {
317 				
318 				_isReady = true;
319 
320 				//apply configurations to requirejs instance:
321 				applyConfigs();
322 				
323 				//execute all callbacks in queue
324 				while(!isEmpty()){
325 					deqExex();
326 				}
327 			},
328 			
329 			/**
330 			 * Register callbacks for initialization-event of framework.
331 			 * 
332 			 * If used after framework has been initialized, the callback is invoked immediately.
333 			 * 
334 			 * @memberOf mmir
335 			 * @name ready
336 			 * @function
337 			 * @public
338 			 * 
339 			 * @param {Function} func
340 			 * 				callback Function that will be triggered when the framework has been initialized 
341 			 */
342 			ready: function(func) {
343 		
344 				//SPECIAL MODE: if already active, execute the callback 
345 				//				(if queue is not empty yet: queue function call in order to preserve the execution ordering)
346 				if(_isReady && ! isEmpty()){
347 					deqExec(func);
348 				}
349 				else {
350 					_funcList.push(func);
351 				}
352 			},
353 			
354 			/**
355 			 * Set options / settings for overwriting the default
356 			 * configuration for RequireJS:
357 			 * 
358 			 * <br>
359 			 * Options / configurations that are added by this
360 			 * method will overwrite settings specified in
361 			 * <code>mainConfig.js</code>.
362 			 * 
363 			 * <p>
364 			 * NOTE: the options added here will be applied in the order
365 			 *       they were added, i.e. if a later option specifies
366 			 *       settings that were already set by a previous call,
367 			 *       then these later options will overwrite the earlier
368 			 *       ones.
369 			 * 
370 			 * @memberOf mmir
371 			 * @name config
372 			 * @function
373 			 * @param {PlainObject} options
374 			 * 			options for RequireJS
375 			 * @public
376 			 * 
377 			 * @example
378 			 * 
379 			 * //IMPORTANT these calls need to done, AFTER core.js is loaded, but BEFORE require.js+mainConfig.js is loaded
380 			 * //(see example index.html in starter-kit)
381 			 * 
382 			 * //set specific log-level for module "moduleName":
383 			 * mmir.config({config: { 'moduleName': {logLevel: 'warn'}}});
384 			 * 
385 			 * //modify default log-levels for dialogManager and inputManager:
386 			 * mmir.config({config: { 'dialogManager': {logLevel: 'warn'}, 'inputManager': {logLevel: 'warn'}}});
387 			 * 
388 			 * //... or using alternative SCXML definition for dialog-engine:
389 			 * mmir.config({config: { 'dialogManager': {scxmlDoc: 'config/statedef/example-view_transitions-dialogDescriptionSCXML.xml'});
390 			 * 
391 			 * //overwrite module location (BEWARE: you should know what you are doing, if you use this)
392 			 * mmir.config({paths: {'jquery': 'content/libs/zepto'}};
393 			 * 
394 			 * 
395 			 * //add ID and location for own module (NOTE: need to omit file-extension ".js" in location! see requirejs docs):
396 			 * mmir.config({paths: {'customAppRouter': 'content/libs/router'}};
397 			 */
398 			config: function(options){
399 				if(_isApplied && typeof require !== 'undefined'){
400 					require.config(options);
401 				}
402 				else {
403 					_configList.push(options);
404 				}
405 			},
406 			
407 			/**
408 			 * Applies settings that were added via
409 			 * {@link #config}.
410 			 * 
411 			 * <p>
412 			 * WARNING: use this only, if you know what
413 			 *          you are doing -- normally this
414 			 *          functions is only called once
415 			 *          during initialization by the
416 			 *          framework, after the default
417 			 *          configuration settings for 
418 			 *          RequireJS in <code>mainConfig.js</code>
419 			 *          were applied.
420 			 * <p>
421 			 * 
422 			 * NOTE: this is a semi-private function that 
423 			 *          should only be used by the initialization
424 			 *          process.
425 			 * 
426 			 * @memberOf mmir
427 			 * @name applyConfigs
428 			 * @function
429 			 * @protected
430 			 */
431 			applyConfig: applyConfigs,
432 			
433 			/**
434 			 * The name of the (this) the core module:
435 			 * this is also the global variable by which the core module (this) can be accessed.
436 			 * 
437 			 * 
438 			 * NOTE: changing this name here will have no affect on the name of the global variable
439 			 * 
440 			 * 
441 			 * @memberOf mmir
442 			 * @name mmirName
443 			 * @type String
444 			 * @default {String} "mmir"
445 			 * @readonly
446 			 * @public
447 			 */
448 			mmirName: CORE_NAME,
449 			
450 			/**
451 			 * The name / ID of the RequireJS module that will
452 			 * be loaded, after the configuration in
453 			 * <code>mainConfig.js</code> was applied.
454 			 * 
455 			 * <p>
456 			 * This module should first start-up the framework and
457 			 * then signal the application (via {@link mmir.setInitialized})
458 			 * that it is ready to be used, i.e. fully initialized now.
459 			 * 
460 			 * <p>
461 			 * NOTE: If set to <code>undefined</code>, no module will be 
462 			 * loaded after configuration in <code>mainConfig.js</code>
463 			 * was applied.
464 			 * 
465 			 * @memberOf mmir
466 			 * @name startModule
467 			 * @type String
468 			 * @default {String} "main" will load the module specified in /main.js
469 			 * @public
470 			 */
471 			startModule: 'main',
472 
473 			/**
474 			 * Name / ID / load-path (requirejs) for the module
475 			 * that handles the views (i.e. "rendering" that is
476 			 * change from one view to the next).
477 			 * 			 
478 			 * @memberOf mmir
479 			 * @name viewEngine
480 			 * @type String
481 			 * @default "jqmViewEngine" will load the default view-engine that uses jQuery Mobile
482 			 * @public
483 			 */
484 			viewEngine: 'jqmViewEngine',
485 			
486 			
487 			/**
488 			 * Property for enabling / disabling logging:
489 			 * if set to <code>true</code> (or omitted), the default Logger implementation <code>tools/logger.js</code>
490 			 * will be loaded as "logger" module.
491 			 * 
492 			 * If set to <code>false</code> the "dummy" Logger implementation <code>tools/loggerDisabled.js</code> will
493 			 * be loaded as "logger" module which essentially will create no logging output.
494 			 * 			 
495 			 * @memberOf mmir
496 			 * @name debug
497 			 * @type Boolean
498 			 * @default true
499 			 * @public
500 			 * 
501 			 * @see mmir.logLevel
502 			 */
503 			debug: true,
504 			
505 			/**
506 			 * Property for the log-level of the Logger module:
507 			 * if set, and property <code>debug</code> is <code>true</code>, then the logger module
508 			 * will use the log-level as default log-level.
509 			 * 
510 			 * If omitted, the Logger's implementation defaults will be used.
511 			 * 
512 			 * If set, the property must be either a Number or a String with one of the following values:
513 			 * 
514 			 * 0: "verbose"
515 			 * 1: "debug"
516 			 * 2: "info"
517 			 * 3: "warn"
518 			 * 4: "error"
519 			 * 5: "critical"
520 			 * 6: "disabled"
521 			 * 
522 			 * NOTE: if you want to disable logging completely, use {@link mmir.debug}.
523 			 *       Setting the logLevel to "disabled" will still allow specific module's to create logging output
524 			 *       (if their log-level is set appropriately)
525 			 * 			 
526 			 * @memberOf mmir
527 			 * @name logLevel
528 			 * @type Integer | String
529 			 * @default "debug"
530 			 * @public
531 			 * 
532 			 * @see mmir.debug
533 			 */
534 			logLevel: 'debug',
535 			
536 			/**
537 			 * Property for enabling / disabling trace output in the Logger module:
538 			 * if set to <code>true</code>, and property <code>debug</code> is <code>true</code>, then 
539 			 * the logger module will print a stack-trace for each log-message.
540 			 * 
541 			 * If set to a configuration object:
542 			 * <pre>
543 			 * {
544 			 * 		"trace": [true | false],	//same as the Boolean primitive for logTrace
545 			 * 		"depth": ["full" | other]	//OPTIONAL: if "full" then the complete stack trace is printed,
546 			 * 									// otherwise only the first stack-entry (i.e. the calling function)
547 			 * 									// is printed.
548 			 * 									//DEFAULT: other
549 			 * }
550 			 * </pre>
551 			 * 
552 			 * i.e. <code>{trace: true}</code> would be the same as using <code>true</code> (or omitting this property).
553 			 * 
554 			 * 
555 			 * The default value (also if omitted!) is <code>true</code>.
556 			 * 			 
557 			 * @memberOf mmir
558 			 * @name logTrace
559 			 * @type Boolean | PlainObject
560 			 * @default true
561 			 * @public
562 			 * 
563 			 * @see mmir.debug
564 			 * @see mmir.logLevel
565 			 */
566 			logTrace: true	//{trace: true, depth: 'full'}
567 	};
568 	
569 	if(typeof define === 'function'){
570 		define(function(){ return mmir; });
571 	}
572 	
573 	//if window[CORE_NAME] already exists:
574 	//  copy all its properties to the new core-mmir object
575 	if(window[CORE_NAME]){
576 		for(var p in window[CORE_NAME]){
577 			if(window[CORE_NAME].hasOwnProperty(p) && typeof mmir[p] === 'undefined'){
578 				mmir[p] = window[CORE_NAME][p];
579 			}
580 		}
581 	}
582 	
583 	//export core-module into global namespace:
584 	window[CORE_NAME] = mmir;
585 	
586 	return mmir;
587 }());
588