Source: mvc/parser/templateRenderUtils.js

  1. define (['mmirf/commonUtils','mmirf/languageManager','mmirf/controllerManager','mmirf/presentationManager','mmirf/parserModule','mmirf/viewConstants',
  2. 'mmirf/logger', 'module'
  3. ],
  4. /**
  5. * A Utility class for rendering parsed (eHTML) templates, or more specifically ParsingResult objects.<br>
  6. *
  7. * @example mmir.parser.RenderUtils.render(parseResult, contentElementList);
  8. *
  9. * @class RenderUtils
  10. * @name mmir.parser.RenderUtils
  11. * @export RenderUtils as mmir.parser.RenderUtils
  12. * @public
  13. * @static
  14. * @hideconstructor
  15. *
  16. */
  17. function (
  18. commonUtils, languageManager, controllerManager, presentationManager, parser, ViewConstants,
  19. Logger, module
  20. ) {
  21. /**
  22. * Object containing the instance of the class RenderUtils
  23. *
  24. * @type RenderUtils
  25. *
  26. * @private
  27. * @memberOf mmir.parser.RenderUtils#
  28. */
  29. var instance = null;
  30. /**
  31. * the logger for the RenderUtils
  32. *
  33. * @type mmir.tools.Logger
  34. *
  35. * @private
  36. * @memberOf mmir.parser.RenderUtils#
  37. */
  38. var logger = Logger.create(module);
  39. //internal "constants" for the RENDERING mode
  40. /**
  41. * @private
  42. * @memberOf mmir.parser.RenderUtils#
  43. */
  44. var RENDER_MODE_LAYOUT = 0;
  45. /**
  46. * @private
  47. * @memberOf mmir.parser.RenderUtils#
  48. */
  49. var RENDER_MODE_PARTIAL = 2;
  50. /**
  51. * @private
  52. * @memberOf mmir.parser.RenderUtils#
  53. */
  54. var RENDER_MODE_VIEW_CONTENT = 4;
  55. /**
  56. * @private
  57. * @memberOf mmir.parser.RenderUtils#
  58. */
  59. var RENDER_MODE_VIEW_DIALOGS = 8;
  60. /**
  61. * @private
  62. * @memberOf mmir.parser.RenderUtils#
  63. */
  64. var RENDER_MODE_JS_SOURCE = 16;
  65. /**
  66. * @private
  67. * @memberOf mmir.parser.RenderUtils#
  68. */
  69. var RENDER_MODE_JS_SOURCE_FORCE_VAR_PREFIX = 32;
  70. /**
  71. * @private
  72. * @memberOf mmir.parser.RenderUtils#
  73. */
  74. var DATA_NAME = parser.element.DATA_NAME;
  75. /**
  76. * @private
  77. * @memberOf mmir.parser.RenderUtils#
  78. */
  79. var PARAM_DATA_NAME = parser.element.DATA_ARGUMENT_NAME;
  80. /**
  81. * @private
  82. * @memberOf mmir.parser.RenderUtils#
  83. */
  84. var PARAM_ARGS_NAME = parser.element.ARGUMENT_ARGUMENT_NAME;
  85. /**
  86. * HELPER for detecting if an object is an Array
  87. *
  88. * @function
  89. *
  90. * @private
  91. * @memberOf mmir.parser.RenderUtils#
  92. *
  93. * @see mmir.CommonUtils#isArray
  94. */
  95. var isArray = commonUtils.isArray;
  96. /**
  97. * helper for sorting an Arrays.
  98. *
  99. * Notes:
  100. * 1. all array elements must have a function {Number} getStart()
  101. * 2. the array will be sorted ascending by getStart(), e.g. sort by occurrence in the raw template-text
  102. *
  103. * Usage example:
  104. * <code>
  105. * theArray.sort(sortAscByStart);
  106. * </code>
  107. *
  108. * @private
  109. * @memberOf mmir.parser.RenderUtils#
  110. */
  111. var sortAscByStart=function(parsedElem1, parsedElem2){
  112. return parsedElem1.getStart() - parsedElem2.getStart();
  113. };
  114. /**
  115. * Constructor-Method of Singleton mmir.parser.RenderUtils
  116. *
  117. * @private
  118. * @ignore
  119. *
  120. * @memberOf mmir.parser.RenderUtils#
  121. */
  122. function constructor(){
  123. //private members.
  124. /**
  125. * @type mmir.LanguageManager
  126. * @name localizer
  127. * @private
  128. * @memberOf mmir.parser.RenderUtils#
  129. */
  130. var localizer = languageManager;
  131. /**
  132. * Prepares the layout:
  133. *
  134. * after loading a layout file, this methods prepares the layout
  135. * for rendering content into it
  136. * (i.e. "prepare layout definition for later view-renderings").
  137. *
  138. *
  139. * NOTE: this does not actually render the layout for "viewing"
  140. * (see renderContent(..))!
  141. *
  142. * @param {ParsingResult} result the parsing result for the layout string
  143. * @param {Array<mmir.view.ContentElement>} contentForArray (usually this would be NULL for pre-rendering layouts)
  144. * @param {Number} renderingMode the rendering mode for layouts
  145. * @returns {String} the (pre-) rendered layout
  146. *
  147. *
  148. * @private
  149. * @memberOf mmir.parser.RenderUtils#
  150. * @name renderLayoutImpl
  151. */
  152. function renderLayout(result, contentForArray, renderingMode) {
  153. //TODO need to enable dynamic elements for this LAYOUT-rendering
  154. // (e.g. for 'calculating' variables that can be used as @yield-arguments ... should vars for this be disabled?)
  155. //create list of all template-expressions
  156. var all = result.scripts.concat(
  157. result.styles//[DISABLED: only process script- and style-tags at this stage], result.yields, result.localizations
  158. );
  159. //sort list by occurrence:
  160. all.sort(sortAscByStart);
  161. var renderResult = new Array();
  162. var pos = 1;
  163. for(var i=0, size = all.length; i < size; ++i){
  164. var scriptElem = all[i];
  165. //render the "static" content, beginning from the
  166. // lastly rendered "dynamic" element up to the start
  167. // of the current "dynamic" element:
  168. renderResult.push(result.rawTemplateText.substring(pos-1, scriptElem.getStart()));
  169. //render the current "dynamic" element:
  170. renderElement(scriptElem, contentForArray, renderingMode, result.rawTemplateText, renderResult);
  171. //set position-marker for "static" content after entry position
  172. // of current "dynamic" element:
  173. pos = scriptElem.getEnd() + 1;
  174. //alert('Replacing \n"'+rawTemplateText.substring(scriptElem.getStart(), scriptElem.getEnd())+'" with \n"'+content+'"');
  175. }
  176. if(pos - 1 < result.rawTemplateText.length){
  177. renderResult.push(result.rawTemplateText.substring(pos - 1));
  178. }
  179. return renderResult.join('');
  180. }
  181. /**
  182. * Prepares JavaScript source code for usage in rendering the template (view/partial etc.).
  183. *
  184. * The replacement-list contains information which parts of the raw JavaScript code should be
  185. * modified (e.g. indices [start,end] for replacing text in the source code).
  186. *
  187. * The function returns the modified JavaScript source code as a String.
  188. *
  189. *
  190. * If the mode is <code>RENDER_MODE_JS_SOURCE_FORCE_VAR_PREFIX</code>, the variable-names that correspond
  191. * to replacementObjectsList are check: if a name does not start with @, then the name will prepended with @ before
  192. * rendering.
  193. *
  194. * @private
  195. * @memberOf mmir.parser.RenderUtils#
  196. * @name renderJSSourceImpl
  197. */
  198. function renderJSSource(rawJSSourceCode, replacementObjectsList, renderingMode) {
  199. if(!replacementObjectsList || replacementObjectsList.length < 1){
  200. return rawJSSourceCode; //////////////////////////// EARLY EXIT //////////////////////////
  201. }
  202. var all = replacementObjectsList;
  203. //sort list by occurrence:
  204. all.sort(sortAscByStart);
  205. var renderResult = new Array();
  206. var pos = 1;
  207. for(var i=0, size = all.length; i < size; ++i){
  208. var scriptElem = all[i];
  209. //render the "static" content, beginning from the
  210. // lastly rendered "dynamic" element up to the start
  211. // of the current "dynamic" element:
  212. renderResult.push(rawJSSourceCode.substring(pos-1, scriptElem.getStart()));
  213. //render the current "dynamic" element:
  214. renderElement(scriptElem, null, renderingMode, rawJSSourceCode, renderResult);
  215. //set position-marker for "static" content after entry position
  216. // of current "dynamic" element:
  217. pos = scriptElem.getEnd() + 1;
  218. //alert('Replacing \n"'+rawTemplateText.substring(scriptElem.getStart(), scriptElem.getEnd())+'" with \n"'+content+'"');
  219. }
  220. if(pos - 1 < rawJSSourceCode.length){
  221. renderResult.push(rawJSSourceCode.substring(pos - 1));
  222. }
  223. return renderResult.join('');
  224. }
  225. /**
  226. * Render a View
  227. *
  228. * Renders the contents into a layout definition (i.e. "render for viewing").
  229. *
  230. * @param {String} htmlContentString the "raw" content string that was parsed
  231. * @param {Array<mmir.view.YieldDeclaration>} yieldDeclarationsArray a list of yield-declarations for the parsed htmlContentString
  232. * @param {Array<mmir.view.ContentElement>} contentForObjectsArray a list of content-for objects for the parsed htmlContentString. This list must supply a corresponding object for each entry in the <tt>yieldDeclarationsArray</tt>.
  233. * @param {Number} renderingMode the render mode
  234. * @param {Object} data the rendering data
  235. * @returns {String} the evaluated and rendered view-content
  236. *
  237. *
  238. * @private
  239. * @function
  240. * @memberOf mmir.parser.RenderUtils#
  241. * @name renderContentImpl
  242. */
  243. function renderContent(htmlContentString, yieldDeclarationsArray, contentForArray, renderingMode, data) {
  244. yieldDeclarationsArray.sort(sortAscByStart);
  245. var renderResult = new Array();
  246. var pos = 1;
  247. for(var i=0, size = yieldDeclarationsArray.length; i < size; ++i){
  248. var yieldDeclaration = yieldDeclarationsArray[i];
  249. if(
  250. (renderingMode === RENDER_MODE_VIEW_CONTENT && yieldDeclaration.getAreaType() !== ViewConstants.CONTENT_AREA_BODY)
  251. || (renderingMode === RENDER_MODE_VIEW_DIALOGS && yieldDeclaration.getAreaType() !== ViewConstants.CONTENT_AREA_DIALOGS)
  252. ){
  253. continue;
  254. }
  255. //render the "static" content, beginning from the
  256. // lastly rendered "dynamic" element up to the start
  257. // of the current "dynamic" element:
  258. renderResult.push(htmlContentString.substring(pos-1, yieldDeclaration.getStart()));
  259. //render the current "dynamic" element:
  260. renderYield(yieldDeclaration, contentForArray, renderingMode, htmlContentString, renderResult, data);
  261. //set position-marker for "static" content after entry position
  262. // of current "dynamic" element:
  263. pos = yieldDeclaration.getEnd() + 1;
  264. }
  265. if(pos - 1 < htmlContentString.length){
  266. renderResult.push(htmlContentString.substring(pos - 1));
  267. }
  268. return renderResult.join('');
  269. }
  270. /**
  271. * Renders a ContentElement object into the renderingBuffer.
  272. *
  273. *
  274. * @param {mmir.view.ContentElement} contentElement
  275. * the ContentElement object that should be rendered
  276. * @param {Array} renderingBuffer
  277. * of Strings (if <code>null</code> a new buffer will be created)
  278. * @param {Object} data
  279. * the data/arguments/variables object;
  280. * the event data with which the rendering was invoked is accessible via <DATA_NAME>[<PARAM_DATA_NAME>]
  281. *
  282. * @param {Array<mmir.view.ContentElement>} [contentForObjectsArray] OPTIONAL
  283. * for rendering layouts, i.e. when YieldDeclarations are contained in the contentElement.allContentElements fields:
  284. * a list of content-for objects for the parsed htmlContentString. This list must supply a corresponding object for each entry in the <tt>yieldDeclarationsArray</tt>.
  285. * @returns {Array}
  286. * a list of Strings the renderingBuffer where the contents of this object are added at the end of the Array
  287. *
  288. * @private
  289. * @memberOf mmir.parser.RenderUtils#
  290. * @name renderContentElementImpl
  291. */
  292. function renderContentElement(contentElement, renderingBuffer, data, contentForObjectsArray){
  293. //create "buffer" if necessary:
  294. var renderResult = getRenderingBuffer(renderingBuffer);
  295. //initialize the contentElement with the current rendering-data object:
  296. contentElement.setRenderData(data);
  297. contentForObjectsArray = contentForObjectsArray || null;
  298. var pos = 1;
  299. //iterate over elements, and render them into the "buffer":
  300. for(var i=0, size = contentElement.allContentElements.length; i < size; ++i){
  301. var childContentElement = contentElement.allContentElements[i];
  302. //render the "static" content, beginning from the
  303. // lastly rendered "dynamic" element up to the start
  304. // of the current "dynamic" element:
  305. renderResult.push(contentElement.definition.substring(pos-1, childContentElement.getStart()));
  306. if(!contentForObjectsArray && childContentElement.isYield()){
  307. logger.e('encountered mmir.view.YieldDeclaration, but now ContentFor list was supplied');
  308. } else {
  309. //render the current "dynamic" element:
  310. renderElement(
  311. childContentElement,
  312. contentForObjectsArray,//<- contentForArray: must be specified, if not used (only for LAYOUTS)
  313. RENDER_MODE_VIEW_CONTENT,//<- renderingMode: render as normal view (i.e. generate all replacements)
  314. contentElement.getRawText(),
  315. renderResult,
  316. data,
  317. contentElement
  318. );
  319. }
  320. //set position-marker for "static" content after entry position
  321. // of current "dynamic" element:
  322. pos = childContentElement.getEnd() + 1;
  323. //alert('Replacing \n"'+rawTemplateText.substring(childContentElement.getStart(), childContentElement.getEnd())+'" with \n"'+content+'"');
  324. }
  325. //append the last part, i.e. if there is some template-text after the last element:
  326. if(pos - 1 < contentElement.definition.length){
  327. if(pos === 1){
  328. renderResult.push(contentElement.definition);
  329. }
  330. else {
  331. renderResult.push(contentElement.definition.substring(pos-1));
  332. }
  333. }
  334. return renderResult;
  335. }
  336. /**
  337. * HELPER creates a new rendering buffer if neccessary
  338. * @returns {Array} rendering buffer
  339. *
  340. * @private
  341. * @memberOf mmir.parser.RenderUtils#
  342. */
  343. function getRenderingBuffer(renderingBuffer){
  344. if(renderingBuffer)// && isArray(renderingBuffer))
  345. return renderingBuffer;
  346. return new Array();
  347. }
  348. /**
  349. * @private
  350. * @memberOf mmir.parser.RenderUtils#
  351. */
  352. function renderElement(elem, contentForArray, renderingMode, rawTemplateText, renderingBuffer, data, /*optional: */ containingContentElement) {
  353. var type = elem.type;
  354. if(type === parser.element.INCLUDE_SCRIPT){
  355. return renderIncludeScript(elem, renderingMode, rawTemplateText, renderingBuffer, data);
  356. }
  357. else if(type === parser.element.INCLUDE_STYLE){
  358. return renderIncludeStyle(elem, renderingMode, rawTemplateText, renderingBuffer, data);
  359. }
  360. else if(type === parser.element.LOCALIZE){
  361. return renderLocalize(elem, renderingMode, rawTemplateText, renderingBuffer, data);
  362. }
  363. else if(type === parser.element.YIELD_DECLARATION){
  364. return renderYield(elem, contentForArray, renderingMode, rawTemplateText, renderingBuffer, data);
  365. }
  366. else if(type === parser.element.ESCAPE_ENTER){
  367. return renderEscape(elem, renderingMode, rawTemplateText, renderingBuffer);
  368. }
  369. else if(type === parser.element.ESCAPE_EXIT){
  370. return renderEscape(elem, renderingMode, rawTemplateText, renderingBuffer);
  371. }
  372. else if(type === parser.element.COMMENT){
  373. return renderComment(elem, renderingMode, rawTemplateText, renderingBuffer, data);
  374. }
  375. else if(type === parser.element.HELPER){
  376. return renderHelper(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement);
  377. }
  378. else if(type === parser.element.YIELD_CONTENT){
  379. //ignore: this should not be rendered itself, but instead its content should be rendered
  380. // in for the corresponding yield-declaration element.
  381. logger.warn('ParseUtil.renderElement: encountered YIELD_CONTENT for '+elem.name+' -> this sould be handled by renderYieldDeclaration!');
  382. }
  383. else if(type === parser.element.IF){
  384. return renderIf(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement);
  385. }
  386. else if(type === parser.element.FOR){
  387. return renderFor(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement);
  388. }
  389. else if(type === parser.element.RENDER){
  390. return renderPartial(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement);
  391. }
  392. else if(type === parser.element.BLOCK){
  393. return renderScriptBlock(elem, renderingMode, rawTemplateText, renderingBuffer, data);
  394. }
  395. else if(type === parser.element.STATEMENT){
  396. return renderScriptStatement(elem, renderingMode, rawTemplateText, renderingBuffer, data);
  397. }
  398. else if(type === parser.element.VAR_DECLARATION){
  399. return renderVarDeclaration(elem, renderingMode, rawTemplateText, renderingBuffer, data);
  400. }
  401. else if(type === parser.element.VAR_REFERENCE){
  402. return renderVarReference(elem, renderingMode, rawTemplateText, renderingBuffer, data);
  403. }
  404. else {
  405. logger.error('ParseUtil.renderElement: unknown element type -> '+type);
  406. return null;
  407. }
  408. }
  409. /**
  410. * @private
  411. * @memberOf mmir.parser.RenderUtils#
  412. */
  413. function renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer){
  414. renderingBuffer = getRenderingBuffer(renderingBuffer);
  415. renderingBuffer.push(
  416. rawTemplateText.substring(elem.getStart(), elem.getEnd())
  417. );
  418. return renderingBuffer;
  419. }
  420. /**
  421. * @private
  422. * @memberOf mmir.parser.RenderUtils#
  423. */
  424. function renderIncludeScript(elem, renderingMode, rawTemplateText, renderingBuffer, data){
  425. renderingBuffer = getRenderingBuffer(renderingBuffer);
  426. renderingBuffer.push('<script type="text/javascript" charset="utf-8" src="');
  427. renderingBuffer.push( elem.getValue(elem.scriptPath, elem.scriptPathType, data) );
  428. renderingBuffer.push('.js"></script>');
  429. return renderingBuffer;
  430. }
  431. /**
  432. * @private
  433. * @memberOf mmir.parser.RenderUtils#
  434. */
  435. function renderIncludeStyle(elem, renderingMode, rawTemplateText, renderingBuffer, data){
  436. renderingBuffer = getRenderingBuffer(renderingBuffer);
  437. renderingBuffer.push('<link rel="stylesheet" type="text/css" href="');
  438. if(parser.stylesRoot){
  439. renderingBuffer.push(parser.stylesRoot);
  440. }
  441. renderingBuffer.push( elem.getValue(elem.stylePath, elem.stylePathType, data) );
  442. renderingBuffer.push('.css" />');
  443. return renderingBuffer;
  444. }
  445. /**
  446. * @private
  447. * @memberOf mmir.parser.RenderUtils#
  448. */
  449. function renderLocalize(elem, renderingMode, rawTemplateText, renderingBuffer, data){
  450. renderingBuffer = getRenderingBuffer(renderingBuffer);
  451. if(RENDER_MODE_LAYOUT === renderingMode){
  452. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  453. }
  454. var name = elem.getValue(elem.name, elem.nameType, data);
  455. var text = localizer.getText(name);
  456. if(!text){
  457. logger.warn('RenderUtils.renderLocalize: could not find localization text for "'+elem.name+'"');
  458. }
  459. else{
  460. renderingBuffer.push(text);
  461. }
  462. return renderingBuffer;
  463. }
  464. /**
  465. * @private
  466. * @memberOf mmir.parser.RenderUtils#
  467. */
  468. function renderHelper(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){
  469. renderingBuffer = getRenderingBuffer(renderingBuffer);
  470. if(RENDER_MODE_LAYOUT === renderingMode){
  471. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  472. }
  473. var name = elem.getValue(elem.helper, elem.helperType, data);
  474. //set arguments, if helper-statement was given a data-argument:
  475. var prevArgs = null;
  476. if(typeof elem.argsEval !== 'undefined'){
  477. //TODO handle scope & collisions more elaborately?
  478. if(typeof data[PARAM_ARGS_NAME] !== 'undefined'){
  479. prevArgs = data[PARAM_ARGS_NAME];
  480. }
  481. data[PARAM_ARGS_NAME] = elem.argsEval(data);
  482. }
  483. var text = containingContentElement.getController().performHelper(name, data[PARAM_DATA_NAME], data[PARAM_ARGS_NAME]);
  484. //clean-up: handle scope for ARGS
  485. delete data[PARAM_ARGS_NAME];
  486. if(prevArgs !== null){
  487. data[PARAM_ARGS_NAME] = prevArgs;
  488. }
  489. if(typeof text !== 'string'){
  490. logger.debug('RenderUtils.renderHelper: not a STRING result for '+containingContentElement.getController().getName()+'::Helper.'+name+'(), but '+(typeof text));
  491. text = text === null || typeof text === 'undefined'? '' + text : text.toString();
  492. }
  493. //TODO HTML escape for toString before pushing the result (?)
  494. renderingBuffer.push(text);
  495. return renderingBuffer;
  496. }
  497. /**
  498. * @private
  499. * @memberOf mmir.parser.RenderUtils#
  500. */
  501. function renderPartial(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){
  502. renderingBuffer = getRenderingBuffer(renderingBuffer);
  503. if(RENDER_MODE_LAYOUT === renderingMode){
  504. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  505. }
  506. var partialName = elem.getValue(elem.partial, elem.partialType, data);
  507. //set arguments, if render-statement was given a data-argument:
  508. var prevArgs = null;
  509. if(typeof elem.argsEval !== 'undefined'){
  510. //TODO handle scope & collisions more elaborately?
  511. if(typeof data[PARAM_ARGS_NAME] !== 'undefined'){
  512. prevArgs = data[PARAM_ARGS_NAME];
  513. }
  514. data[PARAM_ARGS_NAME] = elem.argsEval(data);
  515. }
  516. //get the Controller object:
  517. var ctrlName = elem.getValue(elem.controller, elem.controllerType, data);
  518. var ctrl;
  519. //check if we already have the controller:
  520. if(containingContentElement.getController() && containingContentElement.getController().getName() == ctrlName){
  521. ctrl = containingContentElement.getController();
  522. }
  523. else {
  524. //...if not: retrieve controller
  525. ctrl = controllerManager.getController(ctrlName);
  526. }
  527. //TODO (?) move getPartial-method from PresentationManager (i.e. remove dependency here)?
  528. //NOTE previously, there was a dependency cycle: upon loading of templateRendererUtils.js, the presentationManager was not yet loaded.
  529. // This should not happen anymore, but just to be save, load the presentationManager, if it is not available yet
  530. if(!presentationManager){
  531. presentationManager = require('mmirf/presentationManager');
  532. }
  533. var partial = presentationManager.getPartial(ctrl, partialName);
  534. if(!partial){
  535. logger.warn('RenderUtils.renderPartial: no partial for controller '+containingContentElement.getController().getName()+', with name >'+partialName+'<');
  536. }
  537. else {
  538. renderContentElement(partial.getContentElement(), renderingBuffer, data);
  539. }
  540. //clean-up: handle scope for ARGS
  541. delete data[PARAM_ARGS_NAME];
  542. if(prevArgs !== null){
  543. data[PARAM_ARGS_NAME] = prevArgs;
  544. }
  545. return renderingBuffer;
  546. }
  547. /**
  548. * @private
  549. * @memberOf mmir.parser.RenderUtils#
  550. */
  551. function renderIf(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){
  552. renderingBuffer = getRenderingBuffer(renderingBuffer);
  553. if(RENDER_MODE_LAYOUT === renderingMode){
  554. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  555. }
  556. var evalCondResult = elem.ifEval(data);
  557. if(evalCondResult) {
  558. renderContentElement(elem.content, renderingBuffer, data);
  559. }
  560. else if(elem.elseContent) {
  561. renderContentElement(elem.elseContent.content, renderingBuffer, data);
  562. }
  563. return renderingBuffer;
  564. }
  565. /**
  566. * @private
  567. * @memberOf mmir.parser.RenderUtils#
  568. */
  569. function renderFor(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){
  570. renderingBuffer = getRenderingBuffer(renderingBuffer);
  571. if(RENDER_MODE_LAYOUT === renderingMode){
  572. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  573. }
  574. if(elem.forControlType === 'FORITER'){
  575. //FOR-type: for(prop in obj)...
  576. //get iterator for prop-values:
  577. var it = elem.forIterator(data);
  578. var current;
  579. while( it.hasNext() ){
  580. current = it.next();
  581. //make the prop-name available in inner FOR-block through the data-object:
  582. data[elem.forPropName] = current;
  583. try{
  584. //render inner FOR-content:
  585. renderContentElement(elem.content, renderingBuffer, data);
  586. } catch(err){
  587. //FIXME experimental mechanism for BREAK within @for
  588. // (need to add syntax for this: @break)
  589. //simulate BREAK statement:
  590. if(err == 'break'){//FIXME use internal/private element for this! (add @break to syntax?)
  591. break;
  592. }
  593. else {
  594. //if it is an error: re-throw it
  595. throw err;
  596. }
  597. }
  598. }
  599. }
  600. else {
  601. //FOR-type: for(var i=0; i < size; ++i)...
  602. elem.forInitEval(data);
  603. while( elem.forConditionEval(data) ){
  604. try{
  605. //render inner FOR-content:
  606. renderContentElement(elem.content, renderingBuffer, data);
  607. } catch(err){
  608. //simulate BREAK statement:
  609. if(err == 'break'){//FIXME use internal/private element for this! (add @break to syntax?)
  610. break;
  611. }
  612. else {
  613. //if it is an error: re-throw it
  614. throw err;
  615. }
  616. }
  617. //execute INCREMENT of FOR-statement:
  618. elem.forIncrementEval(data);
  619. }
  620. }
  621. return renderingBuffer;
  622. }
  623. /**
  624. * @private
  625. * @memberOf mmir.parser.RenderUtils#
  626. */
  627. function renderScriptBlock(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){
  628. renderingBuffer = getRenderingBuffer(renderingBuffer);
  629. if(RENDER_MODE_LAYOUT === renderingMode){
  630. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  631. }
  632. evaluate(elem.scriptContent, data, elem, containingContentElement);
  633. //return unchanged renderingBuffer
  634. return renderingBuffer;
  635. }
  636. /**
  637. * @private
  638. * @memberOf mmir.parser.RenderUtils#
  639. */
  640. function renderScriptStatement(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){
  641. renderingBuffer = getRenderingBuffer(renderingBuffer);
  642. if(RENDER_MODE_LAYOUT === renderingMode){
  643. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  644. }
  645. var result = evaluate(elem.scriptContent, data, elem, containingContentElement);
  646. //TODO escape rendered string (?)
  647. renderingBuffer.push(result);
  648. return renderingBuffer;
  649. }
  650. /**
  651. * @private
  652. * @memberOf mmir.parser.RenderUtils#
  653. */
  654. function renderVarDeclaration(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){
  655. renderingBuffer = getRenderingBuffer(renderingBuffer);
  656. if(RENDER_MODE_LAYOUT === renderingMode){
  657. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  658. }
  659. //NOTE all template-vars start with special char @
  660. // var fieldName = '@'+ elem.getValue(elem.name, elem.nameType, data);
  661. var varName = elem.getValue(elem.name, elem.nameType);//FIXME: do not invoke with data; we only want the VAR-name!//, data);
  662. var fieldName = '@'+ varName;
  663. //initialize field for var-declaration
  664. if(typeof data[fieldName] === 'undefined'){
  665. data[fieldName] = null;
  666. //TODO impl. structures/mechanisms for deleting/removing the var on exiting
  667. // the scope of the corresponding ContentElement
  668. // ... with special case when field already existed before:
  669. // (1) delete/remove only on the outer most scope exit
  670. // (2) on 'inner' exits we need to restore the value that the variable had
  671. // when we entered the scope (i.e. when we "overwrote" the existing var
  672. // with an inner local var)
  673. // --> (a) we need to store the value here when var already exists
  674. // (b) on parsing JS code we need to consider var-declarations, i.e. @-variables
  675. // that are proceeded with a 'var'-statement, e.g.: 'var @varName = ...'
  676. }
  677. // TODO handle case when field already exists (?)
  678. //return unchanged renderingBuffer
  679. return renderingBuffer;
  680. }
  681. /**
  682. * @private
  683. * @memberOf mmir.parser.RenderUtils#
  684. */
  685. function renderVarReference(elem, renderingMode, rawTemplateText, renderingBuffer, data, containingContentElement){
  686. renderingBuffer = getRenderingBuffer(renderingBuffer);
  687. if(RENDER_MODE_LAYOUT === renderingMode){
  688. return renderRaw(elem, renderingMode, rawTemplateText, renderingBuffer);
  689. }
  690. var varName = rawTemplateText.substring(elem.getStart(),elem.getEnd());
  691. //handle "import"/"export" of call/args-data
  692. if(varName === PARAM_DATA_NAME || varName === PARAM_ARGS_NAME){
  693. //TODO should there be a check included -> for var-existance?
  694. // --> currently: on assignment, if var does not exists, it will be created
  695. // --> change (?), so that there is a check first, and if var does not exists an ReferenceError is thrown (?)
  696. renderingBuffer.push(DATA_NAME);
  697. renderingBuffer.push('["');
  698. if(renderingMode === RENDER_MODE_JS_SOURCE_FORCE_VAR_PREFIX){
  699. //ensure that the replacement variable-name starts with an @:
  700. if( ! varName[0] === '@'){
  701. varName = '@' + varName;
  702. }
  703. }
  704. //extract var-name from original source-code (NOTE this var must start with @)
  705. renderingBuffer.push(varName);
  706. renderingBuffer.push('"]');
  707. } else {
  708. //render variable (without leading @ symbol)
  709. renderingBuffer.push(varName[0] === '@'? varName.substring(1) : varName);
  710. }
  711. return renderingBuffer;
  712. }
  713. /**
  714. * @private
  715. * @memberOf mmir.parser.RenderUtils#
  716. */
  717. function renderEscape(elem, renderingMode, rawTemplateText, renderingBuffer){
  718. renderingBuffer = getRenderingBuffer(renderingBuffer);
  719. if(RENDER_MODE_LAYOUT === renderingMode){
  720. return renderRaw(elem, renderingMode, rawTemplateText);
  721. }
  722. renderingBuffer.push( elem.text);
  723. return renderingBuffer;
  724. }
  725. /**
  726. * @private
  727. * @memberOf mmir.parser.RenderUtils#
  728. */
  729. function renderComment(elem, renderingMode, rawTemplateText, renderingBuffer){
  730. //render comment: omit comment text from rendering!
  731. // renderingBuffer = getRenderingBuffer(renderingBuffer);
  732. //
  733. // if(RENDER_MODE_LAYOUT === renderingMode){
  734. // return renderRaw(elem, renderingMode, rawTemplateText);
  735. // }
  736. // var comment = rawTemplateText.substring(elem.getStart()+2,elem.getEnd()-2);
  737. // renderingBuffer.push( comment );
  738. return renderingBuffer;
  739. }
  740. /**
  741. * @private
  742. * @memberOf mmir.parser.RenderUtils#
  743. */
  744. function getContentForYield(name, contentForArray){
  745. for(var i=0, size = contentForArray.length; i < size; ++i){
  746. if(name === contentForArray[i].getName()){
  747. return contentForArray[i];
  748. }
  749. }
  750. return null;
  751. }
  752. /**
  753. * @private
  754. * @memberOf mmir.parser.RenderUtils#
  755. */
  756. function renderYield(elem, contentForArray, renderingMode, rawTemplateText, renderingBuffer, data){
  757. renderingBuffer = getRenderingBuffer(renderingBuffer);
  758. if(RENDER_MODE_LAYOUT === renderingMode){
  759. return renderRaw(elem, renderingMode, rawTemplateText);
  760. }
  761. else {
  762. var name = elem.getValue(elem.name, elem.nameType, data);
  763. var contentFor = getContentForYield(name, contentForArray);
  764. if(!contentFor){
  765. logger.info('ParseUtil.renderYield: could not find content-definition for yield '+name);
  766. return renderingBuffer;
  767. }
  768. if(contentFor.hasDynamicContent()){
  769. return renderContentElement(contentFor, renderingBuffer, data);
  770. }
  771. else {
  772. renderingBuffer.push(contentFor.getRawText());
  773. return renderingBuffer;
  774. }
  775. // return contentFor.toHtml();
  776. }
  777. }
  778. /**
  779. * @private
  780. * @memberOf mmir.parser.RenderUtils#
  781. */
  782. function evaluate(evalStatement, data, element, containingContentElement){
  783. var result = element.scriptEval(data);
  784. return result;
  785. }
  786. /**
  787. * HELPER for creating the data-object
  788. * @private
  789. * @memberOf mmir.parser.RenderUtils#
  790. */
  791. function createInternalData(eventData){
  792. //create DATA object that contains (or will be filled with)
  793. // 1. event data (in PARAM_DATA_NAME)
  794. // TODO 2. arguments (for template expressions that support arguments; in ARGS_NAME)
  795. // 3. declared template variables (under the name of their declaration)
  796. // TODO 4. variables in template script-blocks/script-statements that were not declared (for avoiding global var declarations when evaluating these script fragements)
  797. var dataArgs = new Object();
  798. dataArgs[PARAM_DATA_NAME] = eventData;
  799. return dataArgs;
  800. }
  801. /** @lends mmir.parser.RenderUtils.prototype */
  802. return {
  803. //public members:
  804. /**
  805. * Renders a layout in preparation for displaying content:
  806. * This function should be used to preperare the layout content, so that its
  807. * views can be rendered into it (needs to be done only once, after the layout is loaded).
  808. *
  809. * @param {mmir.parser.ParsingResult} parseResult the parsed view template
  810. * @param {mmir.view.ContentElement[]} [contentForArray]
  811. * @returns {String} the prepared layout content
  812. *
  813. * @public
  814. * @memberOf mmir.parser.RenderUtils.prototype
  815. */
  816. renderLayout: function(parseResult, contentForArray){
  817. return renderLayout(parseResult, contentForArray, RENDER_MODE_LAYOUT);
  818. },
  819. /**
  820. * Renders a view.
  821. *
  822. * <p>During rendering, the view's template-expressions are evaluated, and the results rendered into
  823. * the returned String.
  824. *
  825. * @param {String|mmir.view.ContentElement} htmlContentString
  826. * the original view-content of the layout-template text, see {@link mmir.view.Layout#getBodyContents}
  827. * or a ContentElement with its YieldDeclarations in its allContentElements field (by default yields are not contained in ContentElement.allContentElements)
  828. * @param {Array<mmir.view.YieldDeclaration>} yieldDeclarationsArray
  829. * a list of yield-declarations of the layout
  830. * @param {Array<mmir.view.ContentElement>} contentForObjectsArray
  831. * a list of content-for objects of the view. This list must supply a corresponding objecet for each entry in the <tt>yieldDeclarationsArray</tt>.
  832. * @param {Object} [data] OPTIONAL
  833. * a JSON object which's fields will be available during rendering/evaluation of the template expressions
  834. * @returns {String} the evaluated and rendered view-content
  835. *
  836. * @public
  837. * @memberOf mmir.parser.RenderUtils.prototype
  838. */
  839. renderViewContent: function(htmlContentString, yieldDeclarationsArray, contentForObjectsArray, data){
  840. var dataArgs = createInternalData(data);
  841. if(typeof htmlContentString === 'string'){
  842. return renderContent(htmlContentString, yieldDeclarationsArray, contentForObjectsArray, RENDER_MODE_VIEW_CONTENT, dataArgs);
  843. } else {
  844. return renderContentElement(
  845. htmlContentString,
  846. null,//<- yields should be in htmlContentString.allContentElements
  847. dataArgs,
  848. contentForObjectsArray
  849. ).join('');
  850. }
  851. },
  852. /**
  853. * Renders a single {@link mmir.view.ContentElement} object.
  854. *
  855. * <p>During rendering, the view's template-expressions are evaluated, and the results rendered into
  856. * the returned String.
  857. *
  858. * @param {mmir.view.ContentElement} contentElement the ContentElement object that should be rendered
  859. * @param {Object} [data] a JSON object which's fields will be available during rendering/evaluation of the template expressions
  860. * @param {Array<String>} [renderingBuffer] if provided, the partial rendering results will be appended to this Array
  861. * @returns {String} the evaluated and rendered ContentElement; if <tt>renderingBuffer</tt> was provided and not empty, the result will be prepended with the concatenated contents of the Array's Strings
  862. *
  863. * @public
  864. * @memberOf mmir.parser.RenderUtils.prototype
  865. */
  866. renderContentElement: function(contentElement, data, renderingBuffer/*optional*/){
  867. var dataArgs = createInternalData(data);
  868. return renderContentElement(contentElement, renderingBuffer, dataArgs);
  869. },
  870. /**
  871. * Renders the dialog content for a view.
  872. *
  873. * <p>During rendering, the view's template-expressions are evaluated, and the results rendered into
  874. * the returned String.
  875. *
  876. * @param {String} htmlContentString the original dialog-content of the layout-template text, see {@link mmir.view.Layout#getDialogsContents}
  877. * @param mmir.view.YieldDeclaration[]} yieldDeclarationsArray a list of yield-declarations of the layout
  878. * @param {mmir.view.ContentElement[]} contentForObjectsArray a list of content-for objects of the view. This list must supply a corresponding objecet for each entry in the <tt>yieldDeclarationsArray</tt>.
  879. * @param {Object} [data] a JSON object which's fields will be available during rendering/evaluation of the template expressions
  880. * @returns {String} the evaluated and rendered dialog-content
  881. *
  882. * @public
  883. * @memberOf mmir.parser.RenderUtils.prototype
  884. */
  885. renderViewDialogs: function(htmlContentString, yieldDeclarationsArray, contentForObjectsArray, data){
  886. var dataArgs = createInternalData(data);
  887. return renderContent(htmlContentString, yieldDeclarationsArray, contentForObjectsArray, RENDER_MODE_VIEW_DIALOGS, dataArgs);
  888. },
  889. /**
  890. * Prepares JavaScript source code for usage in rendering the template (view/partial etc.).
  891. *
  892. * The replacement-list contains information which parts of the raw JavaScript code should be
  893. * modified (e.g. indices [start,end] for replacing text in the source code).
  894. *
  895. * The function returns the modified JavaScript source code as a String.
  896. *
  897. *
  898. * If the mode is <code>isForcePrefix == true</code>, the variable-names that correspond
  899. * to replacementObjectsList are check: if a name does not start with @, then the name will prepended with @ before
  900. * rendering.
  901. *
  902. * @param {String} rawJSSourceCode the original JavaScript source code
  903. * @param {mmir.parser.ParsingResult[]} replacementObjectsList
  904. * @param {Boolean} [isForcePrefix]
  905. *
  906. * @public
  907. * @memberOf mmir.parser.RenderUtils.prototype
  908. */
  909. renderJS: function(rawJSSourceCode, replacementObjectsList, isForcePrefix){
  910. var mode = isForcePrefix? RENDER_MODE_JS_SOURCE_FORCE_VAR_PREFIX : RENDER_MODE_JS_SOURCE;
  911. return renderJSSource(rawJSSourceCode, replacementObjectsList, mode);
  912. }
  913. };//END: return{}
  914. }//END: constructor()
  915. instance = new constructor();
  916. //FIXME should the renderer be exported to parser.RenderUtils here?
  917. parser.RenderUtils = instance;
  918. return instance;
  919. });//END: define(..., function(){
  920. //}( this.mmir = this.mmir || {} ));