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