1 /**
  2  * @author Pavel Yaschenko
  3  */
  4 
  5 (function($, richfaces, jsf) {
  6 	
  7 	/**
  8 	  * RichFaces Ajax container 
  9 	  * @class
 10 	  * @memberOf RichFaces
 11 	  * @static
 12 	  * @name ajax
 13 	  * */
 14 	richfaces.ajax = richfaces.ajax || {};
 15 	
 16 	/** 
 17 	  * JSF 2.0 original method that sends an asynchronous ajax request to the server
 18 	  * see jsf.ajax.request method for parameter's description
 19 	  * @function
 20 	  * @name RichFaces.ajax.jsfRequest
 21 	  *
 22 	  * */
 23 	richfaces.ajax.jsfRequest = jsf.ajax.request;
 24 	
 25 	/** 
 26 	  * RichFaces wrapper function of JSF 2.0 original method jsf.ajax.request
 27 	  * @function
 28 	  * @name jsf.ajax.request
 29 	  *
 30 	  * @param {string|DOMElement} source - The DOM element or an id that triggered this ajax request
 31 	  * @param {object} [event] - The DOM event that triggered this ajax request
 32 	  * @param {object} [options] - The set name/value pairs that can be sent as request parameters to control client and/or server side request processing
 33 	  * */
 34 	jsf.ajax.request = function request(source, event, options) {
 35 		richfaces.queue.push(source, event, options);
 36 	};
 37 	
 38 	var QUEUE_MODE_PULL = 'pull';
 39 	var QUEUE_MODE_PUSH = 'push';
 40 	var QUEUE_MODE = QUEUE_MODE_PULL;
 41 	var DEFAULT_QUEUE_ID = "org.richfaces.queue.global";
 42 	
 43 	/**
 44 	  * RichFaces Queue API container 
 45 	  * @class
 46 	  * @memberOf RichFaces
 47 	  * @static
 48 	  * @name queue
 49 	  * */
 50 	richfaces.queue = (function(){
 51 		
 52 		var defaultQueueOptions = {};
 53 		//defaultQueueOptions[DEFAULT_QUEUE_ID] = {requestDelay:0, ignoreDupResponse:false, timeout:0};
 54 		var eventHandlers = {};
 55 
 56 		var QueueEntry = function(queue, source, event, options) {
 57 				this.queue = queue;
 58 				this.source = source;
 59 				this.options = $.extend({}, options || {});
 60 				this.queueOptions = {}
 61 				var id;
 62 				
 63 				// find default options for QueueEntry
 64 				if (this.options.queueId) {
 65 					if (defaultQueueOptions[this.options.queueId]) {
 66 						id = this.options.queueId;
 67 					}
 68 					delete this.options.queueId;
 69 				} else {
 70 					var element = richfaces.getDomElement(source);
 71 					var form;
 72 					if (element) {
 73 						element = $(element).closest("form");
 74 						if (element.length>0) {
 75 							form = element.get(0);
 76 						}
 77 					}
 78 					if (form && form.id && defaultQueueOptions[form.id]) {
 79 						id = form.id;
 80 					} else {
 81 						id = DEFAULT_QUEUE_ID;
 82 					}
 83 				}
 84 				if (id) {
 85 					this.queueOptions = defaultQueueOptions[id] || {};
 86 					if (this.queueOptions.queueId) {
 87 						this.queueOptions = $.extend({}, (defaultQueueOptions[this.queueOptions.queueId]||{}), this.queueOptions);
 88 					} else {
 89 						// TODO: clean duplicated code
 90 						var element = richfaces.getDomElement(source);
 91 						var form;
 92 						if (element) {
 93 							element = $(element).closest("form");
 94 							if (element.length>0) {
 95 								form = element.get(0);
 96 							}
 97 						}
 98 						if (form && form.id && defaultQueueOptions[form.id]) {
 99 							id = form.id;
100 						} else {
101 							id = DEFAULT_QUEUE_ID;
102 						}
103 						if (id) {
104 							this.queueOptions = $.extend({}, (defaultQueueOptions[id]||{}), this.queueOptions);
105 						}
106 					}
107 				}
108 
109 				// copy of event should be created otherwise IE will fail
110 				this.event = $.extend({}, event);
111 		
112 				//requestGroupId is mutable, thus we need special field for it
113 				this.requestGroupId = this.queueOptions.requestGroupId;
114 				this.eventsCount = 1;
115 			};
116 	
117 			$.extend(QueueEntry.prototype, {
118 				// now unused functions: isIgnoreDupResponses, ondrop, clearEntry
119 				isIgnoreDupResponses: function() {
120 					return this.queueOptions.ignoreDupResponses;
121 				},
122 				
123 				getRequestGroupId: function() {
124 					return this.requestGroupId;
125 				},
126 				
127 				setRequestGroupId: function(id) {
128 					this.requestGroupId = id;
129 				},
130 				
131 				resetRequestGroupId: function() {
132 					this.requestGroupId = undefined;					
133 				},
134 				
135 				setReadyToSubmit: function(isReady) {
136 					this.readyToSubmit = isReady;
137 				},
138 				
139 				getReadyToSubmit: function() {
140 					return this.readyToSubmit;
141 				},
142 				
143 				ondrop: function() {
144 					var callback = this.queueOptions.onqueuerequestdrop;
145 					if (callback) {
146 						callback.call(this.queue, this.source, this.options, this.event);
147 					}
148 				},
149 				
150 				onRequestDelayPassed: function() {
151 					this.readyToSubmit = true;
152 					submitFirstEntry.call(this.queue);
153 				},
154 				
155 				startTimer: function() {
156 					var delay = this.queueOptions.requestDelay;
157 					if (typeof delay!="number") {
158 						delay = this.queueOptions.requestDelay || 0;
159 					}
160 					
161 					log.debug("Queue will wait " + (delay || 0) + "ms before submit");
162 		
163 					if (delay) {
164 						var _this = this;
165 						this.timer = window.setTimeout(function() {
166 							try {
167 								_this.onRequestDelayPassed();
168 							} finally {
169 								_this.timer = undefined;
170 								_this = undefined;
171 							}
172 						}, delay);
173 					} else {
174 						this.onRequestDelayPassed();
175 					}
176 				},
177 				
178 				stopTimer: function() {
179 					if (this.timer) {
180 						window.clearTimeout(this.timer);
181 						this.timer = undefined;
182 					}
183 				},
184 				
185 				clearEntry: function() { //???
186 					this.stopTimer();
187 					if (this.request) {
188 						this.request.shouldNotifyQueue = false;
189 						this.request = undefined;
190 					}
191 				},
192 				
193 				getEventsCount: function() {
194 					return this.eventsCount;
195 				},
196 				
197 				setEventsCount: function(newCount) {
198 					this.eventsCount = newCount;
199 			}
200 		});
201 		
202 		// TODO: add this two variables to richfaces and report bug to jsf about constants
203 		var JSF_EVENT_TYPE = 'event';
204 		var JSF_EVENT_SUCCESS = 'success';
205 		var JSF_EVENT_COMPLETE = 'complete';
206 		
207 		var log = richfaces.log;
208 		var items = [];
209 		var lastRequestedEntry;	
210 		
211 		//TODO: instance of this function will be created for each queue
212 		var onError = function (data) {
213 			log.debug("richfaces.queue: ajax submit error");
214 			lastRequestedEntry = null;
215 			//TODO: what if somebody is going to clear queue on error?
216 			submitFirstEntry();
217 		};
218 		
219 		var onComplete = function (data) {
220 			if (data.type == JSF_EVENT_TYPE && data.status == JSF_EVENT_SUCCESS) { // or JSF_EVENT_COMPLETE will be rather
221 				log.debug("richfaces.queue: ajax submit successfull");
222 				lastRequestedEntry = null;
223 				submitFirstEntry();
224 			}
225 		};		
226 		
227 		jsf.ajax.addOnEvent(onComplete);
228 		jsf.ajax.addOnError(onError);
229 		
230 		var submitFirstEntry = function() {
231 			if (QUEUE_MODE == QUEUE_MODE_PULL && lastRequestedEntry) {
232 				log.debug("richfaces.queue: Waiting for previous submit results");
233 				return;
234 			}
235 			if (isEmpty()) {
236 				log.debug("richfaces.queue: Nothing to submit");
237 				return;
238 			}
239 			var entry;
240 			if (items[0].getReadyToSubmit()) {
241 				entry = lastRequestedEntry = items.shift();
242 				log.debug("richfaces.queue: will submit request NOW");
243 				var o = lastRequestedEntry.options;
244 				o["AJAX:EVENTS_COUNT"] = lastRequestedEntry.eventsCount;
245 				richfaces.ajax.jsfRequest(lastRequestedEntry.source, lastRequestedEntry.event, o);
246 				
247 				// call event handlers
248 				if (o.queueonsubmit) {
249 					o.queueonsubmit.call(entry);
250 				}
251 				callEventHandler("onrequestdequeue", entry);
252 			}
253 		};
254 		
255 		var isEmpty = function() {
256 			return (getSize()==0)
257 		};
258 		var getSize = function() {
259 			return items.length;
260 		};
261 		
262 		var getLastEntry = function () {
263 			var lastIdx = items.length - 1;
264 			return items[lastIdx];
265 		};
266 		
267 		var updateLastEntry = function (entry) {
268 			var lastIdx = items.length - 1;
269 			items[lastIdx] = entry;
270 		};
271 		
272 		var callEventHandler = function (handlerName, entry) {
273 			handler = entry.queueOptions[handlerName];
274 			if (handler) {
275 				// what is a context should be??
276 				handler.call(null, entry);
277 			}
278 			var opts, handler2;
279 			if (entry.queueOptions.queueId && 
280 				(opts=defaultQueueOptions[entry.queueOptions.queueId]) &&
281 				(handler2=opts[handlerName])
282 				&& handler2!=handler) {
283 				// the same about context
284 				handler2.call(null, entry);
285 			}
286 		}
287 		
288 		var pushEntry = function (entry) {
289 			items.push(entry);
290 			log.debug("New request added to queue. Queue requestGroupId changed to " + entry.getRequestGroupId());
291 			// call event handlers
292 			callEventHandler("onrequestqueue", entry);
293 		}
294 
295 		return {
296 			/**
297 			  * @constant
298 			  * @name RichFaces.queue.DEFAULT_QUEUE_ID
299 			  * @type string
300 			  * */
301 			DEFAULT_QUEUE_ID: DEFAULT_QUEUE_ID,
302 			
303 			/** 
304 			  * Get current queue size
305 			  * @function
306 			  * @name RichFaces.queue.getSize
307 			  *
308 			  * @return {number} size of items in the queue
309 			  * */
310 			getSize: getSize,
311 			
312 			/** 
313 			  * Check if queue is empty
314 			  * @function
315 			  * @name RichFaces.queue.isEmpty
316 			  *
317 			  * @return {boolean} returns true if queue is empty
318 			  * */
319 			isEmpty: isEmpty,
320 			
321 			/** 
322 			  * Extract and submit first QueueEntry in the queue if QueueEntry is ready to submit
323 			  * @function
324 			  * @name RichFaces.queue.submitFirst
325 			  * */
326 			submitFirst: function () {
327 				if (!isEmpty()) {
328 					var entry = items[0];
329 					entry.stopTimer();
330 					entry.setReadyToSubmit(true);
331 					submitFirstEntry();
332 				}
333 			},
334 			
335 			/** 
336 			  * Create and push QueueEntry to the queue for ajax requests
337 			  * @function
338 			  * @name RichFaces.queue.push
339 			  *
340 			  * @param {string|DOMElement} source - The DOM element or an id that triggered this ajax request
341 			  * @param {object} [event] - The DOM event that triggered this ajax request
342 			  * @param {object} [options] - The set name/value pairs that can be sent as request parameters to control client and/or server side request processing
343 			  * */
344 			push: function (source, event, options) {
345 				var entry = new QueueEntry(this, source, event, options);
346 				var requestGroupId = entry.getRequestGroupId();
347 				
348 				var lastEntry = getLastEntry();
349 				
350 				if (lastEntry) {
351 					if (lastEntry.getRequestGroupId() == requestGroupId) {
352 						log.debug("Similar request currently in queue");
353 
354 						log.debug("Combine similar requests and reset timer");
355 						
356 						lastEntry.stopTimer();
357 						entry.setEventsCount(lastEntry.getEventsCount() + 1);
358 						
359 						updateLastEntry(entry);
360 						callEventHandler("onrequestqueue", entry);
361 					} else {
362 						log.debug("Last queue entry is not the last anymore. Stopping requestDelay timer and marking entry as ready for submission")
363 						
364 						lastEntry.stopTimer();
365 						lastEntry.resetRequestGroupId();
366 						lastEntry.setReadyToSubmit(true);
367 						
368 						pushEntry(entry);
369 						submitFirstEntry();
370 					}
371 				} else {
372 					pushEntry(entry);
373 				}
374 
375 				// start timer
376 				entry.startTimer();
377 				
378 			},
379 			
380 			/** 
381 			  * Remove all QueueEntry from the queue
382 			  * @function
383 			  * @name RichFaces.queue.clear
384 			  * */
385 			clear: function () {
386 				var lastEntry = getLastEntry();
387 				if (lastEntry) {
388 					lastEntry.stopTimer();
389 				}
390 				items = [];
391 			},
392 			
393 			/** 
394 			  * Set queue default options
395 			  * @function
396 			  * @name RichFaces.queue.setQueueOptions
397 			  *
398 			  * @param {string||object} [id] - Queue id for storing options or hash with options for multiple options set
399 			  * @param {object} options - Queue options object
400 			  * */
401 			setQueueOptions: function (id, options) {
402 				var tid = typeof id;
403 				if (tid == "string") {
404 					// add named queue options
405 					if (defaultQueueOptions[id]) {
406 						throw "Queue already registered";
407 					} else {
408 						defaultQueueOptions[id] = options;
409 					}
410 				} else if (tid == "object"){
411 					// first parameter is hash with queue names and options
412 					$.extend(defaultQueueOptions, id);
413 				}
414 				return richfaces.queue;
415 			},
416 			
417 			getQueueOptions: function (id) {
418 				return defaultQueueOptions[id] || {};
419 			}
420 		}
421 	}());
422 }(jQuery, RichFaces, jsf));