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 define(['languageManager', 'parserModule', 'storageUtils'],
 28 	//this comment is needed by jsdoc2 [copy of comment for: function ContentElement(...]
 29 	/**
 30 	 * The ContentElement represents "content" parts of a view; it may itself contain one or more ContentElements.
 31 	 * 
 32 	 * This class holds the name of the content-field (used via the yield-tag in the layouts: content, header, footer, dialogs, ...)
 33 	 * and its definition as HTML-String.
 34 	 * 
 35 	 * @class
 36 	 * @name ContentElement
 37 	 * @public
 38 	 * 
 39 	 * @param {Array|Object} group
 40 	 * 				 an array or object with properties <code>name</code> {String}, and <code>content</code> {String}
 41 	 * @param {Object} view 
 42 	 * 				the view that owns this ContentElement-element 
 43 	 * @param {mmir.parser.ParserUtils} parser 
 44 	 * 				for the the content (optional) if supplied this object must have a function <code>parse({String})</code> (see templateParseUtil)
 45 	 * @param {mmir.parser.RenderUtils} renderer
 46 	 * 				 for the the content (optional) if supplied, a <code>parser</code> must also be supplied; the renderer must have a function <code>parse({String})</code> (see templateRenderUtil)
 47 	 * 
 48 	 */
 49 	function(
 50 			languageManager, parser_context
 51 ){//NOTE: dependency storageUtils is actually accessed through parser_context (i.e. it attaches its functions to parserModule)
 52 	
 53 /** @scope ContentElement.prototype *///for jsdoc2
 54 	
 55 //set to @ignore in order to avoid doc-duplication in jsdoc3
 56 /**
 57  * @ignore
 58  * 
 59  * The ContentElement represents "content" parts of a view; it may itself contain one or more ContentElements.
 60  * 
 61  * This class holds the name of the content-field (used via the yield-tag in the layouts: content, header, footer, dialogs, ...)
 62  * and its definition as HTML-String.
 63  * 
 64  * @constructs ContentElement
 65  * @public
 66  * 
 67  * @param {Array|Object} group
 68  * 				 an array or object with properties <code>name</code> {String}, and <code>content</code> {String}
 69  * @param {Object} view 
 70  * 				the view that owns this ContentElement-element 
 71  * @param {mmir.parser.ParserUtils} parser 
 72  * 				for the the content (optional) if supplied this object must have a function <code>parse({String})</code> (see templateParseUtil)
 73  * @param {mmir.parser.RenderUtils} renderer
 74  * 				 for the the content (optional) if supplied, a <code>parser</code> must also be supplied; the renderer must have a function <code>parse({String})</code> (see templateRenderUtil)
 75  * 
 76  */
 77 function ContentElement(group, view, parser, renderer){
 78 
 79 	/**
 80 	 * the "localizer" i.e. for handeling internationalization / localized Strings
 81 	 * 
 82 	 * @protected
 83 	 * @type mmir.LanguageManager
 84 	 * @memberOf ContentElement#
 85 	 */
 86 	this.localizer  = languageManager;
 87 	
 88 	if(arguments.length === 0){
 89 		return this;
 90 	}
 91 	
 92 	
 93 	/**
 94 	 * dummy name, if the ContentElement does not have a name:
 95 	 * only ContentElements that represent Views and Partials have names -
 96 	 * other sub-elements (@if,@for etc) do not have their own name/identifier.
 97 	 * 
 98 	 * TODO externalize as constant
 99 	 * 
100 	 * @private
101 	 * @constant
102 	 * @memberOf ContentElement#
103 	 */
104 	var SUB_ELEMENT_NAME = "@fragment";
105 	
106 	this.parser     = parser;
107 	this.renderer   = renderer;
108 	this.view       = view;
109 	
110 	if(typeof group.name !== 'undefined' && typeof group.content !== 'undefined'){
111 		this.name = group.name;
112 		
113 		//check if the name needs to be converted from a "raw" value:
114 		if(typeof group.getValue === 'function' && typeof group.nameType !== 'undefined'){
115 			this.name = group.getValue(this.name, group.nameType, null);
116 		}
117 		
118 		this.definition = group.content;
119 	}
120 	else {
121 		this.name = group[1];
122 	    this.definition = group[2];
123 	}
124 	
125 	if(typeof group.start !== 'undefined' && typeof group.end !== 'undefined'){
126 		this.start = group.start;
127 		this.end = group.end;
128 	}
129 	
130 	if(typeof group.offset !== 'undefined'){
131 		/**
132 		 * The offset of the ContentElement's raw String-content
133 		 * in relation to its parent ContentElement. 
134 		 * <p>
135 		 * I.e. only when ContentElements are nested with other ContentElements.
136 		 * <p>
137 		 * For nested ContentElements, the offset always refers to outermost
138 		 * ContentElement, e.g.
139 		 * <pre>
140 		 *   content
141 		 *   ContentElement_1
142 		 *   	ContentElement_2.parentOffset: offset to ContentElement_1
143 		 *   		...
144 		 *   			ContentElement_i.parentOffset: offset to ContentElement_1</pre>
145 		 * 
146 		 * @type Number
147 		 * @private
148 		 */
149 		this.parentOffset = group.offset;
150 	}
151 	else if(typeof group.contentOffset !== 'undefined'){
152 
153 		this.parentOffset = group.contentOffset;
154 	}
155 	else {
156 		this.parentOffset = 0;
157 	}
158 	
159 	/**
160 	 * The ParsingResult that represents this ContentElement
161 	 * 
162 	 * @private
163 	 * @type mmir.parser.ParsingResult
164 	 * @memberOf ContentElement#
165 	 */
166 	var parsingResult = parser.parse(this.definition, this);
167 	/**
168 	 * The "raw" template text.
169 	 * 
170 	 * @protected
171 	 * @type String
172 	 * @memberOf ContentElement#
173 	 * 
174 	 */
175 	this.definition 	= parsingResult.rawTemplateText;
176 	/**
177 	 * List of the "localize" statements in the template.
178 	 * 
179 	 * @protected
180 	 * @type mmir.parser.ParsingResult
181 	 * @memberOf ContentElement#
182 	 * 
183 	 * @see mmir.parser.element.LOCALIZE
184 	 */
185 	this.localizations 	= parsingResult.localizations;
186 	/**
187 	 * @protected
188 	 * @type mmir.parser.ParsingResult
189 	 * @memberOf ContentElement#
190 	 * 
191 	 * @see mmir.parser.element.ESCAPE_ENTER
192 	 * @see mmir.parser.element.ESCAPE_EXIT
193 	 */
194 	this.escapes        = parsingResult.escapes;
195 	/**
196 	 * @protected
197 	 * @type mmir.parser.ParsingResult
198 	 * @memberOf ContentElement#
199 	 * 
200 	 * @see mmir.parser.element.HELPER
201 	 */
202 	this.helpers		= parsingResult.helpers;
203 	/**
204 	 * @protected
205 	 * @type mmir.parser.ParsingResult
206 	 * @memberOf ContentElement#
207 	 * 
208 	 * @see mmir.parser.element.BLOCK
209 	 */
210 	this.scriptBlocks     = parsingResult.scriptBlocks;
211 	/**
212 	 * @protected
213 	 * @type mmir.parser.ParsingResult
214 	 * @memberOf ContentElement#
215 	 * 
216 	 * @see mmir.parser.element.STATEMENT
217 	 */
218 	this.scriptStatements = parsingResult.scriptStatements;
219 ////	this.includeScripts   = parsingResult.includeScripts; @see mmir.parser.element.INCLUDE_SCRIPT
220 ////	this.includeStyles    = parsingResult.includeStyles; @see mmir.parser.element.INCLUDE_STYLE
221 	/**
222 	 * @protected
223 	 * @type mmir.parser.ParsingResult
224 	 * @memberOf ContentElement#
225 	 * 
226 	 * @see mmir.parser.element.RENDER
227 	 */
228 	this.partials         = parsingResult.partials;
229 	/**
230 	 * @protected
231 	 * @type mmir.parser.ParsingResult
232 	 * @memberOf ContentElement#
233 	 * 
234 	 * @see mmir.parser.element.IF
235 	 */
236 	this.ifs              = parsingResult.ifs;
237 	/**
238 	 * @protected
239 	 * @type mmir.parser.ParsingResult
240 	 * @memberOf ContentElement#
241 	 * 
242 	 * @see mmir.parser.element.FOR
243 	 */
244 	this.fors             = parsingResult.fors;
245 	/**
246 	 * @protected
247 	 * @type mmir.parser.ParsingResult
248 	 * @memberOf ContentElement#
249 	 * 
250 	 * @see mmir.parser.element.VAR_DECLARATION
251 	 */
252 	this.vars             = parsingResult.vars;
253 	/**
254 	 * @protected
255 	 * @type mmir.parser.ParsingResult
256 	 * @memberOf ContentElement#
257 	 * 
258 	 * @see mmir.parser.element.COMMENT
259 	 */
260 	this.comments         = parsingResult.comments;
261 	
262 //	this.yields           = parsingResult.yields; @see mmir.parser.element.YIELD_DECLARATION
263 //	this.contentFors    = parsingResult.contentFors; @see mmir.parser.element.YIELD_CONTENT
264 	
265 	//create ALL array and sort localizations etc. ...
266 	/**
267 	 * create ALL array and sort it, i.e. for localizations etc. ...
268 	 * @private
269 	 * @type Array<mmir.parser.ParsingResult>
270 	 * @memberOf ContentElement#
271 	 */
272 	var all = this.localizations.concat(
273 			this.escapes, 
274 			this.helpers,
275 			this.scriptBlocks,
276 			this.scriptStatements,
277 ////			this.includeScripts,
278 ////			this.includeStyles,
279 			this.partials,
280 			this.ifs,
281 			this.fors,
282 			this.vars,
283 			this.comments//,
284 //			this.yields,
285 //			this.contentFors
286 	);
287 	
288 	/**
289 	 * HELPER sorting function -> sort elements by occurrence in raw template text
290 	 * @private
291 	 * @function
292 	 * @memberOf ContentElement#
293 	 */
294 	var sortAscByStart = function(parsedElem1, parsedElem2){
295 		return parsedElem1.getStart() - parsedElem2.getStart();
296 	};
297 	all.sort(sortAscByStart);
298 
299 	this.allContentElements = all;
300 	
301 	/**
302 	 * HELPER check if a ContentElement has "dynamic content"
303 	 * 
304 	 * @private
305 	 * @function
306 	 * @memberOf ContentElement#
307 	 */
308 	var checkHasDynamicContent = function(contentElement){
309 		return	(contentElement.localizations 		&& contentElement.localizations.length 		> 0)
310 			|| 	(contentElement.helpers 			&& contentElement.helpers.length 			> 0)
311 			|| 	(contentElement.scriptBlocks 		&& contentElement.scriptBlocks.length 		> 0)
312 			|| 	(contentElement.scriptStatements 	&& contentElement.scriptStatements.length 	> 0)
313 			|| 	(contentElement.partials 			&& contentElement.partials.length 			> 0)
314 			|| 	(contentElement.ifs 				&& contentElement.ifs.length 				> 0)
315 			|| 	(contentElement.fors 				&& contentElement.fors.length 				> 0)
316 			|| 	(contentElement.vars 				&& contentElement.vars.length 				> 0)
317 			//TODO should comments be "pre-rendered", i.e. already removed here, so that they need not be re-evaluated each time a view gets rendered?
318 			|| 	(contentElement.comments			&& contentElement.comments.length 			> 0)
319 		;//TODO if ContentElement supports more dynamic elements (e.g. child-ContentElement objects ...) then add appropriate checks here!
320 	};
321 	
322 	//"buffered" field that signifies if this ContentElement has dynamic content
323 	// (--> i.e. has to be evaluated on each rendering, or -if not- can be statically rendered once)
324 	this.internalHasDynamicContent = checkHasDynamicContent(this);
325 	
326 	/**
327 	 * Error for parsing problems with detailed location information (i.e. where the parsing problem occured).
328 	 * 
329 	 * @property {String} name		the error name, that triggered the ScriptEvalError
330 	 * @property {String} message	the message of the error that triggered the ScriptEvalError
331 	 * @property {String} stack		the error stack (if available)
332 	 * 
333 	 * @property {String} details	the detailed message of the ScriptEvalError including the positional information and the error that triggered it
334 	 * @property {Number} offset	the offset (number of characters) of the ContentElement where the error occurred (in relation to its parent/owning Element)
335 	 * @property {Number} start		the starting position for the content (number of characters) within the ContentElement's <code>rawText</code>
336 	 * @property {Number} end		the end position for the content (number of characters) within the ContentElement's <code>rawText</code>
337 	 * 
338 	 * @class
339 	 * @name ScriptEvalError
340 	 */
341 	var ScriptEvalError = function(error, strScript, contentElement, parsingElement){
342 		
343 		var err = Error.apply(this, arguments);
344 		err.name = this.name = 'ScriptEvalError';
345 
346 		this.stack = err.stack;
347 		this.message = err.message;
348 
349 		if(typeof this.stack === 'string'){
350 			//remove first line of stack (which would only contain a reference to this constructor)
351 			this.stack = this.stack.replace(/^.*?\r?\n/, this.name + ': ');
352 		}
353 		
354 		var offset = 0;
355 //		if(parsingElement.contentOffset){
356 //			console.error('elem.offset: '+parsingElement.contentOffset);
357 ////			offset = parsingElement.contentOffset;
358 //		}
359 		offset += contentElement.getOffset();
360 		
361 		this.offset = offset;
362 		
363 		this.start = this.offset + parsingElement.getStart();
364 		this.end = this.offset + parsingElement.getEnd();
365 		
366 		this.errorDetails = parser_context.parserCreatePrintMessage(
367 				'ContentElement.ScriptEvalError: Error evaluating script '
368 					+JSON.stringify(strScript)
369 					+' for ' + parsingElement.getTypeName()
370 					+' element:\n', 
371 				this.message,
372 				this
373 		);
374 		
375 		/**
376 		 * Get the detailed error message with origin information.
377 		 * 
378 		 * @public
379 		 * @returns {String} the detailed error message
380 		 * @see #details
381 		 * 
382 		 * @var {Function} ScriptEvalError#getDetails
383 		 */
384 		this.getDetails = function(){
385 			return this.errorDetails;
386 		};
387 		
388 		return this;
389 	};
390 	
391 	/**
392 	 * HELPER: this creates a function for embedded JavaScript code:
393 	 *  		using a function pre-compiles codes - this avoids (re-) parsing the code  
394 	 *   		(by the execution environment) each time that the template is rendered.
395 	 *   
396 	 * @param {String} strFuncBody
397 	 * 			the JavaScript code for the function body
398 	 * @param {String} strFuncName
399 	 * 			the name for the function
400 	 * @returns {Function} the evaluated function with one input argument (see <code>DATA_NAME</code>)
401 	 * 
402 	 * @private
403 	 * @function
404 	 * @memberOf ContentElement#
405 	 */
406 	var createJSEvalFunction = function(strFuncBody, strFuncName){
407 		
408 		//COMMENT: using new Function(..) may yield less performance than eval('function...'), 
409 		//         since the function-body using the Function(..)-method is re-evaluated on each invocation
410 		//         whereas when the eval('function...')-method behaves as if the function was declared statically 
411 		//         like a normal function-expression (after its first evaluation here).
412 		//
413 //		var func = new Function(parser_context.element.DATA_NAME, strFuncBody);
414 //		func.name = strFuncName;
415 		
416 		
417 //		//TEST use import/export VARs instead of data-object access:
418 //		//
419 //		//IMPORT
420 //		// * make properties of DATA available as local variables
421 //		// * synchronize the DATA properties to local variables (with property getters/setters)
422 //		//EXPORT
423 //		// * on exit: commit values of local variables to their corresponding DATA-fields (and remove previously set "sync"-code)
424 //		//
425 //		var dataFieldName = parser_context.element.DATA_NAME;
426 //		
427 //		//TODO do static "import" without eval(): only import VARs that were declared by @var() before!
428 //		//     ... also (OPTIMIZATION): during JS-parsing, gather/detect VARIABLE occurrences -> only import VAR if it gets "mentioned" in the func-body! (need to detect arguments vs. variables for this!)		
429 //		var iteratorName = '__$$ITER$$__';//<- iterator name (for iterating over DATA fields)
430 //		var varIteratorStartSrc = 'for(var '+iteratorName+' in '+dataFieldName+'){\
431 //	          if('+dataFieldName+'.hasOwnProperty('+iteratorName+')){';//<- TODO? add check, if field-name starts with @?
432 //		var varIteratorEndSrc = '}}';
433 //		
434 //		var importDataSrc = varIteratorStartSrc
435 //					//create local variable, initialized with the DATA's value 
436 //					+ 'eval("var "+'+iteratorName+'.substring(1)+" = '+dataFieldName+'[\'"+'+iteratorName+'+"\'];");'
437 //					//"synchronize" the DATA object to to the created local variable 
438 //					+ 'Object.defineProperty('+dataFieldName+', '+iteratorName+',{\
439 //		                    configurable : true,\
440 //		                    enumerable : true,\
441 //		    				set: eval("var dummy1 = function set(value){\\n "+'+iteratorName+'.substring(1)+" = value;\\n };dummy1"),\
442 //		    				get: eval("var dummy2 = function get(){\\n return "+'+iteratorName+'.substring(1)+";\\n };dummy2")\
443 //		    			});'
444 //					+ varIteratorEndSrc;
445 //		
446 //		//TODO do not define "export" with the function itself, since there may be problems due to return statements etc.
447 //		//     ... instead: do the "export" after the function was invoked, i.e. obj.evalScript() etc. in renderer
448 //		var exportDataSrc = varIteratorStartSrc
449 //					//DISABLED: use defineProperty() instead (see below) ... this would use the "proxy"/"sync" mechanism..
450 ////					+ 'eval("'+dataFieldName+'[\'"+'+iteratorName+'+"\'] = "+'+iteratorName+'.substring(1)+";");'
451 //		
452 //					//reset to DATA property to normal behavior 
453 //					// i.e. remove proxy-behavior by removing the getter/setter
454 //					// and setting to current value
455 //					+ 'Object.defineProperty('+dataFieldName+', '+iteratorName+',{\
456 //							value : '+dataFieldName+'['+iteratorName+'],\
457 //                    		writable : true,\
458 //		                    configurable : true,\
459 //		                    enumerable : true\
460 //		    			});'
461 //					+ varIteratorEndSrc;
462 //		
463 //		var func = eval( 'var dummy=function '+strFuncName+'('+parser_context.element.DATA_NAME+'){'
464 //				+ importDataSrc + strFuncBody +';'+exportDataSrc+'};dummy' );//<- FIXME WARING: export does not work correctly, if there is a return-statement in the outermost scope of the strFuncBody!
465 		
466 		
467 //		//NOTE: need a dummy variable to catch and return the create function-definition in the eval-statement
468 //		//      (the awkward 'var dummy=...;dummy'-construction avoids leaking the dummy-var into the 
469 //		//       global name-space, where the last ';dummy' represent the the return-statement for eval(..) )
470 		var func = eval( 'var dummy=function '+strFuncName+'('+parser_context.element.DATA_NAME+'){'+strFuncBody+'};dummy' );
471 		
472 		return func;
473 	};
474 	
475 	//init iter-variables
476 	var i=0,size=0;
477 	var parsedJS = null, preparedJSCode = null, forPropNameRef = null, forListNameRef = null;
478 	var forIterInit = null, forIterFunc = null;
479 	var renderPartialsElement = null, helperElement = null, ifElement = null, forElement = null, subContentElement = null;
480 	
481 	//prepare render-partial-elements
482 	for(i=0, size = this.partials.length; i < size; ++i){
483 		renderPartialsElement = this.partials[i];
484 		
485 		//for @render(ctrl,name, DATA):
486 		//  initialize the DATA-argument, if present:
487 		if( renderPartialsElement.hasCallData() ){
488 			//TODO use original parser/results instead of additional parsing pass
489 			parsedJS = parser.parseJS( 
490 				this.definition.substring( renderPartialsElement.getCallDataStart(),  renderPartialsElement.getCallDataEnd() ),
491 				'embeddedStatementTail',//<- "internal" parser rule for parsing fragments: >>JS_STATEMENT EOF<<
492 				this//TODO supply/implement more accurate error-localization: this is indeed wrong, since it is not the view-defintion, but: this.definition=<view's contentFor>, then renderPartialsElement.rawResult and .dataPos contain the information, where exactly this element is located...
493 				, renderPartialsElement.getStart() + this.getOffset() + '@render('.length
494 			);
495 			preparedJSCode = renderer.renderJS(parsedJS.rawTemplateText, parsedJS.varReferences, true);
496 			
497 			try{
498 				renderPartialsElement.argsEval = createJSEvalFunction('return ('+preparedJSCode+');', 'argsEval');
499 			} catch (err){
500 				var error = new ScriptEvalError(err, preparedJSCode,  this, renderPartialsElement);
501 				//attach a dummy function that prints the error each time it is invoked:
502 				renderPartialsElement.argsEval = function(){ console.error(error.getDetails()); };
503 				//... and print the error now, too:
504 				console.error(error.getDetails());
505 			}
506 		}
507 	}
508 	
509 	//prepare helper-elements
510 	for(i=0, size = this.helpers.length; i < size; ++i){
511 		helperElement = this.helpers[i];
512 		
513 		//for @helper(name, DATA):
514 		//  initialize the DATA-argument, if present:
515 		if( helperElement.hasCallData() ){
516 			//TODO use original parser/results instead of additional parsing pass
517 			parsedJS = parser.parseJS( 
518 				this.definition.substring( helperElement.getCallDataStart(),  helperElement.getCallDataEnd() ),
519 				'embeddedStatementTail',//<- "internal" parser rule for parsing fragments: >>JS_STATEMENT EOF<<
520 				this//TODO supply/implement more accurate error-localization: this is indeed wrong, since it is not the view-defintion, but: this.definition=<view's contentFor>, then helperElement.rawResult and .dataPos contain the information, where exactly this element is located...
521 				, helperElement.getStart() + this.getOffset() + '@helper('
522 			);
523 			preparedJSCode = renderer.renderJS(parsedJS.rawTemplateText, parsedJS.varReferences, true);
524 			
525 			try{
526 				helperElement.argsEval = createJSEvalFunction('return ('+preparedJSCode+');', 'argsEval');
527 			} catch (err){
528 				var error = new ScriptEvalError(err, preparedJSCode,  this, helperElement);
529 				//attach a dummy function that prints the error each time it is invoked:
530 				helperElement.argsEval = function(){ console.error(error.getDetails()); };
531 				//... and print the error now, too:
532 				console.error(error.getDetails());
533 			}
534 		}
535 	}
536 	
537 	//prepare if-elements
538 	for(i=0, size = this.ifs.length; i < size; ++i){
539 		ifElement = this.ifs[i];
540 		
541 		//TODO use original parser/results instead of additional parsing pass
542 		parsedJS = parser.parseJS(
543 				ifElement.ifExpr, 
544 				this//TODO supply/implement more accurate error-localization: this is indeed wrong, since it is not the view-defintion, but: this.definition=<view's contentFor>, then helperElement.rawResult and .dataPos contain the information, where exactly this element is located...
545 				, ifElement.getStart() + this.getOffset() + '@if('.length
546 		);
547 		preparedJSCode = renderer.renderJS(parsedJS.rawTemplateText, parsedJS.varReferences);
548 		
549 		try{
550 			ifElement.ifEval = createJSEvalFunction('return ('+preparedJSCode+');', 'ifEval');
551 		} catch (err){
552 			var error = new ScriptEvalError(err, preparedJSCode,  this, ifElement);
553 			//attach a dummy function that prints the error each time it is invoked:
554 			ifElement.ifEval = function(){ console.error(error.getDetails()); };
555 			//... and print the error now, too:
556 			console.error(error.getDetails());
557 		}
558 	}
559 	
560 	//prepare for-elements
561 	for(i=0, size = this.fors.length; i < size; ++i){
562 		forElement = this.fors[i];
563 
564 		if(forElement.forControlType === 'FORITER'){
565 
566 //			forElement.forIterationExpr = ...;
567 //			forElement.forObjectExpr    = ...;
568 
569 			forPropNameRef = forElement.forControlVarPos[0];
570 			forListNameRef = forElement.forControlVarPos[1];
571 			
572 			forElement.forPropName = this.definition.substring(forPropNameRef.getStart(), forPropNameRef.getEnd());
573 			forElement.forListName = this.definition.substring(forListNameRef.getStart(), forListNameRef.getEnd());
574 			
575 			//prepend variable-names with template-var-prefix if necessary:
576 			if( ! forElement.forPropName.startsWith('@')){
577 				forElement.forPropName = '@' + forElement.forPropName;
578 			}
579 			if( ! forElement.forListName.startsWith('@')){
580 				forElement.forListName = '@' + forElement.forListName;
581 			}
582 			
583 			forElement.forIterPos = null;
584 			
585 			if(!forIterInit){
586 				
587 				//the forIteration-function creates a list of all property names for the variable 
588 				// given in the FORITER statement
589 				
590 				forIterInit = function (data) {
591 					//TODO implement this using iteration-functionality of JavaScript (-> yield)
592 					var list = new Array(); 
593 					for(var theProp in data[this.forListName]){
594 						list.push(theProp);
595 					}
596 					return list;
597 				};
598 				
599 				//creates an iterator for the property-list:
600 				forIterFunc = function (data) {
601 					var iterList = this.forInitEval(data);
602 					var iterIndex = 0;
603 					return {
604 						hasNext : function(){
605 							return iterList.length > iterIndex;
606 						},
607 						next : function(){
608 							return iterList[iterIndex++];
609 						}
610 					};
611 				};
612 			}
613 			
614 			forElement.forInitEval = forIterInit;
615 			forElement.forIterator = forIterFunc;
616 		}
617 		else {
618 			
619 			//offset within the for-expression 
620 			// (-> for locating the init-/condition-/increase-statements in case of an error)
621 			var currentOffset = '@for('.length;//<- "@for("
622 			
623 			//TODO use original parser/results instead of additional parsing pass
624 			if(forElement.forInitExpr){
625 				parsedJS = parser.parseJS(
626 						forElement.forInitExpr, 
627 						this//TODO supply/implement more accurate error-localization: this is indeed wrong, since it is not the view-defintion, but: this.definition=<view's contentFor>, then helperElement.rawResult and .dataPos contain the information, where exactly this element is located...
628 						, forElement.getStart() + this.getOffset() + currentOffset
629 				);
630 				preparedJSCode = renderer.renderJS(parsedJS.rawTemplateText, parsedJS.varReferences, true);
631 				
632 				currentOffset += forElement.forInitExpr.length;
633 			}
634 			else {
635 				// -> empty init-statement
636 				preparedJSCode = '';
637 			}
638 			try{
639 				forElement.forInitEval = createJSEvalFunction(preparedJSCode+';', 'forInitEval');
640 			} catch (err){
641 				var error = new ScriptEvalError(err, preparedJSCode,  this, forElement);
642 				//attach a dummy function that prints the error each time it is invoked:
643 				forElement.forInitEval = function(){ console.error(error.getDetails()); };
644 				//... and print the error now, too:
645 				console.error(error.getDetails());
646 			}
647 			
648 			//increase by 1 for semicolon-separator:
649 			++currentOffset;
650 			
651 			if(forElement.forConditionExpr){
652 				parsedJS = parser.parseJS(
653 						forElement.forConditionExpr, 
654 						this//TODO supply/implement more accurate error-localization: this is indeed wrong, since it is not the view-defintion, but: this.definition=<view's contentFor>, then helperElement.rawResult and .dataPos contain the information, where exactly this element is located...
655 						, forElement.getStart() + this.getOffset() + currentOffset
656 				);
657 				preparedJSCode = renderer.renderJS(parsedJS.rawTemplateText, parsedJS.varReferences, true);
658 				
659 				currentOffset += forElement.forConditionExpr.length;
660 			}
661 			else {
662 				//-> empty condition-element
663 				preparedJSCode = 'true';
664 			}
665 			try {
666 				forElement.forConditionEval = createJSEvalFunction('return ('+preparedJSCode+');', 'forConditionEval');
667 			} catch (err){
668 				var error = new ScriptEvalError(err, preparedJSCode,  this, forElement);
669 				//attach a dummy function that prints the error each time it is invoked:
670 				forElement.forConditionEval = function(){ console.error(error.getDetails()); };
671 				//... and print the error now, too:
672 				console.error(error.getDetails());
673 			}
674 
675 
676 			//increase by 1 for semicolon-separator:
677 			++currentOffset;
678 			
679 			if(forElement.forIncrementExpr){
680 				parsedJS = parser.parseJS(
681 						forElement.forIncrementExpr,
682 						this//TODO supply/implement more accurate error-localization: this is indeed wrong, since it is not the view-defintion, but: this.definition=<view's contentFor>, then helperElement.rawResult and .dataPos contain the information, where exactly this element is located...
683 						, forElement.getStart() + this.getOffset() + currentOffset
684 				);
685 				preparedJSCode = renderer.renderJS(parsedJS.rawTemplateText, parsedJS.varReferences, true);
686 			}
687 			else {
688 				//-> empty "increase" expression
689 				preparedJSCode = '';
690 			}
691 			
692 			try{
693 				forElement.forIncrementEval = createJSEvalFunction(preparedJSCode+';', 'forIncrementEval');
694 			} catch (err){
695 				var error = new ScriptEvalError(err, preparedJSCode,  this, forElement);
696 				//attach a dummy function that prints the error each time it is invoked:
697 				forElement.forIncrementEval = function(){ console.error(error.getDetails()); };
698 				//... and print the error now, too:
699 				console.error(error.getDetails());
700 			}
701 		}
702 	}
703 	
704 	//recursively parse content-fields:
705 	for(i=0, size = all.length; i < size; ++i){
706 		subContentElement = all[i];
707 		
708 		if(typeof subContentElement.scriptContent === 'string'){
709 			
710 			var isScriptStatement = subContentElement.isScriptStatement();
711 			
712 			var parsedJS; 
713 			if(isScriptStatement===true){
714 				parsedJS = parser.parseJS(
715 						subContentElement.scriptContent, 
716 						'embeddedStatementTail',
717 						this//TODO supply/implement more accurate error-localization: this is indeed wrong, since it is not the view-defintion, but: this.definition=<view's contentFor>, then helperElement.rawResult and .dataPos contain the information, where exactly this element is located...
718 						, subContentElement.getStart() + this.getOffset() + '@('.length
719 				);
720 			}
721 			else {
722 				parsedJS = parser.parseJS(
723 						subContentElement.scriptContent,
724 						this//TODO supply/implement more accurate error-localization: this is indeed wrong, since it is not the view-defintion, but: this.definition=<view's contentFor>, then helperElement.rawResult and .dataPos contain the information, where exactly this element is located...
725 						, subContentElement.getStart() + this.getOffset() + '@{'.length
726 				);
727 			}
728 			
729 			subContentElement.scriptContent = parsedJS;
730 			
731 			preparedJSCode = renderer.renderJS(parsedJS.rawTemplateText, parsedJS.varReferences); 
732 			
733 			if(isScriptStatement===true){
734 				preparedJSCode = 'return ('+preparedJSCode+');';
735 			}
736 			
737 			try{
738 				subContentElement.scriptEval = createJSEvalFunction(preparedJSCode, 'scriptEval');
739 			} catch (err){
740 				var error = new ScriptEvalError(err, preparedJSCode,  this, subContentElement);
741 				//attach a dummy function that prints the error each time it is invoked:
742 				subContentElement.scriptEval = function(){ console.error(error.getDetails()); };
743 				//... and print the error now, too:
744 				console.error(error.getDetails());
745 			}
746 			
747 			this.internalHasDynamicContent = true;
748 		}
749 		
750 		if(typeof subContentElement.content === 'string'){
751 			
752 			subContentElement.content = new ContentElement({
753 					name: SUB_ELEMENT_NAME, 
754 					content: subContentElement.content,
755 					offset: this.getOffset() + subContentElement.contentOffset
756 				}, view, parser, renderer
757 			);
758 			
759 			this.internalHasDynamicContent = this.internalHasDynamicContent || subContentElement.content.hasDynamicContent();
760 		}
761 		
762 		//IF-elements can have an additional ELSE-content field:
763 		if(subContentElement.hasElse() && typeof subContentElement.elseContent.content === 'string'){
764 			
765 			subContentElement.elseContent.content = new ContentElement({
766 					name: SUB_ELEMENT_NAME,
767 					content: subContentElement.elseContent.content,
768 					offset: this.getOffset() + subContentElement.elseContent.contentOffset
769 				}, view, parser, renderer
770 			);
771 
772 			this.internalHasDynamicContent = this.internalHasDynamicContent || subContentElement.elseContent.content.hasDynamicContent();
773 		}
774 	}
775 	
776     return this;
777 }
778 
779 
780 /**
781  * Gets the name of a {@link mmir.ContentElement} object (content, header, footer, dialogs, ...).
782  * 
783  * @function
784  * @returns {String} Name - used by yield tags in layout
785  * @public
786  */ 
787 ContentElement.prototype.getName = function(){
788     return this.name;
789 };
790 
791 /**
792  * Gets the owner for this ContentElement, i.e. the {@link mmir.View} object.
793  * 
794  * @function
795  * @returns {mmir.View} the owning View
796  * @public
797  */ 
798 ContentElement.prototype.getView = function(){
799     return this.view;
800 };
801 
802 /**
803  * Gets the controller for this ContentElement.
804  * 
805  * @function
806  * @returns {mmir.Controller} the Controller of the owning view
807  * @public
808  */ 
809 ContentElement.prototype.getController = function(){
810     return this.getView().getController();
811 };
812 
813 /**
814  * Gets the definition of a {@link mmir.ContentElement} object.
815  * 
816  * TODO remove this?
817  * 
818  * @function
819  * @returns {String} The HTML content.
820  * @public
821  */
822 ContentElement.prototype.toHtml = function(){
823 //	return this.definition;
824 	return this.toStrings().join('');
825 };
826 
827 /**
828  * Renders this object into the renderingBuffer.
829  * 
830  * @param renderingBuffer {Array} of Strings (if <code>null</code> a new buffer will be created)
831  * @param data {Any} (optional) the event data with which the rendering was invoked
832  * @returns {Array<String>} of Strings the renderingBuffer with the contents of this object added at the end
833  * 
834  * @public
835  */
836 ContentElement.prototype.toStrings = function(renderingBuffer, data){
837 
838 	return this.renderer.renderContentElement(this, data, renderingBuffer);
839 	
840 };
841 
842 /**
843  * @public
844  * @returns {String} the raw text from which this content element was parsed
845  * @see #getDefinition
846  * 
847  * @public
848  */
849 ContentElement.prototype.getRawText = function(){
850     return this.definition;
851 };
852 /**
853  * @deprecated use {@link #getRawText} instead
854  * @returns {String} the raw text from which this content element was parsed
855  * @see #getRawText
856  * 
857  * @public
858  */
859 ContentElement.prototype.getDefinition = function(){
860     return this.definition;
861 };
862 /**
863  * @returns {Number} the start position for this content Element within {@link #getRawText}
864  * @public
865  */
866 ContentElement.prototype.getStart = function(){
867     return this.start;
868 };
869 /**
870  * @returns {Number} the end position for this content Element within {@link #getRawText}
871  * @public
872  */
873 ContentElement.prototype.getEnd = function(){
874     return this.end;
875 };
876 
877 //FIXME add to storage? (this should only be relevant for parsing, which is not necessary in case of store/restore...)
878 ContentElement.prototype.getOffset = function(){
879     return this.parentOffset;
880 };
881 
882 /**
883  * @returns {Boolean} returns <code>true</code> if this ContentElement conatains dynamic content,
884  * 					i.e. if it needs to be "evaluated" for rendering 
885  * 					(otherwise, its plain text representation can be used for rendering)
886  * @public
887  */
888 ContentElement.prototype.hasDynamicContent = function(){
889     return this.internalHasDynamicContent; 
890 };
891 
892 /**
893  * create a String representation for this content element.
894  * @returns {String} the string-representation
895  * @public
896  * 
897  * @requires StorageUtils
898  * @requires RenderUtils
899  */
900 ContentElement.prototype.stringify = function(){
901 	
902 	//TODO use constants for lists
903 		
904 	//primitive-type properties:
905 	// write values 'as is' for these properties
906 	var propList = [
907 	     'name',
908    	     'definition',
909 	     'start',
910 	     'end',
911 	     'internalHasDynamicContent'
912 	];
913 	
914 	//Array-properties
915 	var arrayPropList = [
916    	     'allContentElements' //element type: ParsingResult (stringify-able)
917    	];
918 	
919 
920 //	//SPECIAL: store view by getter function initView: use the view's name view {View} -> 'viewName' {String}, 'ctrlName' {String}
921 //	
922 //	//USED BY RENDERER:
923 ////	allContentElements
924 ////	definition
925 ////	getRawText() == definition
926 ////	getController() (by view)
927 //
928 //	//SPECIAL: store renderer by getter function initRenderer
929 //	
930 //	//function properties:
931 //	var funcPropList = [
932 //   	     'initView',
933 //   	     'initRenderer'
934 //   	];
935 	
936 
937 	//function for iterating over the property-list and generating JSON-like entries in the string-buffer
938 	var appendStringified = parser_context.appendStringified;
939 	
940 	var sb = ['require("storageUtils").restoreObject({ classConstructor: "contentElement"', ','];
941 	
942 	appendStringified(this, propList, sb);
943 	
944 	//non-primitives array-properties with stringify() function:
945 	appendStringified(this, arrayPropList, sb, null, function arrayValueExtractor(name, arrayValue){
946 		
947 		var buf =['['];
948 		for(var i=0, size = arrayValue.length; i < size; ++i){
949 			buf.push(arrayValue[i].stringify());
950 			buf.push(',');
951 		}
952 		//remove last comma
953 		if(arrayValue.length > 0){
954 			buf.splice( buf.length - 1, 1);
955 		}
956 		buf.push(']');
957 		
958 		return buf.join('');
959 	});
960 	
961 	//TODO is there a better way to store the view? -> by its name and its contoller's name, and add a getter function...
962 	if(this['view']){
963 		//getter/setter function for the view/controller
964 		//  (NOTE: needs to be called before view/controller can be accessed!)
965 		sb.push( 'initView: function(){');
966 		
967 		// store view-name:
968 		sb.push( ' var viewName = ');
969 		sb.push( JSON.stringify(this.getView().getName()) );
970 		
971 		// store controller-name:
972 		sb.push( '; var ctrlName = ');
973 		sb.push( JSON.stringify(this.getController().getName()) );
974 		
975 		// ... and the getter/setter code:
976 		sb.push( '; this.view = require("presentationManager").get');
977 		sb.push(this['view'].constructor.name);//<- insert getter-name dependent on the view-type (e.g. View, Partial)
978 		sb.push('(ctrlName, viewName); this.getView = function(){return this.view;}; return this.view; },' );
979 		
980 		
981 		sb.push( 'getView: function(){ return this.initView();}');
982 		
983 		//NOTE: need to add comma in a separate entry 
984 		//      (-> in order to not break the removal method of last comma, see below)
985 		sb.push( ',' );
986 	}
987 	
988 	//TODO is there a better way to store the renderer? -> by a getter function...
989 	if(this['renderer']){
990 		//getter/setter function for the (default) renderer
991 		//  (NOTE: needs to be called before view/controller can be accessed!)
992 		sb.push( 'initRenderer: function(){');
993 		// ... and the getter/setter code:
994 		sb.push( ' this.renderer = require("renderUtils"); }' );
995 		
996 		//NOTE: need to add comma in a separate entry 
997 		//      (-> in order to not break the removal method of last comma, see below)
998 		sb.push( ',' );
999 	}
1000 	
1001 	if(this['renderer'] || this['view']){
1002 		//add initializer function
1003 		//  (NOTE: needs to be called before view/controller or renderer can be accessed!)
1004 		sb.push( 'init: function(){');
1005 		
1006 		if(this['renderer']){
1007 			sb.push( ' this.initRenderer(); ' );
1008 		}
1009 //		if(this['view']){
1010 //			sb.push( ' this.initView(); ' );
1011 //		}
1012 		sb.push( ' }' );
1013 		
1014 		//NOTE: need to add comma in a separate entry 
1015 		//      (-> in order to not break the removal method of last comma, see below)
1016 		sb.push( ',' );
1017 	}
1018 	
1019 	//if last element is a comma, remove it
1020 	if(sb[sb.length - 1] === ','){
1021 		sb.splice( sb.length - 1, 1);
1022 	}
1023 	
1024 	sb.push(' })');
1025 	return sb.join('');
1026 };
1027 
1028 return ContentElement;
1029 
1030 });//END: define(..., function(){