1 /* 2 * Copyright (C) 2012-2013 DFKI GmbH 3 * Deutsches Forschungszentrum fuer Kuenstliche Intelligenz 4 * German Research Center for Artificial Intelligence 5 * http://www.dfki.de 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a 8 * copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be included 16 * in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 */ 26 27 28 29 define([ 'controllerManager', 'constants', 'commonUtils', 'configurationManager'//DISABLED: now loaded on-demand (see init()) -> 'renderUtils' 30 , 'layout', 'view', 'partial', 'dictionary', 'checksumUtils', 'languageManager' 31 , 'jquery', 'core'//, 'module' 32 , 'stringExtension', 'parserModule' 33 ], 34 35 /** 36 * @class 37 * @name mmir.PresentationManager 38 * @static 39 * 40 * Libraries: 41 * - jQuery (>= v1.6.2); ajax, each 42 * 43 * @requires document (DOM object) 44 * 45 * @requires jQuery.Deferred 46 * @requires jQuery.ajax 47 * @requires jQuery.each 48 * 49 */ 50 function ( controllerManager, constants, commonUtils, configurationManager//, renderUtils 51 , Layout, View, Partial, Dictionary, checksumUtils, languageManager 52 , $, core//, module 53 ) { 54 55 //the next comment enables JSDoc2 to map all functions etc. to the correct class description 56 /** @scope mmir.PresentationManager.prototype */ 57 58 /** 59 * Counter that keeps track of the number of times, that a view is rendered 60 * 61 * NOTE: for implementation specific reasons, jQuery Mobile requires that 62 * each page has a different ID. This pageIndex is used to generating 63 * such a unique ID, by increasing the number on each page-change 64 * (i.e. by rendering a view) and appending it to the page's ID/name. 65 * 66 * @type Integer 67 * @public 68 * @memberOf mmir.PresentationManager# 69 */ 70 var _pageIndex = 0; 71 72 /** 73 * Name for the default layout. 74 * 75 * <p> 76 * There must exist a layout definition by 77 * this name, i.e. 78 * <pre>views/layout/<DEFAULT_LAYOUT_NAME>.ehtml</pre> 79 * 80 * NOTE: while the name begins with an upper case 81 * letter, the file name for the layout must 82 * start with a lower case letter, e.g. for 83 * name <code>Default</code>, the file name 84 * must be <code>default.ehtml</code>. 85 * 86 * @type String 87 * @private 88 * @constant 89 * @memberOf mmir.PresentationManager# 90 */ 91 var DEFAULT_LAYOUT_NAME = 'Default'; 92 93 /** 94 * Name of the configuration property that specifies whether or not to use 95 * pre-compiled views, i.e. whether to use generated JavaScript files 96 * instead of parsing & compiling the "raw" templates (eHTML files). 97 * 98 * <p> 99 * NOTE: the configuration value, that can be retrieved by querying this configuration-property 100 * has is either a Boolean, or a String representation of a Boolean value: 101 * <code>[true|false|"true"|"false"]</code> 102 * <br> 103 * NOTE2: There may be no value set at all in the configuration for this property. 104 * In this case you should assume that it was set to <code>false</code>. 105 * 106 * @type String 107 * @private 108 * @constant 109 * @memberOf mmir.PresentationManager# 110 * 111 * @example var isUsePrecompiledViews = mmir.Constants.get(CONFIG_PRECOMPILED_VIEWS_MODE); 112 * 113 */ 114 var CONFIG_PRECOMPILED_VIEWS_MODE = 'usePrecompiledViews';//TODO move this to somewhere else (collected config-vars?)? this should be a public CONSTANT... 115 116 // private members 117 /** 118 * Array of layouts of the application 119 * 120 * @type Dictionary 121 * @private 122 * @memberOf mmir.PresentationManager# 123 */ 124 var _layouts = new Dictionary(); 125 126 /** 127 * Array of all the views of the application 128 * 129 * @type Dictionary 130 * @private 131 * @memberOf mmir.PresentationManager# 132 */ 133 var _views = new Dictionary(); 134 135 /** 136 * Array of all the partials of the application 137 * 138 * @type Dictionary 139 * @private 140 * @memberOf mmir.PresentationManager# 141 */ 142 var _partials = new Dictionary(); 143 144 /** 145 * An object containing data for the currently displayed view.<br> 146 * It contains: name of the corresponding controller, name of the view 147 * and optionally data for the view 148 * 149 * @type View 150 * @private 151 * @memberOf mmir.PresentationManager# 152 */ 153 var _currentView = {}; 154 155 /** 156 * An object containing data for the previously displayed view - the one 157 * displayed before the current view.<br> 158 * It contains: name of the corresponding controller, name of the view 159 * and optionally data for the view 160 * 161 * @type View 162 * @private 163 * @memberOf mmir.PresentationManager# 164 * 165 * @deprecated do not use 166 */ 167 var _previousView = {}; 168 169 /** 170 * The currently displayed dialog object, if a dialog is displayed. Used 171 * mainly to close the dialog. 172 * 173 * @type Object 174 * @private 175 * @memberOf mmir.PresentationManager# 176 * 177 * @see mmir.PresentationManager#showDialog 178 * @see mmir.PresentationManager#hideCurrentDialog 179 */ 180 var _currentDialog = null; 181 182 /** 183 * @private 184 * @memberOf mmir.PresentationManager# 185 */ 186 var viewSeparator = '#'; 187 /** 188 * @type String 189 * @private 190 * @memberOf mmir.PresentationManager# 191 */ 192 var partialSeparator = commonUtils.getPartialsPrefix(); 193 /** 194 * @private 195 * @memberOf mmir.PresentationManager# 196 */ 197 function createLookupKey(ctrl, viewObj, separator){ 198 if(typeof ctrl.getName !== 'undefined'){ 199 ctrl = ctrl.getName(); 200 } 201 if(typeof viewObj.getName !== 'undefined'){ 202 viewObj = viewObj.getName(); 203 } 204 //TODO remove all >partialSeparator< from partial-string beginning 205 return ctrl+separator+viewObj; 206 } 207 /** 208 * @private 209 * @memberOf mmir.PresentationManager# 210 */ 211 function createViewKey(ctrl, view){ 212 return createLookupKey(ctrl, view, viewSeparator); 213 } 214 /** 215 * @private 216 * @memberOf mmir.PresentationManager# 217 */ 218 function createPartialKey(ctrl, partial){ 219 return createLookupKey(ctrl, partial, partialSeparator); 220 } 221 222 223 /** 224 * Default implementation for the rendering-engine: 225 * 226 * does nothing but writing an error message to the console, 227 * if any of its functions is invoked. 228 * 229 * The rendering engine can be set via {@link mmir.PresentationManager#setRenderEngine}. 230 * 231 * @type RenderEngine 232 * @private 233 * @memberOf mmir.PresentationManager# 234 */ 235 var _renderEngine = { 236 /** 237 * The function that actually renders the View.<br> 238 * 239 * The function will be invoked in context of the PresentationManager instance 240 * (i.e. the manager will be the <em>this</em> context). 241 * 242 * Implementations of this function should adhere to the following procedure: 243 * 244 * <br> 245 * 246 * First this function fetches the <em>layout</em> for the <em>controller</em> 247 * (or uses the <code>dialogManager.DEFAULT_LAYOUT<code>). 248 * 249 * Then the <code>before_page_prepare</code> of the <em>controller</em> is invoked (if it exists). 250 * 251 * Then renders the <em>view</em> into the 252 * layout-template; <em>partials</em>, <em>helpers</em> etc. 253 * that are referenced in the <em>view</em> will be processed, 254 * executed etc.; during this, localized Strings should be processed and rendered by 255 * {@link mmir.LanguageManager#getText}. 256 * 257 * Then <em>dialogs</em> are created and the <code>dialogManager.pageIndex</code> is updated. 258 * 259 * The new content is inserted into the document/page (invisibly). 260 * 261 * Then the <code>before_page_load</code> of the <em>controller</em> is invoked (if it exists). 262 * 263 * The new content/page is made visible, and the old one invisible and / or is removed. 264 * 265 * At the end the <b>on_page_load</b> action of the <em>controller</em> is performed. 266 * 267 * @function 268 * @memberOf mmir.PresentationManager._renderingEngine 269 * 270 * @param {String} 271 * ctrlName Name of the controller 272 * @param {String} 273 * viewName Name of the view to render 274 * @param {Object} 275 * view View object that is to be rendered 276 * @param {Object} 277 * ctrl Controller object of the view to render 278 * @param {Object} 279 * [data] optional data for the view. 280 */ 281 render: function(ctrlName, viewName, view, ctrl, data){ 282 console.error('PresentationManager.render: no rendering engine set!'); 283 }, 284 showDialog: function(ctrlName, dialogId, data) { 285 console.error('PresentationManager.showDialog: no rendering engine set!'); 286 }, 287 hideCurrentDialog: function(){ 288 console.error('PresentationManager.hideCurrentDialog: no rendering engine set!'); 289 }, 290 showWaitDialog: function(text, data) { 291 console.error('PresentationManager.showWaitDialog: no rendering engine set!'); 292 }, 293 hideWaitDialog: function() { 294 console.error('PresentationManager.hideWaitDialog: no rendering engine set!'); 295 } 296 }; 297 /** 298 * Reference to the rendering-engine implementation / instance. 299 * 300 * This reference should not be accessed directly. 301 * Custom functions of the rendering implementation can be 302 * invoked via {@link mmir.PresentationManager#callRenderEngine}. 303 * 304 * @type Object 305 * @private 306 */ 307 _renderEngine._engine = _renderEngine; 308 309 var _instance = { 310 /** @scope mmir.PresentationManager.prototype */ 311 312 /** 313 * @deprecated instead: use mmir.PresentationManager directly 314 * 315 * @memberOf mmir.PresentationManager.prototype 316 */ 317 getInstance: function () { 318 return this; 319 }, 320 321 // public members 322 addLayout : function(layout) { 323 _layouts.put(layout.getName(), layout); 324 }, 325 /** 326 * This function returns a layout object by name.<br> 327 * 328 * @function 329 * @param {String} 330 * layoutName Name of the layout which should be returned 331 * @param {Boolean} 332 * [doUseDefaultIfMissing] if supplied and 333 * <code>true</code>, the default controller's layout 334 * will be used as a fallback, in case no corresponding 335 * layout could be found 336 * @returns {Object} The requested layout, "false" if not found 337 * @public 338 * @memberOf mmir.PresentationManager.prototype 339 */ 340 getLayout : function(layoutName, doUseDefaultIfMissing) { 341 var layout = false; 342 layout = _layouts.get(layoutName); 343 if (!layout) { 344 if (doUseDefaultIfMissing) { 345 layout = _instance.getLayout(DEFAULT_LAYOUT_NAME, false); 346 } 347 else { 348 console.error('[PresentationManager.getLayout]: could not find layout "' + layoutName +'"') 349 return false; 350 } 351 } 352 return layout; 353 }, 354 355 /** 356 * 357 * @param {String|Controller} ctrlName 358 * @param {String|View} view 359 * @public 360 * @memberOf mmir.PresentationManager.prototype 361 */ 362 addView : function(ctrlName, view) { 363 _views.put(createViewKey(ctrlName, view), view); 364 }, 365 /** 366 * This function returns a view object by name.<br> 367 * 368 * @function 369 * @param {String} 370 * controllerName Name of the controller for the view 371 * @param {String} 372 * viewName Name of the view which should be returned 373 * @returns {Object} The requested view, <tt>false</tt> if not 374 * found 375 * @public 376 * @memberOf mmir.PresentationManager.prototype 377 */ 378 getView : function(controllerName, viewName) { 379 viewName = createViewKey(controllerName, viewName); 380 var view = false; 381 view = _views.get(viewName); 382 383 if (!view) { 384 console.error('[PresentationManager.getView]: could not find view "' + viewName + '"'); 385 return false; 386 } 387 return view; 388 }, 389 /** 390 * 391 * @param {String|Controller} ctrlName 392 * @param {String|Partial} partial 393 * 394 * @public 395 * @memberOf mmir.PresentationManager.prototype 396 */ 397 addPartial: function(ctrlName, partial){ 398 _partials.put(createPartialKey(ctrlName, partial), partial); 399 }, 400 401 /** 402 * This function returns a partial object by name.<br> 403 * 404 * @function 405 * @param {String} 406 * controllerName Name of the controller for the view 407 * @param {String} 408 * viewName Name of the partial which should be returned 409 * @returns {Object} The requested partial, "false" if not found 410 * @public 411 * @memberOf mmir.PresentationManager.prototype 412 */ 413 getPartial : function(controllerName, partialName) { 414 var partial = false; 415 416 var partialKey = null; 417 if (controllerName) { 418 partialKey = createPartialKey(controllerName, partialName); 419 } 420 else { 421 console.error('[PresentationManager.getPartial]: requested partial "' + partialName + '" for unknown controller: "' + (controllerName ? (controllerName.getName? controllerName.getName(): controllerName) : 'undefined') 422 + '"'); 423 return false; 424 } 425 426 partial = _partials.get(partialKey); 427 if (!partial) { 428 console.error('[PresentationManager.getPartial]: could not find partial "' + partialName + '" for controller "' + (controllerName ? (controllerName.getName? controllerName.getName(): controllerName) : 'undefined') + '"!'); 429 return false; 430 } 431 return partial; 432 }, 433 434 /** 435 * Closes a modal window / dialog (if one is open). 436 * <br> 437 * 438 * @function 439 * @public 440 * @memberOf mmir.PresentationManager.prototype 441 */ 442 hideCurrentDialog : function() { 443 _renderEngine.hideCurrentDialog.apply(this,arguments); 444 }, 445 446 /** 447 * Opens the dialog for ID <code>dialogId</code>. 448 * <br> 449 * 450 * @function 451 * @param {String} ctrlName 452 * the Name of the controller 453 * @param {String} dialogId 454 * the ID of the dialog 455 * @param {Object} [data] OPTIONAL 456 * a data / options object 457 * 458 * @returns {Object} the instance of the opened dialog (void or falsy dialog was not opened) 459 * 460 * @public 461 * @memberOf mmir.PresentationManager.prototype 462 */ 463 showDialog : function(ctrlName, dialogId, data) { 464 _currentDialog = _renderEngine.showDialog.apply(this,arguments); 465 return _currentDialog; 466 }, 467 468 /** 469 * Shows a "wait" dialog, i.e. "work in progress" notification. 470 * 471 * @function 472 * 473 * @param {String} [text] OPTIONAL 474 * the text that should be displayed. 475 * If omitted the language setting for <code>loadingText</code> 476 * will be used instead (from dictionary.json) 477 * @param {Object} [data] OPTIONAL 478 * a data / options object 479 * 480 * @public 481 * @memberOf mmir.PresentationManager.prototype 482 * 483 * @see mmir.PresentationManager#hideWaitDialog 484 */ 485 showWaitDialog : function(text, data) { 486 _renderEngine.showWaitDialog.apply(this,arguments); 487 }, 488 489 /** 490 * Hides / closes the "wait" dialog. 491 * 492 * @function 493 * @public 494 * @memberOf mmir.PresentationManager.prototype 495 * 496 * @see mmir.PresentationManager#showWaitDialog 497 */ 498 hideWaitDialog : function() { 499 _renderEngine.hideWaitDialog.apply(this,arguments); 500 }, 501 502 /** 503 * Gets the view for a controller, then executes helper methods on 504 * the view data. The Rendering of the view is done by the 505 * {@link #doRenderView} method. Also 506 * stores the previous and current view with parameters.<br> 507 * 508 * @function 509 * @param {String} 510 * ctrlName Name of the controller 511 * @param {String} 512 * viewName Name of the view to render 513 * @param {Object} 514 * [data] optional data for the view. 515 * Currently same jQuery Mobile specific properties are supported: <br> 516 * When these are present, they will be used for animating the 517 * page transition upon rendering. 518 * 519 * <pre>{transition: STRING, reverse: BOOLEAN}</pre> 520 * where<br> 521 * <code>transition</code>: the name for the transition (see jQuery Mobile Doc for possible values) 522 * DEFAULT: "none". 523 * <code>reverse</code>: whether the animation should in "forward" (FALSE) direction, or "backwards" (TRUE) 524 * DEFAULT: FALSE 525 * @public 526 * @memberOf mmir.PresentationManager.prototype 527 */ 528 renderView : function(ctrlName, viewName, data) { 529 530 var ctrl = controllerManager.getController(ctrlName); 531 532 if (ctrl != null) { 533 var view = this.getView(ctrlName, viewName); 534 535 _renderEngine.render.call(this, ctrlName, viewName, view, ctrl, data); 536 537 //TODO russa: _previousView is deprecated (should use a history instead, i.e. application level) 538 // Only overwrite previous state if and only if the view is not re-rendered! 539 if (ctrlName != _currentView.ctrlName || viewName != _currentView.viewName || data != _currentView.data){ 540 _previousView.ctrlName=_currentView.ctrlName; 541 _previousView.viewName=_currentView.viewName; 542 _previousView.data=_currentView.data; 543 } 544 545 _currentView.ctrlName=ctrlName; 546 _currentView.viewName=viewName; 547 _currentView.data=data; 548 } 549 else { 550 console.warn('PresentationManager.renderView: could not retrieve controller "'+ctrlName+'"'); 551 } 552 }, 553 554 /** 555 * Renders the current view again, using the 556 * {@link #renderView} method. 557 * 558 * @deprecated you should use {@link #renderView} with appropriate parameters instead. 559 * 560 * @requires mmir.DialogManager 561 * 562 * @function 563 * @public 564 * @memberOf mmir.PresentationManager.prototype 565 */ 566 reRenderView : function() { 567 if (_currentView) { 568 if (_currentView.ctrlName && _currentView.viewName) { 569 require('dialogManager').render(_currentView.ctrlName, _currentView.viewName, _currentView.data); 570 } 571 } 572 }, 573 574 /** 575 * Renders the previous view again, using the 576 * {@link mmir.DialogManager#render} method. 577 * 578 * 579 * @deprecated you should use {@link #renderView} with appropriate parameters instead. 580 * 581 * @requires mmir.DialogManager 582 * 583 * @function 584 * @public 585 * @memberOf mmir.PresentationManager.prototype 586 */ 587 renderPreviousView : function() { 588 if (_previousView) { 589 if (_previousView.ctrlName && _previousView.viewName) { 590 require('dialogManager').render(_previousView.ctrlName, _previousView.viewName, _previousView.data); 591 } 592 } 593 }, 594 595 /** 596 * @function 597 * @memberOf mmir.PresentationManager.prototype 598 */ 599 init: init, 600 601 /** 602 * Sets the <em>rendering engine</em> for the views. 603 * 604 * The render engine <b>must</b> implement a function <em>render</em> 605 * and <i>may</i> implement functions <em>showDialog</em>, 606 * <em>hideCurrentDialog</em>, <em>showWaitDialog</em>, and <em>hideWaitDialog</em>: 607 * 608 * <ul> 609 * <li><b>theRenderEngine.<code>render(ctrlName : String, viewName : String, view : View, ctrl : Controller, data : Object) : void</code></b></li> 610 * <li>theRenderEngine.<code>showDialog(ctrlName : String, dialogId : String, data : Object) : Dialog</code></li> 611 * <li>theRenderEngine.<code>hideCurrentDialog(): void</code></li> 612 * <li>theRenderEngine.<code>showWaitDialog(text : String, data : Object): void</code></li> 613 * <li>theRenderEngine.<code>hideWaitDialog(): void</code></li> 614 * </ul> 615 * 616 * The functions of <code>theRenderEngine</code> will be called in 617 * context of the PresentationManager. 618 * 619 * Custom functions of the specific rendering engine implementation 620 * (i.e. non-standard functions) can be call via {@link #callRenderEngine}. 621 * 622 * 623 * <br> 624 * By default, the rendering-engine as defined by the module ID/path in 625 * <code>core.viewEngine</code> will be loaded and set during initialization 626 * of the DialogManager. 627 * 628 * <br> 629 * The implementation of the default view-engine is at 630 * <code>mmirf/env/view/presentation/jqmViewEngine.js</code>. 631 * 632 * @param {Object} theRenderEngine 633 * the render-engine for views 634 * 635 * @function 636 * @public 637 * @memberOf mmir.PresentationManager.prototype 638 * 639 * @see mmir.PresentationManager#renderView 640 * @see mmir.PresentationManager#showDialog 641 * @see mmir.PresentationManager#hideCurrentDialog 642 * @see mmir.PresentationManager#showWaitDialog 643 * @see mmir.PresentationManager#hideWaitDialog 644 * 645 * @see mmir.PresentationManager#callRenderEngine 646 * 647 */ 648 setRenderEngine: function(theRenderEngine){ 649 _renderEngine.render = theRenderEngine.render; 650 _renderEngine.showDialog = theRenderEngine.showDialog; 651 _renderEngine.hideCurrentDialog = theRenderEngine.hideCurrentDialog; 652 _renderEngine.showWaitDialog = theRenderEngine.showWaitDialog; 653 _renderEngine.hideWaitDialog = theRenderEngine.hideWaitDialog; 654 _renderEngine._engine = theRenderEngine; 655 }, 656 /** 657 * This function allows to call custom functions of the rendering-engine 658 * that was set via {@link #setRenderEngine}. 659 * 660 * IMPORTANT: 661 * note that the function will be invoked in context of rendering-engine 662 * (i.e. <code>this</code> references will refer to rendering-engine 663 * and not to the PresentationManager instance. 664 * For example, when <code>mmir.PresentationManager.callRenderEngine('hideWaitDialog')</code> 665 * is called, any <code>this</code> references within the <code>hideWaitDialog</code> 666 * implementation would refer to object, that was set in <code>setRenderEngine(object)</code>. 667 * In comparison, when called as <code>mmir.PresentationManager.hideWaitDialog()</code> the 668 * <code>this</code> references refer to the mmir.PresentationManager instance. 669 * 670 * <br> 671 * NOTE that calling non-existing functions on the rendering-engine 672 * will cause an error. 673 * 674 * @param {String} funcName 675 * the name of the function, that should be invoked on the rendering 676 * engine. 677 * @param {Array<any>} [args] OPTIONAL 678 * the arguments for <code>funcName</code> invoked 679 * via <code>Function.apply(renderingEngine, args)</code>, e.g. 680 * for <code>args = [param1, param2, param3]</code> the 681 * function will be called with 682 * <code>funcName(param1, param2, param3)</code> 683 * (note that the function receives 3 arguments, and 684 * not 1 Array-argument). 685 * 686 * @function 687 * @public 688 * @memberOf mmir.PresentationManager.prototype 689 */ 690 callRenderEngine: function(funcName, args){ 691 return _renderEngine._engine[funcName].apply(_renderEngine._engine, args); 692 }, 693 694 //exported properties / constants: 695 /** 696 * @public 697 * @type Integer 698 * @constant 699 * @memberOf mmir.PresentationManager.prototype 700 */ 701 pageIndex: _pageIndex 702 };//END: return{... 703 // })();//END: (function(){... 704 705 706 return _instance; 707 708 function init () { 709 710 /** @scope mmir.MediaManager.prototype */ 711 712 delete _instance.init;//FIXME should init be deleted? 713 714 /** 715 * Checks if a pre-compiled view is up-to-date: 716 * loads the view, if it is current. 717 * 718 * If the pre-compiled view is not current, or loading-errors 719 * occur, the fail-callback will be triggered 720 * (the callback argument may contain information about the cause). 721 * 722 * @async 723 * @private 724 * @memberOf mmir.MediaManager.init 725 * 726 * @param {String} rawViewData 727 * the text content of the view template (i.e. content of a eHTML file "as is") 728 * @param {String} targetPath 729 * the path to the pre-compiled view file 730 * @param {Function} success 731 * callback that will be triggered, if pre-compiled view file was loaded 732 * NOTE: the JS code of the loaded file may not have been fully executed yet! 733 * @param {Function} fail 734 * callback that will be triggered, if pre-compiled view is not up-to-date or 735 * an error occurs while loading the file 736 */ 737 function loadPrecompiledView(rawViewData, targetpath, success, fail){ 738 739 //NOTE: stored template require the renderUtils: 740 require(['renderUtils'], function(){ 741 742 if(! isUpToDate(rawViewData, targetpath)){ 743 if(fail) fail('Precompiled view file is outdated!'); 744 else console.warn('Outdated pre-compiled view at: '+targetpath); 745 746 //-> do not load the pre-compiled view, instead let fail-callback handle re-parsing for the view 747 return;/////////////////////// EARLY EXIT ///////////////////// 748 } 749 750 commonUtils.getLocalScript( //scriptUrl, success, fail) 751 targetpath, success, fail 752 ); 753 754 }); 755 756 } 757 758 /** 759 * Flag for determining if pre-compiled views (*.js) should be used 760 * 761 * Reads property {@link #CONFIG_PRECOMPILED_VIEWS_MODE}. If the property is not set, 762 * <code>false</code> is used by default, i.e. no pre-compiled views are used. 763 * 764 * @protected 765 * @default 766 * @type Boolean 767 * @default false: use templates files (*.ehtml) and compile them (freshly) on-the-fly 768 * @memberOf mmir.MediaManager.init 769 */ 770 var isUsePreCompiledViews = configurationManager.getBoolean(CONFIG_PRECOMPILED_VIEWS_MODE, true, false); 771 772 /** 773 * Read the checksum file that was created when the pre-compiled view was created: 774 * 775 * it contains the view's template size (the length of its String representation) and MD5 hash. 776 * 777 * -> by calculating the viewContent's size and MD5 hash, we can determine, if it has changed 778 * by comparing it with the data of the checksum file. 779 * 780 * @sync the checksum file is loaded in synchronous mode 781 * @private 782 * @memberOf mmir.MediaManager.init 783 * 784 * @param {String} viewContent 785 * the content of the view template (i.e. loaded eHTML file) 786 * @param {String} preCompiledViewPath 787 * the path to the corresponding pre-compiled view file 788 * 789 */ 790 function isUpToDate(viewContent, preCompiledViewPath){ 791 //replace file extension with the checksum-file's one: '.js' -> '.checksum.txt' 792 var viewVerificationInfoPath = 793 preCompiledViewPath.substring(0, preCompiledViewPath.length - 3) 794 + checksumUtils.getFileExt(); 795 796 var isCompiledViewUpToDate = false; 797 798 $.ajax({ 799 async: false,//<-- use "SYNC" modus here (NOTE: we win nothing with async here, because the following step (loading/not loading the pre-compiled view) strictly depends on the result of this) 800 dataType: "text", 801 url: viewVerificationInfoPath, 802 success: function(data){ 803 804 //compare raw String to checksum-data from file 805 isCompiledViewUpToDate = checksumUtils.isSame(viewContent, data); 806 } 807 }).fail(function(jqxhr, status, err){ 808 // print out an error message 809 var errMsg = err && err.stack? err.stack : err; 810 console.error("[" + status + "] Could not load '" + viewVerificationInfoPath + "': "+errMsg); //failure 811 }); 812 813 return isCompiledViewUpToDate; 814 } 815 816 /** 817 * This function loads the layouts for every controller and puts the 818 * name of the layouts into the <b>_layouts</b> array. 819 * 820 * @function 821 * @private 822 * @memberOf mmir.MediaManager.init 823 * 824 * @returns {Promise} a Deferred.promise that gets resolved upon loading all layouts; fails/is rejected, if not at least 1 layout was loaded 825 */ 826 function loadLayouts(){ 827 // Load application's layouts. 828 829 /** 830 * @type jQuery.Deffered 831 * @private 832 * @memberOf mmir.MediaManager.init.loadLayouts 833 */ 834 var defer = $.Deferred(); 835 836 /** 837 * @type String 838 * @private 839 * @memberOf mmir.MediaManager.init.loadLayouts 840 */ 841 var ctrlNameList = controllerManager.getControllerNames(); 842 843 /** 844 * HELPER object for tracking the loading-status of the layouts 845 * 846 * @private 847 * @memberOf mmir.MediaManager.init.loadLayouts 848 */ 849 var loadStatus = { 850 loader: defer, 851 remainingCtrlCount: ctrlNameList.length + 1,//+1: for the default layout 852 currentLoadCount: 0, 853 854 //additional property for keeping track on how many layouts were load overall 855 // NOTE: this additional counter is necessary, since currentLoadCount 856 // keeps only track of how many controller's were checked. But since 857 // a controller may not have a layout-defintion of its own, we have 858 // to use another counter to keep track of actually loaded layouts. 859 loadedLayoutsCount: 0, 860 861 //need a custom function for checking the load status: if no layout was loaded, 862 // the Derred will be rejected 863 onCompletionImpl: function(status){ 864 if(status.loadedLayoutsCount < 1){ 865 866 //there must be at least on layout-file for the default-controller: 867 status.loader.reject( 'Could not load any layout! At least one layout must be present at ' 868 + constants.getLayoutPath() 869 + DEFAULT_LAYOUT_NAME[0].toLowerCase() + DEFAULT_LAYOUT_NAME.substring(1) 870 + '.ehtml' 871 ); 872 } 873 else { 874 status.loader.resolve(); 875 } 876 }, 877 878 //extend the status-update function: in case loading succeeded, increase the counter 879 // for overall loaded layouts. 880 extLoadStatusFunc: function(status, hasLoadingFailed){ 881 if(hasLoadingFailed === true){ 882 //do nothing 883 } 884 else { 885 ++status.loadedLayoutsCount; 886 } 887 } 888 }; 889 890 /** 891 * HELPER object for loading/creating the layouts 892 * @private 893 * @memberOf mmir.MediaManager.init.loadLayouts 894 */ 895 var createLayoutConfig = { 896 constructor: Layout, 897 typeName: 'Layout', 898 collection: _layouts 899 }; 900 901 /** 902 * helper for loading a single layout-file 903 * 904 * @private 905 * @memberOf mmir.MediaManager.init.loadLayouts 906 */ 907 var doLoadLayout = function(index, ctrlName, theDefaultLayoutName){ 908 909 var ctrlName; 910 var layoutInfo; 911 if(theDefaultLayoutName){ 912 913 ctrlName = theDefaultLayoutName; 914 915 //create info-object for default-layout 916 var layoutFileName = theDefaultLayoutName[0].toLowerCase() 917 + theDefaultLayoutName.substring(1, theDefaultLayoutName.length); 918 layoutInfo = { 919 name: theDefaultLayoutName, 920 fileName: layoutFileName, 921 genPath: constants.getCompiledLayoutPath()//TODO add compiled-path to view-info object (and read it from file-structure/JSON) 922 + layoutFileName + '.js', 923 path: constants.getLayoutPath() + layoutFileName + '.ehtml' 924 }; 925 926 } 927 else { 928 var ctrl = controllerManager.getController( ctrlName ); 929 ctrlName = ctrl.getName(); 930 layoutInfo = ctrl.getLayout(); 931 } 932 933 if(layoutInfo){ 934 935 doLoadTemplateFile(null, layoutInfo, createLayoutConfig, loadStatus); 936 937 } 938 939 --loadStatus.remainingCtrlCount; 940 checkCompletion(loadStatus); 941 942 };//END: doLoadLayout(){... 943 944 //load the default layout: 945 doLoadLayout(null, null, DEFAULT_LAYOUT_NAME); 946 947 //load layouts for controllers (there may be none defined) 948 $.each(ctrlNameList, doLoadLayout); 949 950 checkCompletion(loadStatus); 951 return defer.promise(); 952 953 }//END: loadLayouts() 954 955 /** 956 * This function actually loads the views for every controller, creates 957 * an instance of a view class and puts the view instance in the 958 * <b>_views</b> array.<br> 959 * 960 * @function 961 * @private 962 * @async 963 * @memberOf mmir.MediaManager.init 964 * 965 * @returns {Promise} a Deferred.promise that gets resolved upon loading all views 966 * 967 * @see doProcessTemplateList 968 */ 969 function loadViews() { 970 971 var creatorConfig = { 972 constructor: View, 973 typeName: 'View', 974 collection: _views, 975 keyGen: createViewKey, 976 accessorName: 'getViews' 977 }; 978 979 return doProcessTemplateList(creatorConfig); 980 981 }//END: loadViews() 982 983 /** 984 * This function actually loads the partials for every controller, 985 * creates an instance of a partial class and puts the partial instance 986 * in the <b>_partials</b> array.<br> 987 * It uses a asynchronous way of loading the partials-files one after 988 * another.<br> 989 * <b>If you want to make sure, that all partials are indeed loaded, 990 * before proceeding with the subsequent instructions, you could look at 991 * the function 992 * {@link mmir.ControllerManager#foundControllersCallBack} for 993 * reference of a function which loads the files one after another - not 994 * asynchronously.</b> 995 * 996 * @function 997 * @private 998 * @async 999 * @memberOf mmir.MediaManager.init 1000 * 1001 * @returns {Promise} a Deferred.promise, that resolves after all partials have been loaded 1002 * NOTE: loading failures will generate a warning message (on the console) 1003 * but will not cause the Promise to fail. 1004 */ 1005 function loadPartials() { 1006 1007 var creatorConfig = { 1008 constructor: Partial, 1009 typeName: 'Partial', 1010 collection: _partials, 1011 keyGen: createPartialKey, 1012 accessorName: 'getPartials' 1013 }; 1014 1015 return doProcessTemplateList(creatorConfig); 1016 1017 }//END: loadPartials() 1018 1019 /** 1020 * HELPER for checking the loading status. 1021 * 1022 * As long as the Deferred <code>status.loader</code> is 1023 * still pending, the loading status will be checked: 1024 * 1025 * Depending on <code>status.currentLoadCount</code> and 1026 * <code>status.remainingCtrlCount</code> the completion 1027 * of the loading process is checked. 1028 * 1029 * If loading is completed, the Deferred <code>status.loader</code> 1030 * will be resolved. 1031 * 1032 * If OPTIONAL <code>status.loader</code> (Function) exists, intead of resolving 1033 * <code>status.loader</code>, this function is invoked in case of completion 1034 * with <code>status</code> as argument. 1035 * 1036 * @private 1037 * @function 1038 * @memberOf mmir.MediaManager.init 1039 * 1040 * @param {PlainObject} status 1041 * the object for managing the laoding status. 1042 * 1043 */ 1044 var checkCompletion = function(status){ 1045 if(status.loader.state() === 'pending' && status.remainingCtrlCount === 0 && status.currentLoadCount === 0){ 1046 1047 if(status.onCompletionImpl){ 1048 status.onCompletionImpl(status); 1049 } 1050 else { 1051 status.loader.resolve(); 1052 } 1053 1054 } 1055 }; 1056 1057 /** 1058 * HELPER for updating the loading status. 1059 * 1060 * Invokes {@link checkCompletion} with <code>status</code> as argument. 1061 * 1062 * @private 1063 * @function 1064 * @memberOf mmir.MediaManager.init 1065 * 1066 * @param {PlainObject} status 1067 * the object for managing the laoding status: 1068 * <code>status.currentLoadCount</code> (Integer): this property will be decreased by 1. 1069 * This value should initially be set to the count 1070 * of files, that will / should be loaded. 1071 * OPTIONAL <code>status.extLoadStatusFunc</code> (Function): if this property is set, the 1072 * function will be invoked with <code>status</code> 1073 * and <code>hasLoadingFailed</code> as arguments. 1074 * 1075 * @param {Boolean} [hasLoadingFailed] OPTIONAL 1076 * if present and <code>true</code>: this indicates that the loading process for the current 1077 * template file (*.ehtml) has failed. NOTE that this is NOT used, when loading of a 1078 * _compiled_ template file (*.js) fails! 1079 */ 1080 var updateLoadStatus = function(status, hasLoadingFailed){ 1081 --status.currentLoadCount; 1082 1083 if(status.extLoadStatusFunc){ 1084 status.extLoadStatusFunc(status, hasLoadingFailed); 1085 } 1086 1087 checkCompletion(status); 1088 }; 1089 1090 /** 1091 * HELPER: creates a template-object (e.g. a View or a Partial) for the 1092 * raw template conent. 1093 * 1094 * If necessary, the parser-classes (module 'parseUtils') are loaded, 1095 * which are necessary to process the raw template content. 1096 * 1097 * @private 1098 * @function 1099 * @memberOf mmir.MediaManager.init 1100 * 1101 * @param {Controller} controller 1102 * the controller to which the template files belong. 1103 * May be <code>null</code>: in this case, this argument will be omitted when 1104 * creating the template object and creating the lookup-key (e.g. in case of a Layout). 1105 * 1106 * @param {String} templateName 1107 * the name for the template (e.g. file-name without extension) 1108 * 1109 * @param {PlainObject} createConfig 1110 * configuration that is used to create the template-object 1111 * for the template-contents: 1112 * <code>createConfig.constructor</code>: the constructor function 1113 * IF controller IS NOT null: <code>(controller, templateName, templateContent)</code> 1114 * (e.g. View, Partial) 1115 * IF controller IS null: <code>(templateName, templateContent)</code> 1116 * (e.g. Layout) 1117 * 1118 * <code>createConfig.collection</code>: the Dictionary to which the created 1119 * template-object will be added 1120 * <code>createConfig.keyGen</code>: a generator function for creating the lookup-key when adding 1121 * the template-object to the collection. 1122 * This function is invoked with <code>(controller.getName(), templateName)</code>, 1123 * that is <code>(String, String)</code>. 1124 * 1125 * NOTE: if controller IS null, the keyGen function will not be used, and 1126 * instead the template-object will be added with the 1127 * created template-object's name as lookup-key. 1128 * @param {PlainObject} status 1129 * the object for managing the loading status. 1130 * After creating and adding the template-object to the collection, the loading 1131 * status will be updated via {@link updateLoadStatus} 1132 * 1133 */ 1134 var doParseTemplate = function(controller, templateName, config, templateContent, status){ 1135 1136 //NOTE need to request renderUtils here too, since it is needed during parsing! 1137 require(['parseUtils', 'renderUtils'], function(){ 1138 1139 var templateObj; 1140 if(controller){ 1141 //"normal" view constructor: (Controller, nameAsString, templateAsString) 1142 templateObj = new config.constructor(controller, templateName , templateContent); 1143 config.collection.put( config.keyGen(controller.getName(), templateName), templateObj ); 1144 } 1145 else { 1146 //in case of Layout: omit controller argument 1147 // -> layout constructor: (nameAsString, templateAsString) 1148 // -> there is a 1:1 correspondence betwenn controller and layout, 1149 // and Layout.name === Controller.name 1150 // => no need to create a lookup-key 1151 templateObj = new config.constructor(templateName , templateContent); 1152 config.collection.put( templateObj.getName(), templateObj ); 1153 } 1154 1155 updateLoadStatus(status); 1156 1157 }); 1158 1159 }; 1160 1161 /** 1162 * Generic helper for loading a list of template files (*.ehtml) 1163 * that correspond to a specific template type (e.g. <code>View</code>s, or <code>Partial</code>s). 1164 * 1165 * If compiled representations of the template file exist AND is up-to-date AND 1166 * the configuration is set to load the compiled files rather than the raw template 1167 * file (see <code>isUsePreCompiledViews</code>), then the compiled template is used. 1168 * 1169 * 1170 * This function uses an asynchronous method for loading the template-files.<br> 1171 * 1172 * <b>If you want to make sure, that all templates have indeed been loaded, before 1173 * proceeding with the subsequent program flow, you should have a look at the 1174 * function {@link mmir.ControllerManager#foundControllersCallBack}: use the returned 1175 * Deferred.Promise for executing code that depends on the templates being loaded.</b> 1176 * 1177 * <p> 1178 * Uses {@link doloadTemplateFile} for loading single template files. 1179 * 1180 * @function 1181 * @private 1182 * @async 1183 * @memberOf mmir.MediaManager.init 1184 * 1185 * @see #doLoadTemplateFile 1186 * 1187 * @param {PlainObject} createConfig 1188 * configuration object that determines which templates are loaded, and how 1189 * the loaded data is processed. 1190 * 1191 * 1192 * @returns {Promise} a Deferred.promise, that resolves after all partials have been loaded 1193 * NOTE: loading failures will generate a warning message (on the console) 1194 * but will not cause the Promise to fail. 1195 */ 1196 var doProcessTemplateList = function(createConfig){ 1197 1198 /** 1199 * @type jQuery.Deferred 1200 * @private 1201 * @memberOf mmir.MediaManager.init.doProcessTemplateList 1202 */ 1203 var defer = $.Deferred(); 1204 1205 /** 1206 * @type String 1207 * @private 1208 * @memberOf mmir.MediaManager.init.doProcessTemplateList 1209 */ 1210 var ctrlNameList = controllerManager.getControllerNames(); 1211 1212 /** 1213 * HELPER object for tracking the loading-status of the views 1214 * 1215 * @private 1216 * @memberOf mmir.MediaManager.init.doProcessTemplateList 1217 */ 1218 var loadStatus = { 1219 loader: defer, 1220 remainingCtrlCount: ctrlNameList.length, 1221 currentLoadCount: 0 1222 }; 1223 1224 $.each(ctrlNameList, function(ctrlIndex, controllerName){ 1225 1226 var controller = controllerManager.getController(controllerName); 1227 1228 $.each(controller[createConfig.accessorName](), function(index, templateInfo){ 1229 1230 doLoadTemplateFile(controller, templateInfo, createConfig, loadStatus); 1231 1232 });//END: each(templateInfo) 1233 1234 -- loadStatus.remainingCtrlCount; 1235 checkCompletion(loadStatus); 1236 1237 });//END: each(ctrlName) 1238 1239 checkCompletion(loadStatus); 1240 return defer.promise(); 1241 1242 };//END: doProcessTemplateList() 1243 1244 /** 1245 * HELPER that loads a single template file asynchronously and creates a corresponding template-class instance 1246 * (depending on <code>createConfig</code>). 1247 * 1248 * The <code>status</code> is updated on successful loading or on error (see {@link updateLoadStatus}). 1249 * 1250 * @example 1251 * 1252 * //EXAMPLE for createConfig for loading template contents into a Partial 1253 * var theCreateConfig = { 1254 * constructor: Partial, // the class constructor that takes the loaded template data 1255 * typeName: 'Partial', // the name of the class that will be created 1256 * collection: _partials, // the map/dictionary to which the created class-instance will be added 1257 * keyGen: createPartialKey, // the function for creating the lookup-key (for the dictionary) 1258 * accessorName: 'getPartials' // the accessor-function's name for accessing the info-objects on the controller-instance 1259 * }; 1260 * doLoadTemplateFiles(theCreateConfig).then(function(){ 1261 * //do something that depends on loading of the template files... 1262 * }); 1263 * 1264 * //EXAMPLE for createConfig for loading template contents into a Layout 1265 * var theCreateLayoutConfig = { 1266 * constructor: Layout, // the class constructor that takes the loaded template data 1267 * typeName: 'Layout', // the name of the class that will be created 1268 * collection: _layouts, // the map/dictionary to which the created class-instance will be added 1269 * }; 1270 * 1271 * doLoadTemplateFiles(theCreateLayoutConfig).then(function(){ 1272 * //do something that depends on loading of the template files... 1273 * }); 1274 * 1275 * //for createConfig for loading template contents into a Partial 1276 * 1277 * @param {Controller} controller 1278 * the controller to which the template files belong. 1279 * May be <code>null</code>: see {@link doParseTemplate}. 1280 * 1281 * @param {PlainObject} templateInfo 1282 * the JSON-like object containing information for the template 1283 * (e.g. <code>name</code>, <code>file-path</code> etc.; see 1284 * {@link mmir.ControllerManager#getControllerResources} for 1285 * more information). 1286 * 1287 * @param {PlainObject} createConfig 1288 * configuration that is used to create a corresponding template-object 1289 * for the loaded template-contents. 1290 * The created object will be added to <code>createConfig.collection</code> 1291 * (Dictionary; with controller's name as key). 1292 * 1293 * @param {PlainObject} loadStatus 1294 * Object for managing the loading-status. The status is updated and used to 1295 * determine, if all templates (e.g. from a list) have been (asynchronously) 1296 * loaded. 1297 * 1298 * @function 1299 * @private 1300 * @async 1301 * @memberOf mmir.MediaManager.init 1302 */ 1303 var doLoadTemplateFile = function(controller, templateInfo, createConfig, loadStatus){ 1304 ++loadStatus.currentLoadCount; 1305 1306 $.ajax({ 1307 async: true, 1308 dataType: "text", 1309 url: templateInfo.path, 1310 success: function(data){ 1311 1312 if(isUsePreCompiledViews){ 1313 1314 loadPrecompiledView(data, templateInfo.genPath, function(){ 1315 1316 updateLoadStatus(loadStatus); 1317 1318 }, function(err){ 1319 1320 console.warn('Could not load precompiled '+createConfig.typeName+' from ' 1321 +templateInfo.genPath+'", because: '+err 1322 +', compiling template instead: ' 1323 +templateInfo.path 1324 ); 1325 1326 doParseTemplate(controller, templateInfo.name, createConfig , data, loadStatus); 1327 1328 }); 1329 1330 } 1331 else { 1332 1333 doParseTemplate(controller, templateInfo.name, createConfig, data, loadStatus); 1334 1335 } 1336 } 1337 1338 }).fail(function(jqxhr, status, err){ 1339 1340 // print out an error message 1341 var errMsg = err && err.stack? err.stack : err; 1342 console.error("[" + status + "] Could not load '" + templateInfo.path + "': "+errMsg); //failure 1343 1344 updateLoadStatus(loadStatus, true); 1345 }); 1346 1347 checkCompletion(loadStatus); 1348 1349 };//END: doLoadTemplateFile() 1350 1351 1352 ///////////// start intialization: //////////////// 1353 1354 /** 1355 * Deferred / promise for loading views. 1356 * 1357 * @type jQuery.Deferred 1358 * @private 1359 * @memberOf mmir.MediaManager.init 1360 */ 1361 var defer = $.Deferred(); 1362 1363 var isLayoutsLoaded = false; 1364 var isViewsLoaded = false; 1365 var isPartialsLoaded = false; 1366 var isViewEngineLoaded = false;//MOD modularize view-engine jqm 1367 1368 /** 1369 * Helper: called each time a loading-function finishes. 1370 * Checks if all other loading-functions have finished, and if so, resolves the init-promise. 1371 * 1372 * @private 1373 * @memberOf mmir.MediaManager.init 1374 */ 1375 var checkResolved = function(){ 1376 if(isLayoutsLoaded && isViewsLoaded && isPartialsLoaded && isViewEngineLoaded){ 1377 defer.resolve(); 1378 } 1379 }; 1380 /** 1381 * Helper: called if an error occured in one of the loading-functions: 1382 * rejects/fails the init-promise. 1383 * 1384 * @private 1385 * @memberOf mmir.MediaManager.init 1386 */ 1387 var failPromise = function(msg){ 1388 defer.reject(msg); 1389 }; 1390 1391 //util for checking if pre-compiled views are up-to-date 1392 // (i.e.: can we use the pre-compiled view, or do we need to use the template file and compile it on-the-fly) 1393 //TODO should this also be configurable -> up-to-date check (e.g. use pre-compiled views without checking for changes) 1394 checksumUtils = checksumUtils.init(); 1395 1396 loadLayouts().then( 1397 function(){ isLayoutsLoaded = true; checkResolved(); }, 1398 failPromise 1399 ); 1400 loadViews().then( 1401 function(){ isViewsLoaded = true; checkResolved(); }, 1402 failPromise 1403 ); 1404 loadPartials().then( 1405 function(){ isPartialsLoaded = true; checkResolved(); }, 1406 failPromise 1407 ); 1408 1409 //MOD modularize view-engine jqm: load viewEngine (default is based on jQuery Mobile) 1410 require([core.viewEngine], function(viewEngineInit){ 1411 viewEngineInit.then( 1412 function(viewEngine){ 1413 _instance.setRenderEngine(viewEngine); 1414 isViewEngineLoaded = true; 1415 checkResolved(); 1416 }, failPromise 1417 ); 1418 }); 1419 1420 return defer.promise(_instance); 1421 1422 };//END: init() 1423 1424 }); 1425