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 || {} ));