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));