1 /**
  2  * TAO API core.
  3  * It provides the tools to set up the environment, 
  4  * stock the data and push them to the server 
  5  * 
  6  * @author CRP Henri Tudor - TAO Team - {@link http://www.tao.lu}
  7  * @license GPLv2  http://www.opensource.org/licenses/gpl-2.0.php
  8  * @package taoItems
  9  * @requires jquery >= 1.4.0 {@link http://www.jquery.com}
 10  */
 11 
 12 /**
 13  * The TaoStack class enables you:
 14  * - to set up the platform to communicate with 
 15  * (it's by default the TAO plateform but could be any other with the same services provided by the server side) 
 16  * - to set and get variables created by the user or defined by the platform
 17  * - to manage the source of data that the item could need
 18  * - to push the communications with the platform
 19  *  
 20  * @class TaoStack
 21  */
 22 function TaoStack (){
 23 	
 24 	
 25 	/**
 26 	 * This object describes the way the data are accessed 
 27 	 * @fieldOf TaoStack
 28 	 * @type {Object} 
 29 	 */
 30 	this.dataSource = new Object();
 31 	
 32 	//default data source environment
 33 	this.dataSource.environment = {
 34 		'type'		: 'async', 							// (manual|sync|async) 
 35 		'url' 		: root_url + '/taoDelivery/ResultDelivery/initialize',	// the url to the server [NOT for manual type] 
 36 		'params'	: { }								// the key/values to send to the server [NOT for manual type] 
 37 	};
 38 	
 39 	//default data source settings
 40 	this.dataSource.settings = {
 41 		'format'		: 'json',		//only json is supported
 42 		'method' 		: 'post',		//HTTP method (get|post) [NOT for manual type] 
 43 		'load'			: 'onInit' 		// when the source is loaded (ONLY onInit is currently supported]
 44 	};
 45 	
 46 	/**
 47 	 * This object stores the contextual  data (sent by the server on load, or on getting them)
 48 	 * @fieldOf TaoStack   
 49 	 * @type {Object}
 50 	 */
 51 	this.dataStore = new Object();
 52 	
 53 	/**
 54 	 * Initialize and setup the data source.
 55 	 * 
 56 	 * @methodOf TaoStack
 57 	 * 
 58 	 * @param {Object} environment 
 59 	 * @see TaoStack.dataSource.environment
 60 	 * 
 61 	 * @param {Object} settings 
 62 	 * @see TaoStack.dataSource.settings
 63 	 * 
 64 	 * @param {Object} source if manual data source
 65 	 */
 66 	this.initDataSource = function(environment, settings, source){
 67 		if($.inArray(environment.type, ['manual','sync','async']) > -1){
 68 			
 69 			this.dataSource.environment.type = environment.type;
 70 			
 71 			if(this.dataSource.environment.type != 'manual'){
 72 				
 73 				//set the source url
 74 				if(environment.url){
 75 					if(/(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(\#[-a-z\d_]*)?$/.test(environment.url)){	//test url format
 76 						this.dataSource.environment.url = url;		
 77 					}
 78 				}
 79 					
 80 				//and the parameters to add
 81 				if($.isPlainObject(environment.params)){	
 82 					for(key in params){
 83 						if(isScalar(environment.params[key])){
 84 							this.dataSource.environment.params[key] = environment.params[key]+''; 
 85 						}
 86 					}
 87 				}
 88 			}
 89 			
 90 			//set the source settings
 91 			if($.isPlainObject(settings)){	
 92 				if(settings.method){		//only the method is supported now
 93 					if(/^get|post$/i.test(settings.method)){
 94 						this.dataSource.settings.method = settings.method;
 95 					}
 96 				}
 97 			}
 98 			
 99 			//load the source
100 			if(this.dataSource.settings.load == 'onInit'){
101 				this.loadData(source);
102 			}
103 		}
104 	};
105 	
106 	/**
107 	 * Load the contextual data 
108 	 * @methodOf TaoStack
109 	 * @param {Object} [source] the data ONLY for the manual source
110 	 */
111 	this.loadData = function(source){
112 		
113 		/** 
114 		 * Assign the 
115 		 * @param {Object} data to the 
116 		 * @param {TaoStack} instance 
117 		 */
118 		var populateData = function(data, instance){
119 			//we filter on what we want
120 			if($.isPlainObject(data)){
121 				for(key in data){
122 					for(uriKey in URI){
123 						if(URI[uriKey] == key ||  $.inArray(key, ['token', 'localNamespace']) > -1){
124 							instance.dataStore[key] = data[key];
125 						}	
126 					}
127 				}
128 			}
129 		};
130 		
131 		if(this.dataSource.environment.type == 'manual' && source != null){		
132 			
133 			//manual loading
134 			populateData(source, this);
135 		}
136 		else{		
137 			
138 			//sync|async loading, use an ajax request 
139 			var params = this.dataSource.environment.params;
140 			var instance = this;
141 			$.ajax({
142 				'url'  		: this.dataSource.environment.url,
143 				'data' 		: params,
144 				'type' 		: this.dataSource.settings.method,
145 				'async'		: (this.dataSource.environment.type == 'async'),
146 				'dataType'  : this.dataSource.settings.format,
147 				'success' 	: function(data){
148 					//we load the data sent back by the remote source, in the FORMAT defined
149 				
150 					if(data.token){		// the token field is MANDATORY
151 						populateData(data, instance);
152 					}
153 				}
154 			});
155 		}
156 	};
157 	
158 	/**
159 	 * The push data
160 	 * @fieldOf TaoStack
161 	 * @type {Object} 
162 	 */
163 	this.dataPush = new Object();
164 	this.dataPush.environment = {
165 		'url' 		: root_url + '/taoDelivery/ResultDelivery/save',					// the url to the server
166 		'params'	: {									// the params to send to the server at each communication 
167 			'token'	: this.dataStore.token				//these parameters comes from the dataStore
168 		}
169 	};
170 	this.dataPush.settings = {
171 		'format'		: 'json',	//only json is supported
172 		'method' 		: 'post',	//HTTP method to push the data (get|post)
173 		'async'			: false,	//if the request is asynchrone 
174 		'clearAfter'	: true		//if the variables stacks are cleared once pushed
175 	};
176 
177 		
178 	/**
179 	 * Initialize and setup the push.
180 	 * 
181 	 * @methodOf TaoStack
182 	 * 
183 	 * @param {Object} environment 
184 	 * @see TaoStack#dataPush#environment
185 	 * @param {Object} settings 
186 	 * @see TaoStack#dataPush#settings
187 	 */
188 	this.initPush = function(environment, settings){
189 		
190 		if($.isPlainObject(environment)){
191 			if(environment.url){
192 				if(/(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(\#[-a-z\d_]*)?$/.test(environment.url)){	//test url
193 					this.dataPush.environment.url = environment.url;		//set url
194 				}
195 			}
196 			
197 			//ADD parameters
198 			if($.isPlainObject(environment.params)){	
199 				for(key in environment.params){
200 					if(isScalar(environment.params[key]) && !this.dataPush.environment.params[key]){	//don't edit the common params
201 						this.dataPush.environment.params[key] = environment.params[key]; 
202 					}
203 				}
204 			}
205 		}
206 		
207 		//set push settings
208 		if($.isPlainObject(settings)){	
209 			if(settings.method){
210 				if(/^get|post$/i.test(settings.method)){
211 					this.dataPush.settings.method = settings.method;
212 				}
213 			}
214 			if(settings.async === false){
215 				this.dataPush.settings.async = false;
216 			}
217 			if(settings.clearAfter === false){
218 				this.dataPush.settings.clearAfter = false;
219 			}
220 		}
221 	};
222 	
223 	/**
224 	 * push all the data in the stack to the server
225 	 * @methodOf TaoStack
226 	 */
227 	this.push = function(){
228 		
229 		var params = this.dataPush.environment.params;	//common parameters
230 		if(params.token == undefined){
231 			params.token = this.dataStore.token;
232 		}
233 		params['taoVars'] 	= new Object();
234 		params['userVars'] 	= this.userVars;
235 		
236 		for (key in this.taoVars){					//tao variables
237 			if(/^##NAMESPACE#/.test(key) && this.dataStore.localNamespace != undefined){
238 				newkey = key.replace('##NAMESPACE', this.dataStore.localNamespace);	//replace the localNamespace
239 				params['taoVars'][newkey]= this.taoVars[key];
240 			}
241 			else{
242 				params['taoVars'][key]= this.taoVars[key];
243 			}
244 		}
245 		
246 		 
247 		//push the data to the server
248 		if(this.dataPush.settings.async === true){
249 			var _instance = this;
250 			$.ajax({
251 				'url'  		: this.dataPush.environment.url,
252 				'data' 		: params,
253 				'type' 		: this.dataPush.settings.method,
254 				'async'		: true,
255 				'dataType'  : this.dataPush.settings.format,
256 				'success' 	: function(data){
257 	
258 					//the server send back the push status as
259 					//@example {"saved": true} or {"saved": false} for a json format
260 					if(data.saved == true){		
261 						//clear the stack 
262 						if(_instance.dataPush.settings.clearAfter === true){
263 							_instance.taoVars  = new Object();
264 							_instance.userVars = new Object();
265 						}
266 					}
267 				}
268 			});
269 		}
270 		else{
271 			received = $.parseJSON($.ajax({
272 				async		: false,
273 				url  		: this.dataPush.environment.url,
274 				data 		: params,
275 				type 		: this.dataPush.settings.method
276 			}).responseText);
277 			if(received.saved == true){		
278 				//clear the stack 
279 				if(this.dataPush.settings.clearAfter === true){
280 					this.taoVars  = new Object();
281 					this.userVars = new Object();
282 				}
283 			}
284 		}
285 	};
286 	
287 /* TAO Variables */
288 	
289 	/**
290 	 * The stack container
291 	 * @fieldOf TaoStack
292 	 * @type {Object} 
293 	 */
294 	this.taoVars = new Object();
295 	
296 	/**
297 	 * Get the value of a TAO varaiable identified by the key
298 	 * 
299 	 * @methodOf TaoStack
300 	 * 
301 	 * @param {String} key
302 	 * @param {boolean} [label] if you want to retrieve the label instead of the complete Object
303 	 * @returns {mixed} value (false if the key is not found)
304 	 */
305 	this.getTaoVar = function(key, label){
306 		
307 		//we check if the data are 
308 		var value =  false;
309 		if(this.taoVars[key]){			//set by the taoVar
310 			value =  this.taoVars[key];
311 		}
312 		else if (this.dataStore[key]){	//or comes from the dataStore
313 			value =  this.dataStore[key];
314 		}
315 		
316 		if($.isPlainObject(value)){
317 			if( (value['uri'] != undefined && value[URI.LABEL] != undefined && value.length == 2) || label){
318 				return value[URI.LABEL];
319 			}
320 		}
321 		return value;
322 	};
323 	
324 	/**
325 	 * The set method is restricted to scalar,
326 	 * but could be used to reference a property node
327 	 * 
328 	 * @methodOf TaoStack
329 	 * 
330 	 * @param {String} key
331 	 * @param {String|number|boolean} value
332 	 * @param {String} [property] the property uri 
333 	 */
334 	this.setTaoVar = function(key, value, property){
335 		
336 		if(isScalar(value)){
337 		
338 			var currentValue =  this.getTaoVar(key);
339 			if($.isPlainObject(currentValue)){
340 				if(property){
341 					this.taoVars[key][property] = value;
342 				}
343 				else if( value.indexOf('uri') > -1 && value.indexOf(URI.LABEL) > -1){
344 					this.taoVars[key][URI.LABEL] = value;
345 				}
346 			}
347 			else{
348 				this.taoVars[key] = value;
349 			}
350 		}
351 	};
352 	
353 /* Custom Variables */
354 	
355 	/**
356 	 * The user custom variables container 
357 	 * @fieldOf TaoStack
358 	 * @type {Object} 
359 	 */
360 	this.userVars = new Object();
361 	
362 	/**
363 	 * Get the value of a previously defined user's custom variable, identified by it's key
364 	 * 
365 	 * @methodOf TaoStack
366 	 * 
367 	 * @param {String} key
368 	 * @returns {String|number|boolean} value (false if the key is not found)
369 	 */
370 	this.getUserVar = function(key){
371 		return (this.userVars[key]) ? this.userVars[key] : false;
372 	};
373 	
374 	/**
375 	 * The item author can define it's own variables in order to keep them in the stack
376 	 * and to send them to the plateform. It's usefull to record cutom field and values
377 	 * that have not been taken in consideration but  have a real interest in the item.
378 	 * 
379 	 * @methodOf TaoStack
380 	 * 
381 	 * @param {String} key
382 	 * @param {String|number|boolean} value
383 	 */
384 	this.setUserVar = function(key, value){
385 		if(isScalar(value)){
386 			this.userVars[key] = value;
387 		}
388 	};
389 }
390 
391 
392 /**
393  * Utility function to check if a value is a scalar:
394  * (string, integer, float and boolean) 
395  * 
396  * @param {mixed} value
397  * @returns {bool} true if it's a scalar
398  */
399 function isScalar(value){
400 	return ($.inArray((typeof value).toLowerCase(), ['string', 'number', 'int', 'float', 'boolean']) > -1);
401 }
402