Source: env/view/viewLoader.js

  1. define([
  2. 'mmirf/layout', 'mmirf/view', 'mmirf/partial'
  3. , 'mmirf/util/deferredWithState', 'mmirf/util/loadFile', 'mmirf/util/forEach'
  4. , 'mmirf/configurationManager', 'mmirf/checksumUtils', 'mmirf/controllerManager', 'mmirf/resources', 'mmirf/core', 'mmirf/commonUtils'
  5. , 'mmirf/logger', 'module'
  6. , 'mmirf/parserModule'//<- loaded, but not directly used
  7. //,'mmirf/renderUtils' DISABLED: loaded on-demand (see loadViews())
  8. ],
  9. /**
  10. * @class
  11. * @name ViewLoader
  12. * @memberOf mmir.env.view
  13. * @hideconstructor
  14. */
  15. function(
  16. Layout, View, Partial,
  17. deferred, loadFile, forEach,
  18. configurationManager, checksumUtils, controllerManager, resources, core, commonUtils,
  19. Logger, module
  20. //renderUtils
  21. ){
  22. /**
  23. * Loads views (layouts and partials):
  24. * either compiled views (i.e. JS files), or raw views (i.e. eHTML files).
  25. *
  26. * The loader checks, if compiled views are up-to-date (using the corresponding
  27. * checksum file).
  28. * If a compiled view is not up-to-date (or its checksum file is missing), then
  29. * the raw view will be loaded and compiled.
  30. *
  31. * If raw views are loaded, the parsing-module will loaded for compiling the
  32. * views (i.e. if only compiled views are loaded, the dependency for the template
  33. * parser and renderer is not required).
  34. *
  35. *
  36. * <br>
  37. * <strong>Configuration (configuration.json)</strong>
  38. * <br>
  39. *
  40. * The configuration value <code>"usePrecompiledViews"</code> (Boolean) allows
  41. * the determine, if views should always be compiled from the eHTML files (even
  42. * if up-to-date compiled views are present).
  43. *
  44. * For example, configuration <code>"usePrecompiledViews": true</code> will use
  45. * compiled views, while <code>"usePrecompiledViews": false</code> will always
  46. * compile the eHTML files.
  47. *
  48. *
  49. * If the configuration value for {@link mmir.PresentationManager.CONFIG_DEFAULT_LAYOUT_NAME} is
  50. * set to <code>NULL</code> no default layout will be loaded.
  51. *
  52. * If {@link mmir.PresentationManager.CONFIG_DEFAULT_LAYOUT_NAME} is a non-empty string, then
  53. * the corresponding layout will be used as default layout, instead of
  54. * {@link mmir.PresentationManager.DEFAULT_LAYOUT_NAME}.
  55. *
  56. *
  57. * @param {PresentationManager} _instance
  58. * the instance of the PresentationManager
  59. * @param {Map<string, Layout>} _layouts
  60. * the layout collection of the PresentationManager for adding loaded layouts
  61. * @param {Map<string, View>} _views
  62. * the view collection of the PresentationManager for adding loaded views
  63. * @param {Map<string, Partial>} _partials
  64. * the partials collection of the PresentationManager for adding loaded partials
  65. * @param {Function} createViewKey
  66. * the PresentationManager's helper function for creating keys to be used when
  67. * adding views to <code>_views</code>:<br>
  68. * <code>createViewKey(ctrl: {mmir.ctrl.Controller|String}, view: {mmir.view.View|String}) : {String}</code>
  69. * @param {Function} createPartialKey
  70. * the PresentationManager's helper function for creating keys to be used when
  71. * adding partials to <code>_partials</code>:<br>
  72. * <code>createPartialKey(partial: {mmir.view.Partial|String}, view: {mmir.view.View|String}) : {String}</code>
  73. * @return {Promise}
  74. * a deferred promise that gets resolved when the views (layouts, and partials) are loaded
  75. *
  76. * @memberOf mmir.env.view.ViewLoader
  77. */
  78. function loadViews (
  79. _instance, _layouts, _views, _partials, createViewKey, createPartialKey
  80. ) {
  81. /**
  82. * The name of the configuration field that holds
  83. * the name for the default layout.
  84. *
  85. * @private
  86. * @type String
  87. * @constant
  88. * @memberOf mmir.env.view.ViewLoader#
  89. */
  90. var CONFIG_DEFAULT_LAYOUT_NAME = _instance.CONFIG_DEFAULT_LAYOUT_NAME;
  91. /**
  92. * Name for the default layout, that will be loaded.
  93. *
  94. * If NULL, no default layout will be loaded
  95. * (see below configurationManager.get(CONFIG_DEFAULT_LAYOUT_NAME...))
  96. *
  97. * @private
  98. * @type String
  99. * @memberOf mmir.env.view.ViewLoader#
  100. */
  101. var defaultLayoutName = _instance.DEFAULT_LAYOUT_NAME;
  102. /**
  103. * The logger for the PresentationManager.
  104. *
  105. * @private
  106. * @type mmir.tools.Logger
  107. * @memberOf mmir.env.view.ViewLoader#
  108. */
  109. var logger = Logger.create(module);//initialize with requirejs-module information
  110. /**
  111. * Name of the configuration property that specifies whether or not to use
  112. * pre-compiled views, i.e. whether to use generated JavaScript files
  113. * instead of parsing & compiling the "raw" templates (eHTML files).
  114. *
  115. * <p>
  116. * NOTE: the configuration value, that can be retrieved by querying this configuration-property
  117. * has is either a Boolean, or a String representation of a Boolean value:
  118. * <code>[true|false|"true"|"false"]</code>
  119. * <br>
  120. * NOTE2: There may be no value set at all in the configuration for this property.
  121. * In this case you should assume that it was set to <code>false</code>.
  122. *
  123. * @type String
  124. * @private
  125. * @constant
  126. * @memberOf mmir.env.view.ViewLoader#
  127. *
  128. * @example var isUsePrecompiledViews = mmir.conf.getBoolean("usePrecompiledViews");
  129. *
  130. */
  131. var CONFIG_PRECOMPILED_VIEWS_MODE = 'usePrecompiledViews';//TODO move this to somewhere else (collected config-vars?)? this should be a public CONSTANT...
  132. // determine if default-layout has a custom name (or is disabled, in it was set to null)
  133. var defLayoutName = configurationManager.get(CONFIG_DEFAULT_LAYOUT_NAME, void(0));
  134. if(typeof defLayoutName !== 'undefined'){
  135. defaultLayoutName = defLayoutName;
  136. }
  137. /**
  138. * Checks if a pre-compiled view is up-to-date:
  139. * loads the view, if it is current.
  140. *
  141. * If the pre-compiled view is not current, or loading-errors
  142. * occur, the fail-callback will be triggered
  143. * (the callback argument may contain information about the cause).
  144. *
  145. * @async
  146. * @private
  147. * @memberOf mmir.env.view.ViewLoader#
  148. *
  149. * @param {String} rawViewData
  150. * the text content of the view template (i.e. content of a eHTML file "as is")
  151. * @param {String} targetPath
  152. * the path to the pre-compiled view file
  153. * @param {Function} success
  154. * callback that will be triggered, if pre-compiled view file was loaded
  155. * NOTE: the JS code of the loaded file may not have been fully executed yet!
  156. * @param {Function} fail
  157. * callback that will be triggered, if pre-compiled view is not up-to-date or
  158. * an error occurs while loading the file
  159. */
  160. function loadPrecompiledView(rawViewData, targetpath, success, fail){
  161. //NOTE: stored template require the renderUtils:
  162. core.require(['mmirf/renderUtils'], function(){
  163. if(rawViewData && !isUpToDate(rawViewData, targetpath)){
  164. if(fail) fail('Precompiled view file is outdated!');
  165. else logger.warn('Outdated pre-compiled view at: '+targetpath);
  166. //-> do not load the pre-compiled view, instead let fail-callback handle re-parsing for the view
  167. return;/////////////////////// EARLY EXIT /////////////////////
  168. }
  169. commonUtils.loadScript( //scriptUrl, success, fail)
  170. targetpath, success, fail
  171. );
  172. });
  173. }
  174. /**
  175. * Flag for determining if pre-compiled views (*.js) should be used
  176. *
  177. * Reads property {@link #CONFIG_PRECOMPILED_VIEWS_MODE}. If the property is not set,
  178. * <code>false</code> is used by default, i.e. no pre-compiled views are used.
  179. *
  180. * @protected
  181. * @default
  182. * @type Boolean
  183. * @default false: use templates files (*.ehtml) and compile them (freshly) on-the-fly
  184. * @memberOf mmir.env.view.ViewLoader#
  185. */
  186. var isUsePreCompiledViews = configurationManager.getBoolean(CONFIG_PRECOMPILED_VIEWS_MODE, false);
  187. /**
  188. * Read the checksum file that was created when the pre-compiled view was created:
  189. *
  190. * it contains the view's template size (the length of its String representation) and MD5 hash.
  191. *
  192. * -> by calculating the viewContent's size and MD5 hash, we can determine, if it has changed
  193. * by comparing it with the data of the checksum file.
  194. *
  195. * @sync the checksum file is loaded in synchronous mode
  196. * @private
  197. * @memberOf mmir.env.view.ViewLoader#
  198. *
  199. * @param {String} viewContent
  200. * the content of the view template (i.e. loaded eHTML file)
  201. * @param {String} preCompiledViewPath
  202. * the path to the corresponding pre-compiled view file
  203. *
  204. */
  205. function isUpToDate(viewContent, preCompiledViewPath){
  206. //there is no pre-compiled view -> need to compile ehtml
  207. if(!preCompiledViewPath){
  208. return false;///////////////////// EARLY EXIT ////////////////////////
  209. }
  210. //replace file extension with the checksum-file's one: '.js' -> '.checksum.txt'
  211. var viewVerificationInfoPath =
  212. preCompiledViewPath.substring(0, preCompiledViewPath.length - 3)
  213. + checksumUtils.getFileExt();
  214. var isCompiledViewUpToDate = false;
  215. loadFile({
  216. 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)
  217. dataType: "text",
  218. url: viewVerificationInfoPath,
  219. success: function onSuccess(data){
  220. //compare raw String to checksum-data from file
  221. isCompiledViewUpToDate = checksumUtils.isSame(viewContent, data);
  222. },
  223. error: function onError(_jqxhr, status, err){
  224. // print out an error message
  225. var errMsg = err && err.stack? err.stack : err;
  226. logger.error("[" + status + "] On checking up-to-date, could not load '" + viewVerificationInfoPath + "': "+errMsg); //failure
  227. }
  228. });
  229. return isCompiledViewUpToDate;
  230. }
  231. /**
  232. * This function loads the layouts for every controller and puts the
  233. * name of the layouts into the <b>_layouts</b> array.
  234. *
  235. * @function
  236. * @private
  237. * @memberOf mmir.env.view.ViewLoader#
  238. *
  239. * @returns {Promise} a deferred promise that gets resolved upon loading all layouts; fails/is rejected, if not at least 1 layout was loaded
  240. */
  241. function loadLayouts(){
  242. // Load application's layouts.
  243. /**
  244. * @type Promise
  245. * @private
  246. * @memberOf mmir.env.view.ViewLoader#loadLayouts
  247. */
  248. var defer = deferred();
  249. /**
  250. * @type String
  251. * @private
  252. * @memberOf mmir.env.view.ViewLoader#loadLayouts
  253. */
  254. var ctrlNameList = controllerManager.getNames();
  255. /**
  256. * HELPER object for tracking the loading-status of the layouts
  257. *
  258. * @private
  259. * @memberOf mmir.env.view.ViewLoader#loadLayouts
  260. */
  261. var loadStatus = {
  262. loader: defer,
  263. remainingCtrlCount: ctrlNameList.length + 1,//+1: for the default layout
  264. currentLoadCount: 0,
  265. //additional property for keeping track on how many layouts were load overall
  266. // NOTE: this additional counter is necessary, since currentLoadCount
  267. // keeps only track of how many controller's were checked. But since
  268. // a controller may not have a layout-definition of its own, we have
  269. // to use another counter to keep track of the actually loaded layouts.
  270. loadedLayoutsCount: 0,
  271. //need a custom function for checking the load status: if no layout was loaded,
  272. // the Derred will be rejected
  273. onCompletionImpl: function(status){
  274. //if there is a default-layout specified, but no layout was loaded -> fail
  275. if(status.loadedLayoutsCount < 1 && defaultLayoutName){
  276. //there must be at least on layout-file for the default-controller:
  277. status.loader.reject( 'Could not load any layout! At least one layout must be present at '
  278. + resources.getLayoutPath()
  279. + defaultLayoutName[0].toLowerCase() + defaultLayoutName.substring(1)
  280. + '.ehtml'
  281. );
  282. }
  283. else {
  284. status.loader.resolve();
  285. }
  286. },
  287. //extend the status-update function: in case loading succeeded, increase the counter
  288. // for overall loaded layouts.
  289. extLoadStatusFunc: function(status, hasLoadingFailed){
  290. if(hasLoadingFailed === true){
  291. //do nothing
  292. }
  293. else {
  294. ++status.loadedLayoutsCount;
  295. }
  296. }
  297. };
  298. /**
  299. * HELPER object for loading/creating the layouts
  300. * @private
  301. * @memberOf mmir.env.view.ViewLoader#loadLayouts
  302. */
  303. var createLayoutConfig = {
  304. constructor: Layout,
  305. typeName: 'Layout',
  306. collection: _layouts
  307. };
  308. /**
  309. * helper for loading a single layout-file
  310. *
  311. * @private
  312. * @memberOf mmir.env.view.ViewLoader#loadLayouts
  313. */
  314. var doLoadLayout = function(ctrlName, _index, theDefaultLayoutName){
  315. var layoutInfo;
  316. if(typeof theDefaultLayoutName === 'string'){
  317. ctrlName = theDefaultLayoutName;
  318. //create info-object for default-layout
  319. var layoutFileName = theDefaultLayoutName[0].toLowerCase()
  320. + theDefaultLayoutName.substring(1, theDefaultLayoutName.length);
  321. layoutInfo = {
  322. name: theDefaultLayoutName,
  323. fileName: layoutFileName,
  324. genPath: resources.getCompiledLayoutPath()//TODO add compiled-path to view-info object (and read it from file-structure/JSON)
  325. + layoutFileName + '.js',
  326. path: resources.getLayoutPath() + layoutFileName + '.ehtml'
  327. };
  328. }
  329. else {
  330. var ctrl = controllerManager.get( ctrlName );
  331. ctrlName = ctrl.getName();
  332. layoutInfo = ctrl.getLayout();
  333. }
  334. if(layoutInfo){
  335. doLoadTemplateFile(null, layoutInfo, createLayoutConfig, loadStatus);
  336. }
  337. --loadStatus.remainingCtrlCount;
  338. checkCompletion(loadStatus);
  339. };//END: doLoadLayout(){...
  340. //load the default layout:
  341. if(defaultLayoutName){
  342. doLoadLayout(null, null, defaultLayoutName);
  343. } else {
  344. logger.info('The name for the default Layout was set to "'+defaultLayoutName+'", no default Layout will be loaded!');
  345. --loadStatus.remainingCtrlCount;
  346. checkCompletion(loadStatus);
  347. }
  348. //load layouts for controllers (there may be none defined)
  349. forEach(ctrlNameList, doLoadLayout);
  350. checkCompletion(loadStatus);
  351. return defer;
  352. }//END: loadLayouts()
  353. /**
  354. * This function actually loads the views for every controller, creates
  355. * an instance of a view class and puts the view instance in the
  356. * <b>_views</b> array.<br>
  357. *
  358. * @function
  359. * @private
  360. * @async
  361. * @memberOf mmir.env.view.ViewLoader#
  362. *
  363. * @returns {Promise} a deferred promise that gets resolved upon loading all views
  364. *
  365. * @see doProcessTemplateList
  366. */
  367. function loadViews() {
  368. var creatorConfig = {
  369. constructor: View,
  370. typeName: 'View',
  371. collection: _views,
  372. keyGen: createViewKey,
  373. accessorName: 'getViews'
  374. };
  375. return doProcessTemplateList(creatorConfig);
  376. }//END: loadViews()
  377. /**
  378. * This function actually loads the partials for every controller,
  379. * creates an instance of a partial class and puts the partial instance
  380. * in the <b>_partials</b> array.<br>
  381. * It uses a asynchronous way of loading the partials-files one after
  382. * another.<br>
  383. * <b>If you want to make sure, that all partials are indeed loaded,
  384. * before proceeding with the subsequent instructions, you could look at
  385. * the function
  386. * {@link mmir.ControllerManager#foundControllersCallBack} for
  387. * reference of a function which loads the files one after another - not
  388. * asynchronously.</b>
  389. *
  390. * @function
  391. * @private
  392. * @async
  393. * @memberOf mmir.env.view.ViewLoader#
  394. *
  395. * @returns {Promise} a deferred promise, that resolves after all partials have been loaded
  396. * NOTE: loading failures will generate a warning message (on the console)
  397. * but will not cause the Promise to fail.
  398. */
  399. function loadPartials() {
  400. var creatorConfig = {
  401. constructor: Partial,
  402. typeName: 'Partial',
  403. collection: _partials,
  404. keyGen: createPartialKey,
  405. accessorName: 'getPartials'
  406. };
  407. return doProcessTemplateList(creatorConfig);
  408. }//END: loadPartials()
  409. /**
  410. * HELPER for checking the loading status.
  411. *
  412. * As long as the Deferred <code>status.loader</code> is
  413. * still pending, the loading status will be checked:
  414. *
  415. * Depending on <code>status.currentLoadCount</code> and
  416. * <code>status.remainingCtrlCount</code> the completion
  417. * of the loading process is checked.
  418. *
  419. * If loading is completed, the Deferred <code>status.loader</code>
  420. * will be resolved.
  421. *
  422. * If OPTIONAL <code>status.loader</code> (Function) exists, intead of resolving
  423. * <code>status.loader</code>, this function is invoked in case of completion
  424. * with <code>status</code> as argument.
  425. *
  426. * @private
  427. * @function
  428. * @memberOf mmir.env.view.ViewLoader#
  429. *
  430. * @param {PlainObject} status
  431. * the object for managing the laoding status.
  432. *
  433. */
  434. var checkCompletion = function(status){
  435. if(status.loader.state() === 'pending' && status.remainingCtrlCount === 0 && status.currentLoadCount === 0){
  436. if(status.onCompletionImpl){
  437. status.onCompletionImpl(status);
  438. }
  439. else {
  440. status.loader.resolve();
  441. }
  442. }
  443. };
  444. /**
  445. * HELPER for updating the loading status.
  446. *
  447. * Invokes {@link checkCompletion} with <code>status</code> as argument.
  448. *
  449. * @private
  450. * @function
  451. * @memberOf mmir.env.view.ViewLoader#
  452. *
  453. * @param {PlainObject} status
  454. * the object for managing the laoding status:
  455. * <code>status.currentLoadCount</code> (Integer): this property will be decreased by 1.
  456. * This value should initially be set to the count
  457. * of files, that will / should be loaded.
  458. * OPTIONAL <code>status.extLoadStatusFunc</code> (Function): if this property is set, the
  459. * function will be invoked with <code>status</code>
  460. * and <code>hasLoadingFailed</code> as arguments.
  461. *
  462. * @param {Boolean} [hasLoadingFailed] OPTIONAL
  463. * if present and <code>true</code>: this indicates that the loading process for the current
  464. * template file (*.ehtml) has failed. NOTE that this is NOT used, when loading of a
  465. * _compiled_ template file (*.js) fails!
  466. */
  467. var updateLoadStatus = function(status, hasLoadingFailed){
  468. --status.currentLoadCount;
  469. if(status.extLoadStatusFunc){
  470. status.extLoadStatusFunc(status, hasLoadingFailed);
  471. }
  472. checkCompletion(status);
  473. };
  474. /**
  475. * HELPER: creates a template-object (e.g. a View or a Partial) for the
  476. * raw template conent.
  477. *
  478. * If necessary, the parser-classes (module 'mmirf/parseUtils') are loaded,
  479. * which are necessary to process the raw template content.
  480. *
  481. * @private
  482. * @function
  483. * @memberOf mmir.env.view.ViewLoader#
  484. *
  485. * @param {mmir.ctrl.Controller} controller
  486. * the controller to which the template files belong.
  487. * May be <code>null</code>: in this case, this argument will be omitted when
  488. * creating the template object and creating the lookup-key (e.g. in case of a Layout).
  489. *
  490. * @param {String} templateName
  491. * the name for the template (e.g. file-name without extension)
  492. *
  493. * @param {PlainObject} createConfig
  494. * configuration that is used to create the template-object
  495. * for the template-contents:
  496. * <code>createConfig.constructor</code>: the constructor function
  497. * IF controller IS NOT null: <code>(controller, templateName, templateContent)</code>
  498. * (e.g. View, Partial)
  499. * IF controller IS null: <code>(templateName, templateContent)</code>
  500. * (e.g. Layout)
  501. *
  502. * <code>createConfig.collection</code>: the Map to which the created
  503. * template-object will be added
  504. * <code>createConfig.keyGen</code>: a generator function for creating the lookup-key when adding
  505. * the template-object to the collection.
  506. * This function is invoked with <code>(controller.getName(), templateName)</code>,
  507. * that is <code>(String, String)</code>.
  508. *
  509. * NOTE: if controller IS null, the keyGen function will not be used, and
  510. * instead the template-object will be added with the
  511. * created template-object's name as lookup-key.
  512. * @param {PlainObject} status
  513. * the object for managing the loading status.
  514. * After creating and adding the template-object to the collection, the loading
  515. * status will be updated via {@link updateLoadStatus}
  516. *
  517. */
  518. var doParseTemplate = function(controller, templateName, config, templateContent, status){
  519. //NOTE need to request renderUtils here too, since it is needed during parsing!
  520. core.require(['mmirf/parseUtils', 'mmirf/renderUtils'], function(){
  521. var templateObj;
  522. if(controller){
  523. //"normal" view constructor: (Controller, nameAsString, templateAsString)
  524. templateObj = new config.constructor(controller, templateName , templateContent);
  525. config.collection.set( config.keyGen(controller.getName(), templateName), templateObj );
  526. }
  527. else {
  528. //in case of Layout: omit controller argument
  529. // -> layout constructor: (nameAsString, templateAsString)
  530. // -> there is a 1:1 correspondence betwenn controller and layout,
  531. // and Layout.name === Controller.name
  532. // => no need to create a lookup-key
  533. templateObj = new config.constructor(templateName , templateContent);
  534. config.collection.set( templateObj.getName(), templateObj );
  535. }
  536. updateLoadStatus(status);
  537. });
  538. };
  539. /**
  540. * Generic helper for loading a list of template files (*.ehtml)
  541. * that correspond to a specific template type (e.g. <code>View</code>s, or <code>Partial</code>s).
  542. *
  543. * If compiled representations of the template file exist AND is up-to-date AND
  544. * the configuration is set to load the compiled files rather than the raw template
  545. * file (see <code>isUsePreCompiledViews</code>), then the compiled template is used.
  546. *
  547. *
  548. * This function uses an asynchronous method for loading the template-files.<br>
  549. *
  550. * <b>If you want to make sure, that all templates have indeed been loaded, before
  551. * proceeding with the subsequent program flow, you should have a look at the
  552. * function {@link mmir.ControllerManager#foundControllersCallBack}: use the returned
  553. * Deferred.Promise for executing code that depends on the templates being loaded.</b>
  554. *
  555. * <p>
  556. * Uses {@link doloadTemplateFile} for loading single template files.
  557. *
  558. * @function
  559. * @private
  560. * @async
  561. * @memberOf mmir.env.view.ViewLoader#
  562. *
  563. * @see #doLoadTemplateFile
  564. *
  565. * @param {PlainObject} createConfig
  566. * configuration object that determines which templates are loaded, and how
  567. * the loaded data is processed.
  568. *
  569. *
  570. * @returns {Promise} a deferred promise, that resolves after all partials have been loaded
  571. * NOTE: loading failures will generate a warning message (on the console)
  572. * but will not cause the Promise to fail.
  573. */
  574. var doProcessTemplateList = function(createConfig){
  575. /**
  576. * @type Promise
  577. * @private
  578. * @memberOf mmir.env.view.ViewLoader#doProcessTemplateList
  579. */
  580. var defer = deferred();
  581. /**
  582. * @type String
  583. * @private
  584. * @memberOf mmir.env.view.ViewLoader#doProcessTemplateList
  585. */
  586. var ctrlNameList = controllerManager.getNames();
  587. /**
  588. * HELPER object for tracking the loading-status of the views
  589. *
  590. * @private
  591. * @memberOf mmir.env.view.ViewLoader#doProcessTemplateList
  592. */
  593. var loadStatus = {
  594. loader: defer,
  595. remainingCtrlCount: ctrlNameList.length,
  596. currentLoadCount: 0
  597. };
  598. forEach(ctrlNameList, function(controllerName){
  599. var controller = controllerManager.get(controllerName);
  600. forEach(controller[createConfig.accessorName](), function(templateInfo){
  601. doLoadTemplateFile(controller, templateInfo, createConfig, loadStatus);
  602. });//END: each(templateInfo)
  603. -- loadStatus.remainingCtrlCount;
  604. checkCompletion(loadStatus);
  605. });//END: each(ctrlName)
  606. checkCompletion(loadStatus);
  607. return defer;
  608. };//END: doProcessTemplateList()
  609. /**
  610. * HELPER that loads a single template file asynchronously and creates a corresponding template-class instance
  611. * (depending on <code>createConfig</code>).
  612. *
  613. * The <code>status</code> is updated on successful loading or on error (see {@link updateLoadStatus}).
  614. *
  615. * @example
  616. *
  617. * //EXAMPLE for createConfig for loading template contents into a Partial
  618. * var theCreateConfig = {
  619. * constructor: Partial, // the class constructor that takes the loaded template data
  620. * typeName: 'Partial', // the name of the class that will be created
  621. * collection: _partials, // the map/dictionary to which the created class-instance will be added
  622. * keyGen: createPartialKey, // the function for creating the lookup-key (for the dictionary)
  623. * accessorName: 'getPartials' // the accessor-function's name for accessing the info-objects on the controller-instance
  624. * };
  625. * doLoadTemplateFiles(theCreateConfig).then(function(){
  626. * //do something that depends on loading of the template files...
  627. * });
  628. *
  629. * //EXAMPLE for createConfig for loading template contents into a Layout
  630. * var theCreateLayoutConfig = {
  631. * constructor: Layout, // the class constructor that takes the loaded template data
  632. * typeName: 'Layout', // the name of the class that will be created
  633. * collection: _layouts, // the map/dictionary to which the created class-instance will be added
  634. * };
  635. *
  636. * doLoadTemplateFiles(theCreateLayoutConfig).then(function(){
  637. * //do something that depends on loading of the template files...
  638. * });
  639. *
  640. * //for createConfig for loading template contents into a Partial
  641. *
  642. * @param {mmir.ctrl.Controller} controller
  643. * the controller to which the template files belong.
  644. * May be <code>null</code>: see {@link doParseTemplate}.
  645. *
  646. * @param {PlainObject} templateInfo
  647. * the JSON-like object containing information for the template
  648. * (e.g. <code>name</code>, <code>file-path</code> etc.; see
  649. * {@link mmir.ControllerManager#getControllerResources} for
  650. * more information).
  651. *
  652. * @param {PlainObject} createConfig
  653. * configuration that is used to create a corresponding template-object
  654. * for the loaded template-contents.
  655. * The created object will be added to <code>createConfig.collection</code>
  656. * (Map; with controller's name as key).
  657. *
  658. * @param {PlainObject} loadStatus
  659. * Object for managing the loading-status. The status is updated and used to
  660. * determine, if all templates (e.g. from a list) have been (asynchronously)
  661. * loaded.
  662. *
  663. * @function
  664. * @private
  665. * @async
  666. * @memberOf mmir.env.view.ViewLoader#
  667. */
  668. var doLoadTemplateFile = function(controller, templateInfo, createConfig, loadStatus){
  669. ++loadStatus.currentLoadCount;
  670. if(!templateInfo.path){
  671. logger.warn('cannot check if pre-compiled view is updated: no template file available for '+templateInfo.name);
  672. loadPrecompiledView(null, templateInfo.genPath, function(){
  673. updateLoadStatus(loadStatus);
  674. }, function(err){
  675. logger.error('Could not load precompiled '+createConfig.typeName+' from '
  676. +templateInfo.genPath+'", because: '+err
  677. );
  678. updateLoadStatus(loadStatus);
  679. });
  680. return;///////////// EARLY EXIT //////////////////
  681. }
  682. loadFile({
  683. async: true,
  684. dataType: "text",
  685. url: templateInfo.path,
  686. success: function onSuccess(data){
  687. if(isUsePreCompiledViews){
  688. loadPrecompiledView(data, templateInfo.genPath, function(){
  689. updateLoadStatus(loadStatus);
  690. }, function(err){
  691. logger.warn('Could not load precompiled '+createConfig.typeName+' from '
  692. +templateInfo.genPath+'", because: '+err
  693. +', compiling template instead: '
  694. +templateInfo.path
  695. );
  696. doParseTemplate(controller, templateInfo.name, createConfig , data, loadStatus);
  697. });
  698. }
  699. else {
  700. doParseTemplate(controller, templateInfo.name, createConfig, data, loadStatus);
  701. }
  702. },
  703. error: function onError(_jqxhr, status, err){
  704. // print out an error message
  705. var errMsg = err && err.stack? err.stack : err;
  706. logger.error("[" + status + "] Could not load eHTML file '" + templateInfo.path + "': "+errMsg); //failure
  707. updateLoadStatus(loadStatus, true);
  708. }
  709. });
  710. checkCompletion(loadStatus);
  711. };//END: doLoadTemplateFile()
  712. ///////////// start intialization: ////////////////
  713. /**
  714. * Deferred / promise for loading views.
  715. *
  716. * @type Promise
  717. * @private
  718. * @memberOf mmir.env.view.ViewLoader#
  719. */
  720. var defer = deferred();
  721. var isLayoutsLoaded = false;
  722. var isViewsLoaded = false;
  723. var isPartialsLoaded = false;
  724. /**
  725. * Helper: called each time a loading-function finishes.
  726. * Checks if all other loading-functions have finished, and if so, resolves the init-promise.
  727. *
  728. * @private
  729. * @memberOf mmir.env.view.ViewLoader#
  730. */
  731. var checkResolved = function(){
  732. if(isLayoutsLoaded && isViewsLoaded && isPartialsLoaded){
  733. defer.resolve();
  734. }
  735. };
  736. /**
  737. * Helper: called if an error occured in one of the loading-functions:
  738. * rejects/fails the init-promise.
  739. *
  740. * @private
  741. * @memberOf mmir.env.view.ViewLoader#
  742. */
  743. var failPromise = function(msg){
  744. defer.reject(msg);
  745. };
  746. //util for checking if pre-compiled views are up-to-date
  747. // (i.e.: can we use the pre-compiled view, or do we need to use the template file and compile it on-the-fly)
  748. //TODO should this also be configurable -> up-to-date check (e.g. use pre-compiled views without checking for changes)
  749. checksumUtils = checksumUtils.init();
  750. loadLayouts().then(
  751. function(){ isLayoutsLoaded = true; checkResolved(); },
  752. failPromise
  753. );
  754. loadViews().then(
  755. function(){ isViewsLoaded = true; checkResolved(); },
  756. failPromise
  757. );
  758. loadPartials().then(
  759. function(){ isPartialsLoaded = true; checkResolved(); },
  760. failPromise
  761. );
  762. return defer;
  763. };//END: loadViews(){
  764. return loadViews;
  765. });