Source: manager/presentationManager.js

  1. define([ 'mmirf/controllerManager', 'mmirf/commonUtils', 'mmirf/viewLoader', 'mmirf/logger'
  2. , 'mmirf/util/deferredWithState', 'mmirf/core', 'module', 'require'
  3. ],
  4. /**
  5. * @class
  6. * @name mmir.PresentationManager
  7. * @static
  8. * @hideconstructor
  9. *
  10. * @requires dialogManager if reRenderView() or renderPreviousView() are used
  11. *
  12. */
  13. function ( controllerManager, commonUtils, viewLoader, Logger
  14. , deferred, core, module, require
  15. ) {
  16. //the next comment enables JSDoc2 to map all functions etc. to the correct class description
  17. /** @scope mmir.PresentationManager.prototype */
  18. /**
  19. * Counter that keeps track of the number of times, that a view is rendered
  20. *
  21. * NOTE: for implementation specific reasons, jQuery Mobile requires that
  22. * each page has a different ID. This pageIndex is used to generating
  23. * such a unique ID, by increasing the number on each page-change
  24. * (i.e. by rendering a view) and appending it to the page's ID/name.
  25. *
  26. * @type Integer
  27. * @public
  28. * @memberOf mmir.PresentationManager#
  29. */
  30. var _pageIndex = 0;
  31. /**
  32. * Name for the default layout.
  33. *
  34. * <p>
  35. * There must exist a layout definition by
  36. * this name, i.e.
  37. * <pre>views/layout/<DEFAULT_LAYOUT_NAME>.ehtml</pre>
  38. *
  39. * NOTE: while the name begins with an upper case
  40. * letter, the file name for the layout must
  41. * start with a lower case letter, e.g. for
  42. * name <code>Default</code>, the file name
  43. * must be <code>default.ehtml</code>.
  44. *
  45. * @type String
  46. * @private
  47. * @constant
  48. * @memberOf mmir.PresentationManager#
  49. *
  50. * @example var defaultLayoutName = mmir.conf.get(mmir.presentation.CONFIG_DEFAULT_LAYOUT);
  51. */
  52. var DEFAULT_LAYOUT_NAME = 'Default';
  53. /**
  54. * Name of the configuration property that specifies a custom name for the default layout.
  55. *
  56. * <p>
  57. * NOTE: if FALSY (other than <code>undefined</code>) no default layout will be loaded.
  58. * Rendering views may fail, if they rely on a {@link mmir.view.Layout}!
  59. *
  60. * @type String
  61. * @private
  62. * @constant
  63. * @memberOf mmir.PresentationManager#
  64. *
  65. * @example var defaultLayout = mmir.conf.get(mmir.presentation.CONFIG_DEFAULT_LAYOUT_NAME);
  66. *
  67. */
  68. var CONFIG_DEFAULT_LAYOUT_NAME = 'defaultLayoutName';//TODO move this to somewhere else (collected config-vars?)? this should be a public CONSTANT...
  69. // private members
  70. /**
  71. * The logger for the PresentationManager.
  72. *
  73. * @private
  74. * @type mmir.tools.Logger
  75. * @memberOf mmir.PresentationManager#
  76. */
  77. var logger = Logger.create(module);//initialize with requirejs-module information
  78. /**
  79. * Array of layouts of the application
  80. *
  81. * @type Map
  82. * @private
  83. * @memberOf mmir.PresentationManager#
  84. */
  85. var _layouts = new Map();
  86. /**
  87. * Array of all the views of the application
  88. *
  89. * @type Map
  90. * @private
  91. * @memberOf mmir.PresentationManager#
  92. */
  93. var _views = new Map();
  94. /**
  95. * Array of all the partials of the application
  96. *
  97. * @type Map
  98. * @private
  99. * @memberOf mmir.PresentationManager#
  100. */
  101. var _partials = new Map();
  102. /**
  103. * The currently displayed dialog object, if a dialog is displayed. Used
  104. * mainly to close the dialog.
  105. *
  106. * @type Object
  107. * @private
  108. * @memberOf mmir.PresentationManager#
  109. *
  110. * @see mmir.PresentationManager#showDialog
  111. * @see mmir.PresentationManager#hideCurrentDialog
  112. */
  113. var _currentDialog = null;
  114. /**
  115. * @private
  116. * @memberOf mmir.PresentationManager#
  117. */
  118. var viewSeparator = '#';
  119. /**
  120. * @private
  121. * @memberOf mmir.PresentationManager#
  122. */
  123. var reHandlerName = /^on_/;
  124. /**
  125. * @type String
  126. * @private
  127. * @memberOf mmir.PresentationManager#
  128. */
  129. var partialSeparator = commonUtils.getPartialsPrefix();
  130. /**
  131. * @private
  132. * @memberOf mmir.PresentationManager#
  133. */
  134. function createLookupKey(ctrl, viewObj, separator){
  135. if(typeof ctrl.getName !== 'undefined'){
  136. ctrl = ctrl.getName();
  137. }
  138. if(typeof viewObj.getName !== 'undefined'){
  139. viewObj = viewObj.getName();
  140. }
  141. //TODO remove all >partialSeparator< from partial-string beginning
  142. return ctrl+separator+viewObj;
  143. }
  144. /**
  145. * @private
  146. * @memberOf mmir.PresentationManager#
  147. */
  148. function createViewKey(ctrl, view){
  149. return createLookupKey(ctrl, view, viewSeparator);
  150. }
  151. /**
  152. * @private
  153. * @memberOf mmir.PresentationManager#
  154. */
  155. function createPartialKey(ctrl, partial){
  156. return createLookupKey(ctrl, partial, partialSeparator);
  157. }
  158. /**
  159. * Default implementation for the rendering-engine:
  160. *
  161. * does nothing but writing an error message to the console,
  162. * if any of its functions is invoked.
  163. *
  164. * The rendering engine can be set via {@link mmir.PresentationManager#setRenderEngine}.
  165. *
  166. * @type RenderEngine
  167. * @private
  168. * @memberOf mmir.PresentationManager#
  169. */
  170. var _renderEngine = {
  171. /**
  172. * The function that actually renders the View.<br>
  173. *
  174. * The function will be invoked in context of the PresentationManager instance
  175. * (i.e. the manager will be the <em>this</em> context).
  176. *
  177. * Implementations of this function should adhere to the following procedure:
  178. *
  179. * <br>
  180. *
  181. * First this function fetches the <em>layout</em> for the <em>controller</em>
  182. * (or uses the <code>dialogManager.DEFAULT_LAYOUT<code>).
  183. *
  184. * Then the <code>before_page_prepare</code> of the <em>controller</em> is invoked (if it exists).
  185. *
  186. * Then renders the <em>view</em> into the
  187. * layout-template; <em>partials</em>, <em>helpers</em> etc.
  188. * that are referenced in the <em>view</em> will be processed,
  189. * executed etc.; during this, localized Strings should be processed and rendered by
  190. * {@link mmir.LanguageManager#getText}.
  191. *
  192. * Then <em>dialogs</em> are created and the <code>dialogManager.pageIndex</code> is updated.
  193. *
  194. * The new content is inserted into the document/page (invisibly).
  195. *
  196. * Then the <code>before_page_load</code> of the <em>controller</em> is invoked (if it exists).
  197. *
  198. * The new content/page is made visible, and the old one invisible and / or is removed.
  199. *
  200. * At the end the <b>on_page_load</b> action of the <em>controller</em> is performed.
  201. *
  202. * @function
  203. * @memberOf mmir.PresentationManager._renderingEngine
  204. *
  205. * @param {String}
  206. * ctrlName Name of the controller
  207. * @param {String}
  208. * viewName Name of the view to render
  209. * @param {mmir.view.View}
  210. * view View object that is to be rendered
  211. * @param {mmir.ctrl.Controller}
  212. * ctrl Controller object of the view to render
  213. * @param {Object}
  214. * [data] optional data for the view.
  215. * @returns {void|Promise}
  216. * if void/undefined is returned, the view is rendered synchronously, i.e.
  217. * the view is rendered, when this method returns.
  218. * If a Promise is returned, the view is rendered asynchronously
  219. * (rendering is finished, when the promise is resolved)
  220. */
  221. render: function(ctrlName, viewName, view, ctrl, data){
  222. logger.error('PresentationManager.render: no rendering engine set!');
  223. },
  224. showDialog: function(ctrlName, dialogId, data) {
  225. logger.error('PresentationManager.showDialog: no rendering engine set!');
  226. },
  227. hideCurrentDialog: function(){
  228. logger.error('PresentationManager.hideCurrentDialog: no rendering engine set!');
  229. },
  230. showWaitDialog: function(text, data) {
  231. logger.error('PresentationManager.showWaitDialog: no rendering engine set!');
  232. },
  233. hideWaitDialog: function() {
  234. logger.error('PresentationManager.hideWaitDialog: no rendering engine set!');
  235. }
  236. };
  237. /**
  238. * Reference to the rendering-engine implementation / instance.
  239. *
  240. * This reference should not be accessed directly.
  241. * Custom functions of the rendering implementation can be
  242. * invoked via {@link mmir.PresentationManager#callRenderEngine}.
  243. *
  244. * @type Object
  245. * @private
  246. */
  247. _renderEngine._engine = _renderEngine;
  248. var _instance = {
  249. /** @scope mmir.PresentationManager.prototype */
  250. // public members
  251. /**
  252. * @param {mmir.view.Layout} layout
  253. * the layout to add
  254. * @memberOf mmir.PresentationManager.prototype
  255. */
  256. addLayout : function(layout) {
  257. _layouts.set(layout.getName(), layout);
  258. },
  259. /**
  260. * This function returns a layout object by name.<br>
  261. *
  262. * @function
  263. * @param {String}
  264. * layoutName Name of the layout which should be returned
  265. * @param {Boolean}
  266. * [doUseDefaultIfMissing] if supplied and
  267. * <code>true</code>, the default controller's layout
  268. * will be used as a fallback, in case no corresponding
  269. * layout could be found
  270. * @returns {mmir.view.Layout} The requested layout, "false" if not found
  271. * @public
  272. * @memberOf mmir.PresentationManager.prototype
  273. */
  274. getLayout : function(layoutName, doUseDefaultIfMissing) {
  275. var layout = false;
  276. layout = _layouts.get(layoutName);
  277. if (!layout) {
  278. if (doUseDefaultIfMissing) {
  279. layout = _instance.getLayout(DEFAULT_LAYOUT_NAME, false);
  280. }
  281. else {
  282. logger.error('[PresentationManager.getLayout]: could not find layout "' + layoutName +'"')
  283. return false;
  284. }
  285. }
  286. return layout;
  287. },
  288. /**
  289. *
  290. * @param {String|Controller} ctrlName
  291. * @param {String|mmir.view.View} view
  292. * @public
  293. * @memberOf mmir.PresentationManager.prototype
  294. */
  295. addView : function(ctrlName, view) {
  296. _views.set(createViewKey(ctrlName, view), view);
  297. },
  298. /**
  299. * This function returns a view object by name.<br>
  300. *
  301. * @function
  302. * @param {String}
  303. * controllerName Name of the controller for the view
  304. * @param {String}
  305. * viewName Name of the view which should be returned
  306. * @returns {mmir.view.View} The requested view, <tt>false</tt> if not
  307. * found
  308. * @public
  309. * @memberOf mmir.PresentationManager.prototype
  310. */
  311. getView : function(controllerName, viewName) {
  312. viewName = createViewKey(controllerName, viewName);
  313. var view = false;
  314. view = _views.get(viewName);
  315. if (!view) {
  316. logger.error('[PresentationManager.getView]: could not find view "' + viewName + '"');
  317. return false;
  318. }
  319. return view;
  320. },
  321. /**
  322. *
  323. * @param {String|Controller} ctrlName
  324. * @param {String|mmir.view.Partial} partial
  325. *
  326. * @public
  327. * @memberOf mmir.PresentationManager.prototype
  328. */
  329. addPartial: function(ctrlName, partial){
  330. _partials.set(createPartialKey(ctrlName, partial), partial);
  331. },
  332. /**
  333. * This function returns a partial object by name.<br>
  334. *
  335. * @function
  336. * @param {String}
  337. * controllerName Name of the controller for the view
  338. * @param {String}
  339. * viewName Name of the partial which should be returned
  340. * @returns {mmir.view.Partial} The requested partial, "false" if not found
  341. * @public
  342. * @memberOf mmir.PresentationManager.prototype
  343. */
  344. getPartial : function(controllerName, partialName) {
  345. var partial = false;
  346. var partialKey = null;
  347. if (controllerName) {
  348. partialKey = createPartialKey(controllerName, partialName);
  349. }
  350. else {
  351. logger.error('[PresentationManager.getPartial]: requested partial "' + partialName + '" for unknown controller: "' + (controllerName ? (controllerName.getName? controllerName.getName(): controllerName) : 'undefined')
  352. + '"');
  353. return false;
  354. }
  355. partial = _partials.get(partialKey);
  356. if (!partial) {
  357. logger.error('[PresentationManager.getPartial]: could not find partial "' + partialName + '" for controller "' + (controllerName ? (controllerName.getName? controllerName.getName(): controllerName) : 'undefined') + '"!');
  358. return false;
  359. }
  360. return partial;
  361. },
  362. /**
  363. * Closes a modal window / dialog (if one is open).
  364. * <br>
  365. *
  366. * @function
  367. * @public
  368. * @memberOf mmir.PresentationManager.prototype
  369. */
  370. hideCurrentDialog : function() {
  371. _renderEngine.hideCurrentDialog.apply(this,arguments);
  372. },
  373. /**
  374. * Opens the dialog for ID <code>dialogId</code>.
  375. * <br>
  376. *
  377. * @function
  378. * @param {String} ctrlName
  379. * the Name of the controller
  380. * @param {String} dialogId
  381. * the ID of the dialog
  382. * @param {Object} [data] OPTIONAL
  383. * a data / options object
  384. *
  385. * @returns {Object} the instance of the opened dialog (void or falsy if dialog was not opened)
  386. *
  387. * @public
  388. * @memberOf mmir.PresentationManager.prototype
  389. */
  390. showDialog : function(ctrlName, dialogId, data) {
  391. _currentDialog = _renderEngine.showDialog.apply(this,arguments);
  392. return _currentDialog;
  393. },
  394. /**
  395. * Shows a "wait" dialog, i.e. "work in progress" notification.
  396. *
  397. * @function
  398. *
  399. * @param {String} [text] OPTIONAL
  400. * the text that should be displayed.
  401. * If omitted the language setting for <code>loadingText</code>
  402. * will be used instead (from dictionary.json)
  403. * @param {Object} [data] OPTIONAL
  404. * a data / options object
  405. *
  406. * @public
  407. * @memberOf mmir.PresentationManager.prototype
  408. *
  409. * @see mmir.PresentationManager#hideWaitDialog
  410. */
  411. showWaitDialog : function(text, data) {
  412. _renderEngine.showWaitDialog.apply(this,arguments);
  413. },
  414. /**
  415. * Hides / closes the "wait" dialog.
  416. *
  417. * @function
  418. * @public
  419. * @memberOf mmir.PresentationManager.prototype
  420. *
  421. * @see mmir.PresentationManager#showWaitDialog
  422. */
  423. hideWaitDialog : function() {
  424. _renderEngine.hideWaitDialog.apply(this,arguments);
  425. },
  426. /**
  427. * Gets the view for a controller, then executes helper methods on
  428. * the view data. The Rendering of the view is done by the
  429. * {@link #doRenderView} method. Also
  430. * stores the previous and current view with parameters.<br>
  431. *
  432. * @function
  433. * @param {String}
  434. * ctrlName Name of the controller
  435. * @param {String}
  436. * viewName Name of the view to render
  437. * @param {Object}
  438. * [data] optional data for the view.
  439. * @returns {void|Promise}
  440. * if void/undefined is returned, the view is rendered synchronously, i.e.
  441. * the view is rendered, when this method returns.
  442. * If a Promise is returned, the view is rendered asynchronously
  443. * (rendering is finished, when the promise is resolved)
  444. *
  445. * @public
  446. * @memberOf mmir.PresentationManager.prototype
  447. */
  448. render : function(ctrlName, viewName, data) {
  449. var ctrl = controllerManager.get(ctrlName);
  450. var renderResult;
  451. if (ctrl != null) {
  452. var view = this.getView(ctrlName, viewName);
  453. if(!view){
  454. logger.error('PresentationManager.renderView: could not find view "'+viewName+'" for controller "'+ctrlName+'"');
  455. return;
  456. }
  457. renderResult = _renderEngine.render.call(this, ctrlName, viewName, view, ctrl, data);
  458. }
  459. else {
  460. logger.error('PresentationManager.renderView: could not retrieve controller "'+ctrlName+'"');
  461. }
  462. return renderResult;
  463. },
  464. /**
  465. * Helper for emitting pre-/post-render events on controller instance and/or
  466. * PresentationManager instance.
  467. *
  468. * @function
  469. *
  470. * @param {mmir.ctrl.Controller}
  471. * ctrl the controller instance for which the render event should be emitted
  472. * @param {String}
  473. * eventName Name of the event: "page_load", ...
  474. * @param {Object}
  475. * [eventData] OPTIONAL the rendering data
  476. * @param {Object}
  477. * [pageData] OPTIONAL the page data/rendering options (may not be present for all events)
  478. * @param {Object}
  479. * [data] optional data for the view.
  480. * @returns {any | false}
  481. * if false returned by an event handler, cancalebale rendering actions will stopped
  482. */
  483. _fireRenderEvent: function(ctrl, eventName, eventData, pageData){
  484. var isContinue = ctrl.performIfPresent(eventName, eventData, pageData);
  485. //if "global" event handler is set:
  486. var evtHandlerName = reHandlerName.test(eventName)? eventName : 'on_' + eventName;
  487. if(typeof this[evtHandlerName] === 'function'){
  488. //if global event handler cancels rendering (note: may not be possible for all type of events)
  489. if(this[evtHandlerName](ctrl.getName(), eventName, eventData, pageData) === false){
  490. isContinue = false;
  491. }
  492. }
  493. return isContinue;
  494. },
  495. /**
  496. * @function
  497. * @async
  498. * @memberOf mmir.PresentationManager.prototype
  499. * @returns {Promise}
  500. * a deferred promise that gets fulfilled when initialization is completed.
  501. */
  502. init: function(){
  503. var defer = deferred();
  504. var isViewEngineLoaded = false;//MOD modularize view-engine
  505. var isViewsLoaded = false;//MOD modularize view-loading & -compiling
  506. var checkResolved = function(){
  507. if(isViewEngineLoaded && isViewsLoaded){
  508. defer.resolve(_instance);
  509. }
  510. };
  511. var failPromise = function(msg){
  512. defer.reject(msg);
  513. };
  514. //MOD modularize view-engine: load viewEngine (default uses standard HTML document API)
  515. require([typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD? 'mmirf/simpleViewEngine' : core.viewEngine], function(viewEngineInit){//FIXME
  516. viewEngineInit.then(
  517. function(viewEngine){
  518. _instance.setRenderEngine(viewEngine);
  519. isViewEngineLoaded = true;
  520. checkResolved();
  521. }, failPromise
  522. );
  523. });
  524. viewLoader(
  525. _instance, _layouts, _views, _partials, createViewKey, createPartialKey
  526. ).then(function(){
  527. isViewsLoaded = true;
  528. checkResolved();
  529. }, failPromise);
  530. return defer;
  531. },// init,
  532. /**
  533. * Sets the <em>rendering engine</em> for the views.
  534. *
  535. * The render engine <b>must</b> implement a function <em>render</em>
  536. * and <i>may</i> implement functions <em>showDialog</em>,
  537. * <em>hideCurrentDialog</em>, <em>showWaitDialog</em>, and <em>hideWaitDialog</em>:
  538. *
  539. * <ul>
  540. * <li><b>theRenderEngine.<code>render(ctrlName : String, viewName : String, view : View, ctrl : Controller, data : Object) : void|Promise</code></b></li>
  541. * <li>theRenderEngine.<code>showDialog(ctrlName : String, dialogId : String, data : Object) : Dialog</code></li>
  542. * <li>theRenderEngine.<code>hideCurrentDialog(): void</code></li>
  543. * <li>theRenderEngine.<code>showWaitDialog(text : String, data : Object): void</code></li>
  544. * <li>theRenderEngine.<code>hideWaitDialog(): void</code></li>
  545. * </ul>
  546. *
  547. * The functions of <code>theRenderEngine</code> will be called in
  548. * context of the PresentationManager.
  549. *
  550. * Custom functions of the specific rendering engine implementation
  551. * (i.e. non-standard functions) can be call via {@link #callRenderEngine}.
  552. *
  553. *
  554. * <br>
  555. * By default, the rendering-engine as defined by the module ID/path in
  556. * <code>core.viewEngine</code> will be loaded and set during initialization
  557. * of the DialogManager.
  558. *
  559. * <br>
  560. * The implementation of the default view-engine is at
  561. * <code>mmirf/env/view/presentation/simpleViewEngine.js</code>.
  562. *
  563. * @param {Object} theRenderEngine
  564. * the render-engine for views
  565. *
  566. * @function
  567. * @public
  568. * @memberOf mmir.PresentationManager.prototype
  569. *
  570. * @see mmir.PresentationManager#renderView
  571. * @see mmir.PresentationManager#showDialog
  572. * @see mmir.PresentationManager#hideCurrentDialog
  573. * @see mmir.PresentationManager#showWaitDialog
  574. * @see mmir.PresentationManager#hideWaitDialog
  575. *
  576. * @see mmir.PresentationManager#callRenderEngine
  577. *
  578. */
  579. setRenderEngine: function(theRenderEngine){
  580. _renderEngine.render = theRenderEngine.render;
  581. _renderEngine.showDialog = theRenderEngine.showDialog;
  582. _renderEngine.hideCurrentDialog = theRenderEngine.hideCurrentDialog;
  583. _renderEngine.showWaitDialog = theRenderEngine.showWaitDialog;
  584. _renderEngine.hideWaitDialog = theRenderEngine.hideWaitDialog;
  585. _renderEngine._engine = theRenderEngine;
  586. },
  587. /**
  588. * This function allows to call custom functions of the rendering-engine
  589. * that was set via {@link #setRenderEngine}.
  590. *
  591. * IMPORTANT:
  592. * note that the function will be invoked in context of rendering-engine
  593. * (i.e. <code>this</code> references will refer to rendering-engine
  594. * and not to the PresentationManager instance.
  595. * For example, when <code>mmir.PresentationManager.callRenderEngine('hideWaitDialog')</code>
  596. * is called, any <code>this</code> references within the <code>hideWaitDialog</code>
  597. * implementation would refer to object, that was set in <code>setRenderEngine(object)</code>.
  598. * In comparison, when called as <code>mmir.PresentationManager.hideWaitDialog()</code> the
  599. * <code>this</code> references refer to the mmir.PresentationManager instance.
  600. *
  601. * <br>
  602. * NOTE that calling non-existing functions on the rendering-engine
  603. * will cause an error.
  604. *
  605. * @param {String} funcName
  606. * the name of the function, that should be invoked on the rendering
  607. * engine.
  608. * @param {Array<any>} [args] OPTIONAL
  609. * the arguments for <code>funcName</code> invoked
  610. * via <code>Function.apply(renderingEngine, args)</code>, e.g.
  611. * for <code>args = [param1, param2, param3]</code> the
  612. * function will be called with
  613. * <code>funcName(param1, param2, param3)</code>
  614. * (note that the function receives 3 arguments, and
  615. * not 1 Array-argument).
  616. *
  617. * @function
  618. * @public
  619. * @memberOf mmir.PresentationManager.prototype
  620. */
  621. callRenderEngine: function(funcName, args){
  622. return _renderEngine._engine[funcName].apply(_renderEngine._engine, args);
  623. },
  624. //exported properties / constants:
  625. /**
  626. * @public
  627. * @type Integer
  628. * @constant
  629. * @memberOf mmir.PresentationManager.prototype
  630. */
  631. pageIndex: _pageIndex
  632. };//END: _instance = {...
  633. //export constants:
  634. _instance.DEFAULT_LAYOUT_NAME = DEFAULT_LAYOUT_NAME;
  635. _instance.CONFIG_DEFAULT_LAYOUT_NAME = CONFIG_DEFAULT_LAYOUT_NAME;
  636. return _instance;
  637. });
  638. //});