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 ( ['commonUtils', 'contentElement', 'storageUtils'],
 28 	//this comment is needed by jsdoc2 [copy of comment for: function View(...]
 29 	/**
 30 	 * The View class is a kind of interface-class which gives access to the methods and data of a helper (which itself belongs to a controller)<br>
 31 	 * Apart from initializing some properties, the constructor also parses the view description and looks for needed helper methods.
 32 	 * 
 33 	 * @constructs View
 34 	 * @param {Object} ctrl 
 35 	 * 			Controller instance / object
 36 	 * @param {String} name
 37 	 * 			Name of the View 
 38 	 * @param {String} definition
 39 	 * 			View description, i.e. the raw template code that will be processed.
 40 	 * 			May be empty: in this case the processed contents must be
 41 	 * 						  added manually (cf. parser.StorageUtils)
 42 	 * 
 43 	 * @requires if param definition is NOT empty: parser.RenderUtils (must be loaded beforehand via <code>require(["renderUtils"]...</code>)
 44 	 * @requires if param definition is NOT empty: parser.ParseUtils (must be loaded beforehand via <code>require(["parseUtils"]...</code>)
 45 	 * 
 46 	 * @name View
 47 	 * @class
 48 	 */
 49 	function(
 50 			commonUtils, ContentElement, parser
 51 ){
 52 /** @scope View.prototype *///for jsdoc2
 53 
 54 //set to @ignore in order to avoid doc-duplication in jsdoc3
 55 /**
 56  * @ignore
 57  * 
 58  * The View class is a kind of interface-class which gives access to the methods and data of a helper (which itself belongs to a controller)<br>
 59  * Apart from initializing some properties, the constructor also parses the view description and looks for needed helper methods.
 60  * 
 61  * @constructs View
 62  * @param {Object} ctrl 
 63  * 			Controller instance / object
 64  * @param {String} name
 65  * 			Name of the View 
 66  * @param {String} definition
 67  * 			View description, i.e. the raw template code that will be processed.
 68  * 			May be empty: in this case the processed contents must be
 69  * 						  added manually (cf. parser.StorageUtils)
 70  * 
 71  * @requires if param definition is NOT empty: parser.RenderUtils (must be loaded beforehand via <code>require(["renderUtils"]...</code>)
 72  * @requires if param definition is NOT empty: parser.ParseUtils (must be loaded beforehand via <code>require(["parseUtils"]...</code>)
 73  * 
 74  */
 75  function View(ctrl, name, definition){
 76     
 77 //	console.log("[View] '" + name + "' loaded.");
 78 	 if(definition){
 79 	    // remove HTML comments from View
 80 	    definition = definition.replace(commonUtils.regexHTMLComment, '');
 81 	}
 82 	 
 83     /**
 84      * The controller to which this view belongs.
 85      * 
 86      * @type Controller
 87      * @public
 88      */
 89     this.controller = ctrl;
 90 	
 91     /**
 92      * The description of the view in eHTML.
 93      * 
 94      * @type String
 95      * @public
 96      */
 97     this.def = definition;
 98 	
 99     /**
100      * The name of the view.
101      * 
102      * @type String
103      * @public
104      */
105     this.name = name;
106    
107 	
108     /**
109      * An array of all the views {@link mmir.ContentElement} objects.<br>
110      * 
111      * @type Array<ContentElement>
112      * @public
113      */
114     this.contentFors = new Array();
115    
116 	
117     /**
118      *
119      * An array of all names of the for the view required helper methods.
120      * 
121      * @deprecated helper methods must now explicitly called in template definition (using syntax <code>@helper(name,args)</code>)
122      * 
123      * @type Array
124      * @public
125      */
126     this.helperMethods = new Array();
127     
128     if(this.def){
129 	    
130 	    var parserUtils = require('parseUtils');
131 	    var renderUtils = require('renderUtils');
132 	    
133 	    var parseResult = parserUtils.parse(this.def, this);
134 	    
135 	    for(var i=0, size = parseResult.contentFors.length; i < size ; ++i){
136 	    	this.contentFors.push(new ContentElement(parseResult.contentFors[i], this, parserUtils, renderUtils));
137 	    }
138     }
139     
140 };
141 
142 /**
143  * Gets the definition of a view.
144  * 
145  * @function
146  * @returns {String} The view description string
147  */
148 View.prototype.getDefinition = function(){
149     return this.def;
150 };
151 
152 
153 /**
154  * Gets the name of a view. 
155  * 
156  * @function
157  * @returns {String} The name of the view
158  */
159 View.prototype.getName = function(){
160     return this.name;
161 };
162 
163 /**
164  * Gets the name of a view. 
165  * 
166  * @function
167  * @returns {Object} The controller for the view
168  */
169 View.prototype.getController = function(){
170     return this.controller;
171 };
172 
173 
174 /**
175  * Gets a specific {@link mmir.ContentElement} object by name. 
176  * 
177  * @function
178  * @param {String} name Name of the ContentElement object
179  * @returns {object} The wanted ContentElement object or null
180  */
181 View.prototype.getContentElement = function( name){
182     
183     for(var i=0, size = this.contentFors.length; i < size ; ++i){
184     	if(this.contentFors[i].getName() == name){
185     		return this.contentFors[i];/////////////////////// EARLY EXIT /////////////////////////////
186     	}
187     }
188     
189     return null;
190     
191 };
192 
193 /**
194  * Gets an array of all helper methods. 
195  * 
196  * @function
197  * @returns {Array} Array of all helper methods
198  */
199 View.prototype.getHelperMethods = function(){
200 	return this.helperMethods;
201 };
202 
203 View.prototype.stringify = function(){
204 	
205 	// "plain properties" list
206 	var propList = [
207 	     'name', 
208 	     'def'
209 //	     , 'helperMethods'//DISABLE: this field is deprecated!
210 	];
211 
212 	//Array-properties
213 	var arrayPropList = [
214    	     'contentFors' //element type: ContentElement (stringify-able)
215    	];
216 
217 	//function for iterating over the property-list and generating JSON-like entries in the string-buffer
218 	var appendStringified = parser.appendStringified;
219 	
220 	var moduleNameString = '"'+this.name+this.getController().getName()+'View"';
221 	
222 
223 	//TODO use requirejs mechanism? (NOTE there may occur timing problems for loading/registering the JS file, and querying the PresentationManager for it ...)
224 	//TODO(2) should all dependencies be added?
225 	// eg. -> [...,"presentationManager","controllerManager","view"]
226 	// and function(...,presentationManager,controllerManager,View)
227 	//... this would require to gather all nested dependencies and "apply" them here...
228 	//
229 //	var sb = ['define('+moduleNameString+', ["storageUtils"], function(parser){ return parser.restoreObject({ classConstructor: "view"', ','];
230 	
231 	var sb = ['require("storageUtils").restoreObject({ classConstructor: "view"', ','];
232 	
233 	appendStringified(this, propList, sb);
234 	
235 	//non-primitives array-properties with stringify() function:
236 	appendStringified(this, arrayPropList, sb, null, function arrayValueExtractor(name, arrayValue){
237 		
238 		var buf =['['];
239 		for(var i=0, size = arrayValue.length; i < size; ++i){
240 			buf.push(arrayValue[i].stringify());
241 			buf.push(',');
242 		}
243 		//remove last comma
244 		if(arrayValue.length > 0){
245 			buf.splice( buf.length - 1, 1);
246 		}
247 		buf.push(']');
248 		
249 		return buf.join('');
250 	});
251 	
252 	//TODO should require() be replaced by define()-dependency declaration?
253 	//     NOTE the use of require() here, assumes that the dependency has already been loaded (i.e. has already been request by some other module!)
254 	sb.push( 'initPublish: function(){ require("presentationManager").addView(this.getController(), this); }');
255 	sb.push(',');
256 	
257 	//TODO is there a better way to store the controller? -> by its contoller's name, and add a getter function...
258 	if(this['controller']){
259 		
260 		//getter/setter function for controller
261 		//  (NOTE: this init-function needs to be called before controller can be accessed!)
262 		sb.push( 'initController: function(){');
263 
264 		// store controller-name:
265 		sb.push( ' var ctrlName = ');
266 		sb.push( JSON.stringify(this.getController().getName()) );
267 		
268 		// ... and the getter/setter code:
269 		sb.push( '; this.controller = require("controllerManager").getController(ctrlName); },' );//TODO see remark about use of require() above
270 		
271 		//add initializer function
272 		//  (NOTE: needs to be called before controller or renderer can be accessed!)
273 		sb.push( 'init: function(){');
274 		sb.push( ' this.initController(); ' );
275 		sb.push( ' }' );
276 		
277 		//NOTE: need to add comma in a separate entry 
278 		//      (-> in order to not break the removal method of last comma, see below)
279 		sb.push( ',' );
280 	}
281 	
282 	//if last element is a comma, remove it
283 	if(sb[sb.length - 1] === ','){
284 		sb.splice( sb.length - 1, 1);
285 	}
286 	
287 	//TODO use requirejs mechanism? (see remark above)
288 //	sb.push(' }, true); });\n require(['//<- add require-call, so that this JS-file adds itself to the loaded dependencies in requirejs
289 //			+ moduleNameString + ']);');
290 	
291 	sb.push(' }, true, '+parser.STORAGE_FILE_FORMAT_NUMBER+');');
292 	
293 	return sb.join('');
294 };
295 
296 
297 
298 /**
299  * Gets an array of all helper methods. 
300  * 
301  * @deprecated helper methods must now explicitly called in template definition (using syntax <code>@helper(name,args)</code>)
302  * 
303  * @function
304  * @returns {Array} Array of all helper methods
305  */
306 View.prototype.getHelperMethods = function(){
307 	return this.helperMethods;
308 };
309 
310 /**
311  * Executes all helper methods that were specified / referenced in the view; with **data** as parameter.
312  * 
313  * @deprecated helper methods must now explicitly called in template definition (using syntax <code>@helper(name,args)</code>)
314  * 
315  * @function
316  * @param {Object} data Parameter to pass to the helper methods
317  */
318 View.prototype.executeHelperMethods = function(data){
319 	for(var i=0, size = this.getHelperMethods().length; i < size ; ++i){
320 		this.controller.performHelper(this.getHelperMethods()[i], data);
321     }
322 };
323 
324 return View;
325 
326 });//END: define(..., function(){
327