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