Source: mvc/views/layout.js


define (
	['mmirf/commonUtils','mmirf/viewConstants','mmirf/yield','mmirf/storageUtils','mmirf/contentElement','require' ],
	function(
		commonUtils, ViewConstants, YieldDeclaration, storageUtils, ContentElement, require
) {

//set to @ignore in order to avoid doc-duplication in jsdoc3
/**
 * The Layout class
 * The constructor parses the layout and divides them into containers (headerContents, bodyContents, dialogsContents).
 *
 * @class
 * @name Layout
 * @memberOf mmir.view
 * @param {String} name
 * 			Name of the Layout (usually the same name as the Layout's controller).
 * @param {String} definition
 * 			Layout description, i.e. the raw template code that will be processed.
 * 			May be empty: in this case the processed contents must be
 * 						  added manually (cf. parser.StorageUtils)
 * @param {Boolean} [remote] if the layout refers to a remote resource (DEFAULT: false)
 * @param {Boolean} [ignoreMissingBody] if parsing should ignore missing BODY tag (DEFAULT: false)
 *
 * @requires if param <code>definition</code> is NOT empty: parser.RenderUtils (must be loaded beforehand via <code>require(["mmirf/renderUtils"]...</code>)
 *
 * @requires if param <code>definition</code> is NOT empty: parser.ParseUtils (must be loaded beforehand via <code>require(["mmirf/parseUtils"]...</code>)
 */
function Layout(name, definition, remote, ignoreMissingBody){
//		console.log("[Layout] initialize '"+name+"'.");


		//FIXME MODIFICATIONS for "remote content layout object":
		/**
		 * if layout is a remote resouce (will mark script- etc. tags accordingly)
		 * @memberOf mmir.view.Layout#
		 * @member remoteaccess
		 * @type {Boolean}
		 */
		this.remoteaccess = false;
		if ((typeof remote !== 'undefined') && (remote == true)){
			this.remoteaccess = true;
		}


		/**
		 * The definition string of the layout (ehtml-format, taken from assets/www/views/layout/*.ehtml)
		 *
		 * @type Object
		 * @public
		 * @memberOf mmir.view.Layout#
		 * @member def
		 */
		this.def = definition? definition.replace(commonUtils.regexHTMLComment, '') : definition;//remove HTML comments!

		/**
		 * The name of the layout.
		 *
		 * @type String
		 * @public
		 * @memberOf mmir.view.Layout#
		 * @member name
		 */
		this.name = name;

		/**
		 * This variable holds the contents of the header part of the layout.
		 *
		 * @type String
		 * @public
		 * @deprecated unused
		 * @memberOf mmir.view.Layout#
		 * @member headerContents
		 */
		this.headerContents = '';

		/**
		 * List for extracted & parsed SCRIPT, LINK and STYLE tags
		 *
		 * @type Array<mmir.view.Layout.TagElement>
		 * @public
		 * @memberOf mmir.view.Layout#
		 * @member headerElements
		 */
		this.headerElements = [];

		/**
		 * The page / layout title
		 *
		 * Will be extracted from <em>definition</em>'s TITLE-tag, if present.
		 *
		 * @type String
		 * @public
		 * @memberOf mmir.view.Layout#
		 * @member title
		 */
		this.title = '';

		/**
		 * This variable holds the contents of the body part of the layout.
		 *
		 * @type String
		 * @public
		 * @deprecated unused
		 * @memberOf mmir.view.Layout#
		 * @member bodyContents
		 */
		this.bodyContents = "";

		/**
		 * This variable holds the contents of the dialogs part of the layout.
		 *
		 * @type String
		 * @public
		 * @memberOf mmir.view.Layout#
		 * @member dialogsContents
		 */
		this.dialogsContents = '';

		/**
		 * The (parsed) content for the body-container.
		 *
		 * @type ContentElement
		 * @public
		 * @memberOf mmir.view.Layout#
		 * @member bodyContentElement
		 */
		this.bodyContentElement = void(0);

		/**
		 * A list holding the content-references (yield declarations)
		 * for the containers (except for body):
		 * header, footer, and dialogs
		 *
		 * @type Array
		 * @public
		 * @memberOf mmir.view.Layout#
		 * @member yields
		 */
		this.yields = [];

		/**
		 * A JSON-like object containing the attributes of the BODY-tag as String values.
		 *
		 * For example, for the following BODY-tag:
		 * <pre>
		 * <body onload="handleOnLoad()" class  = 'some css-classes' >
		 * </pre>
		 * the bodyAttributes would be
		 * <pre>
		 * {
		 * 	"onload": "handleOnLoad()",
		 * 	"class": "some css-classes"
		 * }
		 * </pre>
		 *
		 * @type Object
		 * @default undefined
		 * @public
		 * @memberOf mmir.view.Layout#
		 * @member bodyAttributes
		 */
		this.bodyAttributes = void(0);

		if(this.def){

			//console.debug('Layout<constructor>: start rendering layout for "'+this.name+'"'+(remote?' (REMOTE)':'')+', RAW: '+this.def);
			var parser = typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD? __webpack_require__('mmirf/parseUtils') : require('mmirf/parseUtils');
			var parseResult = parser.parse(this.def, this);

			var renderer = typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD? __webpack_require__('mmirf/renderUtils') : require('mmirf/renderUtils');
			var renderedLayout = renderer.renderLayout(parseResult, null/*FIXME?*/);

			//DISABLED: parsing a string as HTML via jQuery etc. does not work (removes head, body,... tags):
//			var doc = new DOMParser().parseFromString(renderedLayout, "text/html");
//			if(!doc){
//				doc = document.implementation.createHTMLDocument('LAYOUT');
//			}
//			var $layout = $(doc);//$.parseHTML(renderedLayout, doc);
//
//			var headerElements = $('head', $layout);
//			for(var i=0, size = headerElements.length; i < size; ++i){
//				this.headerContents += headerElements[i].outerHTML;
//			}
//
//			var bodyElement = $('body', $layout);
//			this.bodyContents = bodyElement.html();
//			var dialogElement = $('dialogs', $layout);
//			this.dialogsContents = dialogElement.html();


			//TODO remove this (replace with real HTML parser?)

			var self = this;

			this.markerAttributeName = ViewConstants.REMOTE_RESOURCES_ATTR_NAME;
			this.markerAttributeValue = ViewConstants.REMOTE_RESOURCES_ATTR_VALUE;
			this.markerUseSingleQuotes = false;//<- set value enclosed in single-quotes (true) or double-quotes (false)

			//appends the marker attribute for "signaling"/marking that a
			//		  script/style/link-TAG was parsed&evaluated by this layout object
			var addMarkerAttribute = function(strStartOfTag){
				return strStartOfTag + ' '
						+ self.markerAttributeName + '='
						+ (self.markerUseSingleQuotes? '\'': '"')
						+ self.markerAttributeValue
						+ (self.markerUseSingleQuotes? '\'': '"');
			};

			//pure HTML:
			// (1) removed all HTML comments (via RegExpr)
			// (2) removed template comments (via parser/renderer)
			var pureHtml = renderedLayout;

			var regExprTagContent = //match one of the following (as groups):
									'(('+ 		//match as groups
										'('+		//(1) CDATA: this may contain anything, even a closing script-statement
											'<!\\[CDATA\\['+		//CDATA open: <![CDATA[
												'(.|[\\r\\n])*?'+ //allow anything within CDATA, but match non-greedily...
											'\\]\\]>'+			//...for the CDATA-closing statement: ]]>
																//   (i.e. the first time we encounter this, we stop)
										')'+				//close group for CDATA-matching

										'|[\\r\\n]'+	//(2) OR line breaks \r and \n (in any combination)
		//DISABLED (using . instead!):	'|[^<]'+	//(3) OR any symbol that is NOT <
										'|.'+		//(4) OR any symbol (REQUIRED for allowing <-symbol within script!
									')'+			//close group
									'*'+			//match this any number of times (or even none)
									'?'+			//do matching non-greedy (i.e. until the next pattern in the RegExpr can be matched the first time)

								')';			//close outer group (i.e. one group for ALL content that is matched)

			//_non-greedy_ RegExpr for
			//	* any line-break: \r or \n or a combination of them
			//	* any other character but < (less-symbol); note that this itself does not include line breaks
			var regExprTagInternal = '('+			//open group: match as a group (i.e. give access to the matched content via an index in the RegExpr object)
										'[\\r\\n]'+	//line breaks \r and \n
											'|'+			//OR
										'[^<]'+		//any symbol that is NOT <
									')'+			//close group
									'*'+			//match this any number of times (or even none)
									'?';			//do matching non-greedy (i.e. until the next pattern in the RegExpr can be matched the first time)


			//matching: <script some attributes...> some content and '<!CDATA[' with any content allowed  </script>
			//	  or: <script some attributes... />
//		var regExpScriptTag = /((<script([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm;// /((<script([\r\n]|[^<])*?>)((<!\[CDATA|[\r\n]|[^<])*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm;
			var strRegExpScriptTag = '((<script'+regExprTagInternal+'>)'+regExprTagContent+'(</script>))'+ //DETECT	"normal" script-TAG with optional content
									'|(<script'+regExprTagInternal+'/>)';								   //OR DETECT "self-closing" script TAG
			var regExpScriptTag = new RegExp(strRegExpScriptTag,'igm');

			//change to the following RegExpr (change the ones for link/style etc too)
			// from
			//		/((<script([\r\n]|[^<])*?>)(([\r\n]|[^<])*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm;
			// to
			//		/((<script([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm
			//
			// -> this RegExpr additionally
			//		* respects CDATA (<![CDTATA[ ... ]]>), with any content within its boundaries, even a "closing" </script>-TAG
			//		* allows opening < within the script-TAGs
			// LIMITATIONS: both this and the current one do not allow a < within the script-TAGS attributes, e.g.
			//				NOT: <script data-condition=" i < 5">
			//				WORKAROUND: encode the <-symbol, i.e. instead use <script data-condition=" i &lt; 5">
			//							==> use "&lt;" or "&#60;" instead of "<" in TAG attributes!

			// regExpScriptTag[0]: complete match
			// regExpScriptTag[1]: script with start and end tag (if matched)
			// regExpScriptTag[4]: TEXT content of script-tag (if present/matched)
			// regExpScriptTag[9]: self-closing script (if matched)
			var matchScriptTag = null;

			self.headerContents = '';

			var removedScriptAndLinkHmtl = new Array();
//		var matchIndex;
			while(matchScriptTag = regExpScriptTag.exec(pureHtml)){
//			matchIndex = matchScriptTag[1] ? 1 : (matchScriptTag[9]? 9 : -1);

				if(matchScriptTag[0]){//matchIndex != -1){
//				console.warn("Remote: " + self.remoteaccess);
					if (self.remoteaccess) {
//					self.headerContents += matchScriptTag[0].replace("<script", "<script loc=\"remote\"");//[matchIndex];
						self.headerContents += addMarkerAttribute('<script') + matchScriptTag[0].substring('<script'.length);
					} else {
						self.headerContents += matchScriptTag[0];
					}
					//remove script tag, and continue search
//				pureHtml = pureHtml.substring(0,matchScriptTag.index) + pureHtml.substring(matchScriptTag.index + matchScriptTag[0].length);// pureHtml.replace(matchScriptTag[matchIndex], '');

					removedScriptAndLinkHmtl.push({start: matchScriptTag.index, end: matchScriptTag.index + matchScriptTag[0].length});

					self.headerElements.push(new Layout.TagElement(matchScriptTag[2] || matchScriptTag[9], matchScriptTag[4], 'SCRIPT'));
				}
			}

			//matching: <link some attributes...> some content and '<!CDATA[' with any content allowed </link>
			//	  or: <link some attributes... />
//		var regExpLinkTag = /((<link([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/link>))|(<link([\r\n]|[^<])*?\/>)/igm;
			var strRegExpLinkTag = '((<link'+regExprTagInternal+'>)'+regExprTagContent+'(</link>))'+ //DETECT	"normal" script-TAG with optional content
										'|(<link'+regExprTagInternal+'/>)';								 //OR DETECT "self-closing" script TAG
			var regExpLinkTag = new RegExp(strRegExpLinkTag,'igm');
			// regExpLinkTag[0]: complete match
			// regExpLinkTag[1]: link with start and end tag (if matched)
			// regExpLinkTag[4]: TEXT content of link-tag (if present/matched)
			// regExpLinkTag[9]: self-closing link (if matched)
			var matchLinkTag = null;

			while(matchLinkTag = regExpLinkTag.exec(pureHtml)){
//			console.warn("Matchlinktag: " + matchLinkTag[0]);
				if(matchLinkTag[0]){
//				console.warn("Remote: " + self.remoteaccess);
					if (self.remoteaccess) {
//					self.headerContents += matchLinkTag[0].replace("<link", "<link loc=\"remote\"");
						self.headerContents += addMarkerAttribute('<link') + matchLinkTag[0].substring('<link'.length);
					} else {
						self.headerContents += matchLinkTag[0];
					}
					removedScriptAndLinkHmtl.push({start: matchLinkTag.index, end: matchLinkTag.index + matchLinkTag[0].length});

					self.headerElements.push(new Layout.TagElement(matchLinkTag[2] || matchLinkTag[9], matchLinkTag[4], 'LINK'));
				}
			}


			//matching: <style type="text/css" some attributes...> some content and '<!CDATA[' with any content allowed </style>
	//		var regExpStyleTag = /((<style([\r\n]|[^<])*?type="text\/css"([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/style>))/igm;
			var strRegExpStyleTag = '((<style'+regExprTagInternal+'>)'+regExprTagContent+'(</style>))'; //DETECT only "normal" style-TAG with content
			var regExpStyleTag = new RegExp(strRegExpStyleTag,'igm');
			// regExpStyleTag[0]: complete match
			// regExpStyleTag[1]: script with start and end tag (if matched)
			// regExpStyleTag[4]: TEXT content of style-tag (if present/matched)
			var matchStyleTag = null;

			while(matchStyleTag = regExpStyleTag.exec(pureHtml)){
	//			matchIndex = matchStyleTag[1] ? 1 : -1;

				if(matchStyleTag[0]){//matchIndex != -1){
	//				console.warn("Remote: " + self.remoteaccess);
					if (self.remoteaccess) {
	//					self.headerContents += matchStyleTag[0].replace("<style", "<style loc=\"remote\"");//[matchIndex];
						self.headerContents += addMarkerAttribute('<style') + matchStyleTag[0].substring('<style'.length);
					} else {
						self.headerContents += matchStyleTag[0];
					}
					//remove script tag, and continue search
	//				pureHtml = pureHtml.substring(0,matchStyleTag.index) + pureHtml.substring(matchStyleTag.index + matchStyleTag[0].length);// pureHtml.replace(matchStyleTag[matchIndex], '');

					removedScriptAndLinkHmtl.push({start: matchStyleTag.index, end: matchStyleTag.index + matchStyleTag[0].length});

					self.headerElements.push(new Layout.TagElement(matchStyleTag[2], matchStyleTag[4], 'STYLE'));
				}
			}

			//only need to "process" removed script/link tags, if some were found:
			if(removedScriptAndLinkHmtl.length > 0){

				removedScriptAndLinkHmtl.sort(function(a,b){
					return a.start - b.start;
				});

				var cleanedHtml = new Array();
				var remPos = 0;
				var removalElement = removedScriptAndLinkHmtl[0];

				for(var i=0, size = removedScriptAndLinkHmtl.length; i < size; ++i){
					removalElement = removedScriptAndLinkHmtl[i];

					var text = pureHtml.substring(remPos, removalElement.start);
					cleanedHtml.push(text);

					remPos = removalElement.end;
				}
				//add rest of the HTML if necessary
				if(removalElement.end < pureHtml.length){
					cleanedHtml.push(pureHtml.substring(removalElement.end));
				}
				//replace HTML with the removed/clean version:
				pureHtml = cleanedHtml.join('');
			}

			//TODO this is needed my be of interest for further processing
			// (for processing partial HTML responses)
			//-> should this be part of the framework? (i.e. "public" property for Layout; TODO add to stringify()?)
			this._processedDef = pureHtml;

			//FIXME reg-expr does not detect body-TAG, if body has no content (i.e. body="<body></body>")
			var regExpBodyTag = /(<body([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/body>)/igm;
			// matchBodyTag[0]: complete match
			// matchBodyTag[1]: body start tag
			// matchBodyTag[2]: last CHAR within body start tag, before closing, e.g. "...lskdjf>" -> "f"
			// matchBodyTag[3]: body text content
			// matchBodyTag[4]: last CHAR within body text content, before closing, e.g. "...lsk</body>" -> "k"
			// matchBodyTag[5]: body end tag
			var matchBodyTag = regExpBodyTag.exec(pureHtml);

			self.bodyContents = '';

			if(matchBodyTag && matchBodyTag[3]){
				self.bodyContents += matchBodyTag[3];
			}
			else if(!ignoreMissingBody) {
				//TODO throw error?
				console.error('Layout.<constructor>: Layout template does not contain a <body> element!');
			}


			//TEST: experimental -> "remember" attributes of body tag
			//NOTE this assumes that matchBodyTag-RegExpr starts with: /(<body([\r\n]|.)*?>) ...
			if(matchBodyTag && matchBodyTag[1] && matchBodyTag[1].length > '<body>'.length){

//				//NOTE: 1st case should really never occur.
//				var reTagSelfClose = /\/>$/;
//				var bodyAttrEnd = reTagSelfClose.test(matchBodyTag[1])? matchBodyTag[1].length-2 : matchBodyTag[1].length-1;
//				var bodyAttr = '<div ' + matchBodyTag[1].substring('<body'.length, bodyAttrEnd) + '</div>';
//				bodyAttr = jQuery(bodyAttr);

				self.bodyAttributes = Layout.getTagAttr(matchBodyTag[1]);
			}

			//Extract title-tag
			var regExpTitleTag = /(<title([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/title>)/igm;
			// matchTitleTag[0]: complete match
			// matchTitleTag[1]: title start tag
			// matchTitleTag[2]: last CHAR within title start tag, before closing, e.g. "...lskdjf>" -> "f"
			// matchTitleTag[3]: title text content
			// matchTitleTag[4]: last CHAR within title text content, before closing, e.g. "...lsk</title>" -> "k"
			// matchTitleTag[5]: title end tag
			var matchTitleTag = regExpTitleTag.exec(pureHtml);

			if(matchTitleTag && matchTitleTag[3]){
				self.title = matchTitleTag[3];
			}

			var regExpDialogsTag = /(<dialogs([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/dialogs>)/igm;
			// matchDialogsTag[0]: complete match
			// matchDialogsTag[1]: dialogs start tag
			// matchDialogsTag[2]: last CHAR within dialogs start tag, before closing, e.g. "...lskdjf>" -> "f"
			// matchDialogsTag[3]: dialogs text content
			// matchDialogsTag[4]: last CHAR within dialogs text content, before closing, e.g. "...lsk</dialogs>" -> "k"
			// matchDialogsTag[5]: dialogs end tag
			var matchDialogsTag = regExpDialogsTag.exec(pureHtml);

			self.dialogsContents = '';
			if(matchDialogsTag && matchDialogsTag[3]){
				self.dialogsContents += matchDialogsTag[3];
			}

			var parseBodyResult = new ContentElement({name: this.name, content: this.bodyContents}, this, parser, renderer);// parser.parse(this.bodyContents, this);
//			for(var i=0, size = parseBodyResult.yields.length; i < size ; ++i){
//				this.yields.push(new YieldDeclaration(parseBodyResult.yields[i], ViewConstants.CONTENT_AREA_BODY));
//			}
//			parseBodyResult.yields = void(0);
			var all = parseBodyResult.allContentElements.concat(parseBodyResult.yields);
			all.sort(function(parsedElem1, parsedElem2){
				return parsedElem1.getStart() - parsedElem2.getStart();
			});
			parseBodyResult.allContentElements = all;
			parseBodyResult.getController = function(){ return {//FIXME
				name: null,
				getName: function(){
					return this.name;
				}
			}};
			this.bodyContentElement = parseBodyResult;

			var parseDialogResult = parser.parse(this.dialogsContents, this);
			for(var i=0, size = parseDialogResult.yields.length; i < size ; ++i){
				this.yields.push(new YieldDeclaration(parseDialogResult.yields[i], ViewConstants.CONTENT_AREA_DIALOGS));
			}

		}//END: if(this.def)

	}//END: Layout()


	/**
	 * HELPER: extracts TAG attributes into an JSON-object
	 *
	 * @memberOf mmir.view.Layout
	 * @function getTagAttr
	 * @private
	 * @static
	 *
	 * @param {String} str
	 * 			the start-TAG as String
	 * @param {Object} [target] OPTIONAL
	 * 			the target-object to which the extracted attributes will be attached
	 * 			if omitted, a new, empty object will be created
	 *
	 * @return {Object} the object with the extracted attributes as properties
	 * 					(if <em>target</em> was provided, then this is the <em>target</em> object)
	 *
	 * @example
	 * e.g. <body onload="on_load();" class = 'biggestFont'>
	 * -->
	 * {"onload": "on_load()", "class": "biggestFont"}
	 *
	 */
	Layout.getTagAttr = function(str, target){

		//RegExp for:
		// name = "..."
		//or
		// name = '...'
		//
		//NOTE: the RegExp does not extract "single properties", as e.g. <tag required>
		//	  ... instead, for extraction, they must be specified as follows: <tag required="...">
		//NOTE: values MUST be enclosed in double-quotes or quotes, "quote-less" attribute values cannot be extracted!
		//	  ... e.g. NOT: <tag name=value>, instead: <tag name="value"> or <tag name='value'>
		//NOTE: the RegExp also detects escaped double-quotes and quotes respectively
		var regExpr = /\s+([^=]*?)\s*=\s*(("((\\"|[^"])*)")|('((\\'|[^'])*)'))/igm;
		var result = target || {};
		var match;

		while(match = regExpr.exec(str)){

			if(match[4]){
				result[match[1]] = match[4];
			}
			else if(match[7]){
				result[match[1]] = match[7];
			}
		}
		return result;
	};

	/**
	 * HELPER class: extract raw TAG Strings into a property-object
	 *
	 * @public
	 * @constructor
	 * @memberOf mmir.view
	 * @param {String} tag
	 * 			the start TAG
	 * @param {String} content
	 * 			the TEXT content of the TAG (may be empty)
	 * @param {String} tagType
	 *  		the TAG type, e.g. "SCRIPT"
	 *
	 * @returns {mmir.view.Layout.TagElement}
	 *
	 * 		prop {String} tagName: the TAG type, e.g. "SCRIPT"
	 * 		prop {String} textContent: the TEXT content of the TAG (may be an empty String)
	 * 		prop EXTRACTED ATTRIBUTES: the extracted attributes form the start-TAG
	 *
	 * 		func {String} attr(STRING name): returns the attribute-value for name (may be undefined)
	 * 		func {String} html(): returns the TEXT content of the TAG (may be an empty String)
	 *
	 * 		func {Boolean} isScript(): returns TRUE if tagType is SCRIPT
	 * 		func {Boolean} isStyle():  returns TRUE if tagType is STYLE
	 * 		func {Boolean} isLink():   returns TRUE if tagType is LINK
	 */
	Layout.TagElement = function TagElement(tag, content, tagType){
		/**  the TAG type, e.g. "SCRIPT" */
		this.tagName = tagType;
		/** the TEXT content of the TAG (may be an empty String) */
		this.textContent = content || '';

		var tis = this;
//		tis.attr = function(name){
//			return this[name];
//		};
//		tis.html = function(){
//			return this.textContent;
//		};
//		tis.isScript = function(){return this.tagName === 'SCRIPT';};
//		tis.isStyle  = function(){return this.tagName === 'STYLE'; };
//		tis.isLink   = function(){return this.tagName === 'LINK';  };

		//extract attributes as properties from TAG string:
		Layout.getTagAttr(tag, tis);
		return tis;
	};

	/**
	 * Prototype for TagElement
	 *
	 * 		func {String} attr(STRING name): returns the attribute-value for name (may be undefined)
	 * 		func {String} html(): returns the TEXT content of the TAG (may be an empty String)
	 *
	 * 		func {Boolean} isScript(): returns TRUE if tagType is SCRIPT
	 * 		func {Boolean} isStyle():  returns TRUE if tagType is STYLE
	 * 		func {Boolean} isLink():   returns TRUE if tagType is LINK
	 *
	 * @lends mmir.view.Layout.TagElement
	 */
	Layout.TagElement.prototype = {
		/**
		 * @param {String} name the attribute name
		 * @returns {any} the attribute-value for name (may be undefined)
		 */
		attr: function(name){
			return this[name];
		},
		/**  @returns {String} the TEXT content of the TAG (may be an empty String)  */
		html: function(){
			return this.textContent;
		},
		/** @returns {Boolean} returns TRUE if tagType is SCRIPT */
		isScript: function(){return this.tagName === 'SCRIPT';},
		/** @returns {Boolean} returns TRUE if tagType is STYLE */
		isStyle: function(){return this.tagName === 'STYLE'; },
		/** @returns {Boolean} returns TRUE if tagType is LINK */
		isLink: function(){return this.tagName === 'LINK';  }
	};

	/**
	 * This methods returns an associative array holding the contents of the different containers: header, body, footer and dialogs.
	 *
	 * @function
	 * @returns {Array} An associative array holding the contents of the different containers: header, body, footer and dialogs
	 * @public
	 */
	Layout.prototype.getYields = function(){
		return this.yields;
	};

	/**
	 * This methods returns the contents of the header part of the layout.
	 *
	 * @function getHeaderContents
	 * @returns {String} The contents of the header part of the layout
	 * @public
	 * @memberOf mmir.view.Layout#
	 */
	Layout.prototype.getHeaderContents = function(){
		return this.headerContents;
	};

	/**
	 * This methods returns the contents of the dialog part of the layout.
	 *
	 * @function getDialogsContents
	 * @returns {String} The contents of the dialog part of the layout
	 * @public
	 * @memberOf mmir.view.Layout#
	 */
	Layout.prototype.getDialogsContents = function(){
		return this.dialogsContents;
	};

	/**
	 * This methods returns the contents of the body part of the layout.
	 *
	 * @function getBodyContents
	 * @returns {String} The contents of the body part of the layout
	 * @public
	 * @memberOf mmir.view.Layout#
	 */
	Layout.prototype.getBodyContents = function(){
		return this.bodyContents;
	};

	/**
	 * Gets the name of the layout.
	 *
	 * @function getName
	 * @returns {String} The name of the layout.
	 * @public
	 * @memberOf mmir.view.Layout#
	 */
	Layout.prototype.getName = function(){
		return this.name;
	};

	/**
	 * HELPER: add prototype functions of Layout.TagElement to the #headerElements
	 *
	 * @function _extHeaderElements
	 * @protected
	 * @memberOf mmir.view.Layout#
	 */
	Layout.prototype._extHeaderElements = function(){
		var prot = Layout.TagElement.prototype;
		var funcs = Object.keys(prot);
		var len = funcs.length -1;
		var i, j, elem, fname;
		for(i = this.headerElements.length-1; i >= 0; --i){
			elem = this.headerElements[i];
			for(j = len; j >= 0; --j){
				fname = funcs[j];
				elem[fname] = prot[fname];
			}
		}
	};

/**
 * @function stringify
 * @memberOf mmir.view.Layout#
 *
 * @param  {Boolean} [disableStrictMode] OPTIONAL 	disable JavaScript strict mode in the generated view code
 * @return {String} stringified representation of the layout
 */
Layout.prototype.stringify = function(disableStrictMode){

	// "plain properties" list
	var propList = [
		'name',
		'remoteaccess',
		'def',
		'headerElements',
		'headerContents',
		'title',
//		 'bodyContents',		//DISABLED: store in this.bodyContentElement.definition now
		'dialogsContents',
		'markerAttributeName',
		'markerAttributeValue',
		'markerUseSingleQuotes',
		'bodyAttributes'
	];

	//stringify-able properties
	var stringifyPropList = [
		'bodyContentElement' //element type: ContentElement (stringify-able)
	];

	//complex Array-properties
	var arrayPropList = [
		'yields' //element type: YieldDeclaration (stringify-able)
	];

	//function for iterating over the property-list and generating JSON-like entries in the string-buffer
	var appendStringified = storageUtils.appendStringified;

	var moduleNameString = '"'+this.name+'Layout"';

	var sb = [storageUtils.getCodeWrapPrefix(disableStrictMode), 'require("mmirf/storageUtils").restoreObject({ classConstructor: "mmirf/layout"', ','];

	appendStringified(this, propList, sb);

	//NOTE the use of require() here, assumes that the dependency has already been loaded (i.e. has already been request by some other module!)
	sb.push( 'initPublish: function(){ this._extHeaderElements(); this.bodyContents=this.bodyContentElement.definition; require("mmirf/presentationManager").addLayout(this); }');
	sb.push(',');

	//non-primitives properties with stringify() function:
	appendStringified(this, stringifyPropList, sb, null, function stringifyValueExtractor(name, obj){

		return obj.stringify(disableStrictMode);
	});

	//non-primitives array-properties with stringify() function:
	appendStringified(this, arrayPropList, sb, null, function arrayValueExtractor(name, arrayValue){

		var buf =['['];
		for(var i=0, size = arrayValue.length; i < size; ++i){
			buf.push(arrayValue[i].stringify(disableStrictMode));
			buf.push(',');
		}
		//remove last comma
		if(arrayValue.length > 0){
			buf.splice( buf.length - 1, 1);
		}
		buf.push(']');

		return buf.join('');
	});


	//if last element is a comma, remove it
	if(sb[sb.length - 1] === ','){
		sb.splice( sb.length - 1, 1);
	}

	//TODO use requirejs mechanism? (see remark above)
//	sb.push(' }, true); });\n require(['//<- add require-call, so that this JS-file adds itself to the loaded dependencies in requirejs
//			+ moduleNameString + ']);');

	sb.push(' }, true, '+storageUtils.STORAGE_FILE_FORMAT_NUMBER+');');
	sb.push(storageUtils.STORAGE_CODE_WRAP_SUFFIX);

	return sb.join('');
};

return Layout;

});