define([ 'mmirf/controllerManager', 'mmirf/commonUtils', 'mmirf/viewLoader', 'mmirf/logger'
, 'mmirf/util/deferredWithState', 'mmirf/core', 'module', 'require'
],
/**
* @class
* @name mmir.PresentationManager
* @static
* @hideconstructor
*
* @requires dialogManager if reRenderView() or renderPreviousView() are used
*
*/
function ( controllerManager, commonUtils, viewLoader, Logger
, deferred, core, module, require
) {
//the next comment enables JSDoc2 to map all functions etc. to the correct class description
/** @scope mmir.PresentationManager.prototype */
/**
* Counter that keeps track of the number of times, that a view is rendered
*
* NOTE: for implementation specific reasons, jQuery Mobile requires that
* each page has a different ID. This pageIndex is used to generating
* such a unique ID, by increasing the number on each page-change
* (i.e. by rendering a view) and appending it to the page's ID/name.
*
* @type Integer
* @public
* @memberOf mmir.PresentationManager#
*/
var _pageIndex = 0;
/**
* Name for the default layout.
*
* <p>
* There must exist a layout definition by
* this name, i.e.
* <pre>views/layout/<DEFAULT_LAYOUT_NAME>.ehtml</pre>
*
* NOTE: while the name begins with an upper case
* letter, the file name for the layout must
* start with a lower case letter, e.g. for
* name <code>Default</code>, the file name
* must be <code>default.ehtml</code>.
*
* @type String
* @private
* @constant
* @memberOf mmir.PresentationManager#
*
* @example var defaultLayoutName = mmir.conf.get(mmir.presentation.CONFIG_DEFAULT_LAYOUT);
*/
var DEFAULT_LAYOUT_NAME = 'Default';
/**
* Name of the configuration property that specifies a custom name for the default layout.
*
* <p>
* NOTE: if FALSY (other than <code>undefined</code>) no default layout will be loaded.
* Rendering views may fail, if they rely on a {@link mmir.view.Layout}!
*
* @type String
* @private
* @constant
* @memberOf mmir.PresentationManager#
*
* @example var defaultLayout = mmir.conf.get(mmir.presentation.CONFIG_DEFAULT_LAYOUT_NAME);
*
*/
var CONFIG_DEFAULT_LAYOUT_NAME = 'defaultLayoutName';//TODO move this to somewhere else (collected config-vars?)? this should be a public CONSTANT...
// private members
/**
* The logger for the PresentationManager.
*
* @private
* @type mmir.tools.Logger
* @memberOf mmir.PresentationManager#
*/
var logger = Logger.create(module);//initialize with requirejs-module information
/**
* Array of layouts of the application
*
* @type Map
* @private
* @memberOf mmir.PresentationManager#
*/
var _layouts = new Map();
/**
* Array of all the views of the application
*
* @type Map
* @private
* @memberOf mmir.PresentationManager#
*/
var _views = new Map();
/**
* Array of all the partials of the application
*
* @type Map
* @private
* @memberOf mmir.PresentationManager#
*/
var _partials = new Map();
/**
* The currently displayed dialog object, if a dialog is displayed. Used
* mainly to close the dialog.
*
* @type Object
* @private
* @memberOf mmir.PresentationManager#
*
* @see mmir.PresentationManager#showDialog
* @see mmir.PresentationManager#hideCurrentDialog
*/
var _currentDialog = null;
/**
* @private
* @memberOf mmir.PresentationManager#
*/
var viewSeparator = '#';
/**
* @private
* @memberOf mmir.PresentationManager#
*/
var reHandlerName = /^on_/;
/**
* @type String
* @private
* @memberOf mmir.PresentationManager#
*/
var partialSeparator = commonUtils.getPartialsPrefix();
/**
* @private
* @memberOf mmir.PresentationManager#
*/
function createLookupKey(ctrl, viewObj, separator){
if(typeof ctrl.getName !== 'undefined'){
ctrl = ctrl.getName();
}
if(typeof viewObj.getName !== 'undefined'){
viewObj = viewObj.getName();
}
//TODO remove all >partialSeparator< from partial-string beginning
return ctrl+separator+viewObj;
}
/**
* @private
* @memberOf mmir.PresentationManager#
*/
function createViewKey(ctrl, view){
return createLookupKey(ctrl, view, viewSeparator);
}
/**
* @private
* @memberOf mmir.PresentationManager#
*/
function createPartialKey(ctrl, partial){
return createLookupKey(ctrl, partial, partialSeparator);
}
/**
* Default implementation for the rendering-engine:
*
* does nothing but writing an error message to the console,
* if any of its functions is invoked.
*
* The rendering engine can be set via {@link mmir.PresentationManager#setRenderEngine}.
*
* @type RenderEngine
* @private
* @memberOf mmir.PresentationManager#
*/
var _renderEngine = {
/**
* The function that actually renders the View.<br>
*
* The function will be invoked in context of the PresentationManager instance
* (i.e. the manager will be the <em>this</em> context).
*
* Implementations of this function should adhere to the following procedure:
*
* <br>
*
* First this function fetches the <em>layout</em> for the <em>controller</em>
* (or uses the <code>dialogManager.DEFAULT_LAYOUT<code>).
*
* Then the <code>before_page_prepare</code> of the <em>controller</em> is invoked (if it exists).
*
* Then renders the <em>view</em> into the
* layout-template; <em>partials</em>, <em>helpers</em> etc.
* that are referenced in the <em>view</em> will be processed,
* executed etc.; during this, localized Strings should be processed and rendered by
* {@link mmir.LanguageManager#getText}.
*
* Then <em>dialogs</em> are created and the <code>dialogManager.pageIndex</code> is updated.
*
* The new content is inserted into the document/page (invisibly).
*
* Then the <code>before_page_load</code> of the <em>controller</em> is invoked (if it exists).
*
* The new content/page is made visible, and the old one invisible and / or is removed.
*
* At the end the <b>on_page_load</b> action of the <em>controller</em> is performed.
*
* @function
* @memberOf mmir.PresentationManager._renderingEngine
*
* @param {String}
* ctrlName Name of the controller
* @param {String}
* viewName Name of the view to render
* @param {mmir.view.View}
* view View object that is to be rendered
* @param {mmir.ctrl.Controller}
* ctrl Controller object of the view to render
* @param {Object}
* [data] optional data for the view.
* @returns {void|Promise}
* if void/undefined is returned, the view is rendered synchronously, i.e.
* the view is rendered, when this method returns.
* If a Promise is returned, the view is rendered asynchronously
* (rendering is finished, when the promise is resolved)
*/
render: function(ctrlName, viewName, view, ctrl, data){
logger.error('PresentationManager.render: no rendering engine set!');
},
showDialog: function(ctrlName, dialogId, data) {
logger.error('PresentationManager.showDialog: no rendering engine set!');
},
hideCurrentDialog: function(){
logger.error('PresentationManager.hideCurrentDialog: no rendering engine set!');
},
showWaitDialog: function(text, data) {
logger.error('PresentationManager.showWaitDialog: no rendering engine set!');
},
hideWaitDialog: function() {
logger.error('PresentationManager.hideWaitDialog: no rendering engine set!');
}
};
/**
* Reference to the rendering-engine implementation / instance.
*
* This reference should not be accessed directly.
* Custom functions of the rendering implementation can be
* invoked via {@link mmir.PresentationManager#callRenderEngine}.
*
* @type Object
* @private
*/
_renderEngine._engine = _renderEngine;
var _instance = {
/** @scope mmir.PresentationManager.prototype */
// public members
/**
* @param {mmir.view.Layout} layout
* the layout to add
* @memberOf mmir.PresentationManager.prototype
*/
addLayout : function(layout) {
_layouts.set(layout.getName(), layout);
},
/**
* This function returns a layout object by name.<br>
*
* @function
* @param {String}
* layoutName Name of the layout which should be returned
* @param {Boolean}
* [doUseDefaultIfMissing] if supplied and
* <code>true</code>, the default controller's layout
* will be used as a fallback, in case no corresponding
* layout could be found
* @returns {mmir.view.Layout} The requested layout, "false" if not found
* @public
* @memberOf mmir.PresentationManager.prototype
*/
getLayout : function(layoutName, doUseDefaultIfMissing) {
var layout = false;
layout = _layouts.get(layoutName);
if (!layout) {
if (doUseDefaultIfMissing) {
layout = _instance.getLayout(DEFAULT_LAYOUT_NAME, false);
}
else {
logger.error('[PresentationManager.getLayout]: could not find layout "' + layoutName +'"')
return false;
}
}
return layout;
},
/**
*
* @param {String|Controller} ctrlName
* @param {String|mmir.view.View} view
* @public
* @memberOf mmir.PresentationManager.prototype
*/
addView : function(ctrlName, view) {
_views.set(createViewKey(ctrlName, view), view);
},
/**
* This function returns a view object by name.<br>
*
* @function
* @param {String}
* controllerName Name of the controller for the view
* @param {String}
* viewName Name of the view which should be returned
* @returns {mmir.view.View} The requested view, <tt>false</tt> if not
* found
* @public
* @memberOf mmir.PresentationManager.prototype
*/
getView : function(controllerName, viewName) {
viewName = createViewKey(controllerName, viewName);
var view = false;
view = _views.get(viewName);
if (!view) {
logger.error('[PresentationManager.getView]: could not find view "' + viewName + '"');
return false;
}
return view;
},
/**
*
* @param {String|Controller} ctrlName
* @param {String|mmir.view.Partial} partial
*
* @public
* @memberOf mmir.PresentationManager.prototype
*/
addPartial: function(ctrlName, partial){
_partials.set(createPartialKey(ctrlName, partial), partial);
},
/**
* This function returns a partial object by name.<br>
*
* @function
* @param {String}
* controllerName Name of the controller for the view
* @param {String}
* viewName Name of the partial which should be returned
* @returns {mmir.view.Partial} The requested partial, "false" if not found
* @public
* @memberOf mmir.PresentationManager.prototype
*/
getPartial : function(controllerName, partialName) {
var partial = false;
var partialKey = null;
if (controllerName) {
partialKey = createPartialKey(controllerName, partialName);
}
else {
logger.error('[PresentationManager.getPartial]: requested partial "' + partialName + '" for unknown controller: "' + (controllerName ? (controllerName.getName? controllerName.getName(): controllerName) : 'undefined')
+ '"');
return false;
}
partial = _partials.get(partialKey);
if (!partial) {
logger.error('[PresentationManager.getPartial]: could not find partial "' + partialName + '" for controller "' + (controllerName ? (controllerName.getName? controllerName.getName(): controllerName) : 'undefined') + '"!');
return false;
}
return partial;
},
/**
* Closes a modal window / dialog (if one is open).
* <br>
*
* @function
* @public
* @memberOf mmir.PresentationManager.prototype
*/
hideCurrentDialog : function() {
_renderEngine.hideCurrentDialog.apply(this,arguments);
},
/**
* Opens the dialog for ID <code>dialogId</code>.
* <br>
*
* @function
* @param {String} ctrlName
* the Name of the controller
* @param {String} dialogId
* the ID of the dialog
* @param {Object} [data] OPTIONAL
* a data / options object
*
* @returns {Object} the instance of the opened dialog (void or falsy if dialog was not opened)
*
* @public
* @memberOf mmir.PresentationManager.prototype
*/
showDialog : function(ctrlName, dialogId, data) {
_currentDialog = _renderEngine.showDialog.apply(this,arguments);
return _currentDialog;
},
/**
* Shows a "wait" dialog, i.e. "work in progress" notification.
*
* @function
*
* @param {String} [text] OPTIONAL
* the text that should be displayed.
* If omitted the language setting for <code>loadingText</code>
* will be used instead (from dictionary.json)
* @param {Object} [data] OPTIONAL
* a data / options object
*
* @public
* @memberOf mmir.PresentationManager.prototype
*
* @see mmir.PresentationManager#hideWaitDialog
*/
showWaitDialog : function(text, data) {
_renderEngine.showWaitDialog.apply(this,arguments);
},
/**
* Hides / closes the "wait" dialog.
*
* @function
* @public
* @memberOf mmir.PresentationManager.prototype
*
* @see mmir.PresentationManager#showWaitDialog
*/
hideWaitDialog : function() {
_renderEngine.hideWaitDialog.apply(this,arguments);
},
/**
* Gets the view for a controller, then executes helper methods on
* the view data. The Rendering of the view is done by the
* {@link #doRenderView} method. Also
* stores the previous and current view with parameters.<br>
*
* @function
* @param {String}
* ctrlName Name of the controller
* @param {String}
* viewName Name of the view to render
* @param {Object}
* [data] optional data for the view.
* @returns {void|Promise}
* if void/undefined is returned, the view is rendered synchronously, i.e.
* the view is rendered, when this method returns.
* If a Promise is returned, the view is rendered asynchronously
* (rendering is finished, when the promise is resolved)
*
* @public
* @memberOf mmir.PresentationManager.prototype
*/
render : function(ctrlName, viewName, data) {
var ctrl = controllerManager.get(ctrlName);
var renderResult;
if (ctrl != null) {
var view = this.getView(ctrlName, viewName);
if(!view){
logger.error('PresentationManager.renderView: could not find view "'+viewName+'" for controller "'+ctrlName+'"');
return;
}
renderResult = _renderEngine.render.call(this, ctrlName, viewName, view, ctrl, data);
}
else {
logger.error('PresentationManager.renderView: could not retrieve controller "'+ctrlName+'"');
}
return renderResult;
},
/**
* Helper for emitting pre-/post-render events on controller instance and/or
* PresentationManager instance.
*
* @function
*
* @param {mmir.ctrl.Controller}
* ctrl the controller instance for which the render event should be emitted
* @param {String}
* eventName Name of the event: "page_load", ...
* @param {Object}
* [eventData] OPTIONAL the rendering data
* @param {Object}
* [pageData] OPTIONAL the page data/rendering options (may not be present for all events)
* @param {Object}
* [data] optional data for the view.
* @returns {any | false}
* if false returned by an event handler, cancalebale rendering actions will stopped
*/
_fireRenderEvent: function(ctrl, eventName, eventData, pageData){
var isContinue = ctrl.performIfPresent(eventName, eventData, pageData);
//if "global" event handler is set:
var evtHandlerName = reHandlerName.test(eventName)? eventName : 'on_' + eventName;
if(typeof this[evtHandlerName] === 'function'){
//if global event handler cancels rendering (note: may not be possible for all type of events)
if(this[evtHandlerName](ctrl.getName(), eventName, eventData, pageData) === false){
isContinue = false;
}
}
return isContinue;
},
/**
* @function
* @async
* @memberOf mmir.PresentationManager.prototype
* @returns {Promise}
* a deferred promise that gets fulfilled when initialization is completed.
*/
init: function(){
var defer = deferred();
var isViewEngineLoaded = false;//MOD modularize view-engine
var isViewsLoaded = false;//MOD modularize view-loading & -compiling
var checkResolved = function(){
if(isViewEngineLoaded && isViewsLoaded){
defer.resolve(_instance);
}
};
var failPromise = function(msg){
defer.reject(msg);
};
//MOD modularize view-engine: load viewEngine (default uses standard HTML document API)
require([typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD? 'mmirf/simpleViewEngine' : core.viewEngine], function(viewEngineInit){//FIXME
viewEngineInit.then(
function(viewEngine){
_instance.setRenderEngine(viewEngine);
isViewEngineLoaded = true;
checkResolved();
}, failPromise
);
});
viewLoader(
_instance, _layouts, _views, _partials, createViewKey, createPartialKey
).then(function(){
isViewsLoaded = true;
checkResolved();
}, failPromise);
return defer;
},// init,
/**
* Sets the <em>rendering engine</em> for the views.
*
* The render engine <b>must</b> implement a function <em>render</em>
* and <i>may</i> implement functions <em>showDialog</em>,
* <em>hideCurrentDialog</em>, <em>showWaitDialog</em>, and <em>hideWaitDialog</em>:
*
* <ul>
* <li><b>theRenderEngine.<code>render(ctrlName : String, viewName : String, view : View, ctrl : Controller, data : Object) : void|Promise</code></b></li>
* <li>theRenderEngine.<code>showDialog(ctrlName : String, dialogId : String, data : Object) : Dialog</code></li>
* <li>theRenderEngine.<code>hideCurrentDialog(): void</code></li>
* <li>theRenderEngine.<code>showWaitDialog(text : String, data : Object): void</code></li>
* <li>theRenderEngine.<code>hideWaitDialog(): void</code></li>
* </ul>
*
* The functions of <code>theRenderEngine</code> will be called in
* context of the PresentationManager.
*
* Custom functions of the specific rendering engine implementation
* (i.e. non-standard functions) can be call via {@link #callRenderEngine}.
*
*
* <br>
* By default, the rendering-engine as defined by the module ID/path in
* <code>core.viewEngine</code> will be loaded and set during initialization
* of the DialogManager.
*
* <br>
* The implementation of the default view-engine is at
* <code>mmirf/env/view/presentation/simpleViewEngine.js</code>.
*
* @param {Object} theRenderEngine
* the render-engine for views
*
* @function
* @public
* @memberOf mmir.PresentationManager.prototype
*
* @see mmir.PresentationManager#renderView
* @see mmir.PresentationManager#showDialog
* @see mmir.PresentationManager#hideCurrentDialog
* @see mmir.PresentationManager#showWaitDialog
* @see mmir.PresentationManager#hideWaitDialog
*
* @see mmir.PresentationManager#callRenderEngine
*
*/
setRenderEngine: function(theRenderEngine){
_renderEngine.render = theRenderEngine.render;
_renderEngine.showDialog = theRenderEngine.showDialog;
_renderEngine.hideCurrentDialog = theRenderEngine.hideCurrentDialog;
_renderEngine.showWaitDialog = theRenderEngine.showWaitDialog;
_renderEngine.hideWaitDialog = theRenderEngine.hideWaitDialog;
_renderEngine._engine = theRenderEngine;
},
/**
* This function allows to call custom functions of the rendering-engine
* that was set via {@link #setRenderEngine}.
*
* IMPORTANT:
* note that the function will be invoked in context of rendering-engine
* (i.e. <code>this</code> references will refer to rendering-engine
* and not to the PresentationManager instance.
* For example, when <code>mmir.PresentationManager.callRenderEngine('hideWaitDialog')</code>
* is called, any <code>this</code> references within the <code>hideWaitDialog</code>
* implementation would refer to object, that was set in <code>setRenderEngine(object)</code>.
* In comparison, when called as <code>mmir.PresentationManager.hideWaitDialog()</code> the
* <code>this</code> references refer to the mmir.PresentationManager instance.
*
* <br>
* NOTE that calling non-existing functions on the rendering-engine
* will cause an error.
*
* @param {String} funcName
* the name of the function, that should be invoked on the rendering
* engine.
* @param {Array<any>} [args] OPTIONAL
* the arguments for <code>funcName</code> invoked
* via <code>Function.apply(renderingEngine, args)</code>, e.g.
* for <code>args = [param1, param2, param3]</code> the
* function will be called with
* <code>funcName(param1, param2, param3)</code>
* (note that the function receives 3 arguments, and
* not 1 Array-argument).
*
* @function
* @public
* @memberOf mmir.PresentationManager.prototype
*/
callRenderEngine: function(funcName, args){
return _renderEngine._engine[funcName].apply(_renderEngine._engine, args);
},
//exported properties / constants:
/**
* @public
* @type Integer
* @constant
* @memberOf mmir.PresentationManager.prototype
*/
pageIndex: _pageIndex
};//END: _instance = {...
//export constants:
_instance.DEFAULT_LAYOUT_NAME = DEFAULT_LAYOUT_NAME;
_instance.CONFIG_DEFAULT_LAYOUT_NAME = CONFIG_DEFAULT_LAYOUT_NAME;
return _instance;
});
//});