1 /* 2 * JBoss, Home of Professional Open Source 3 * Copyright 2013, Red Hat, Inc. and individual contributors 4 * by the @authors tag. See the copyright.txt in the distribution for a 5 * full listing of individual contributors. 6 * 7 * This is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU Lesser General Public License as 9 * published by the Free Software Foundation; either version 2.1 of 10 * the License, or (at your option) any later version. 11 * 12 * This software is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this software; if not, write to the Free 19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 21 */ 22 23 /** 24 * @author Pavel Yaschenko 25 */ 26 27 (function($, rf, jsf) { 28 29 /** 30 * RichFaces Ajax container 31 * @class 32 * @memberOf RichFaces 33 * @static 34 * @name ajaxContainer 35 * */ 36 rf.ajaxContainer = rf.ajaxContainer || {}; 37 38 if (rf.ajaxContainer.jsfRequest) { 39 return; 40 } 41 42 /** 43 * JSF 2.0 original method that sends an asynchronous ajax request to the server 44 * see jsf.ajax.request method for parameter's description 45 * @function 46 * @name RichFaces.ajaxContainer.jsfRequest 47 * 48 * */ 49 rf.ajaxContainer.jsfRequest = jsf.ajax.request; 50 51 /** 52 * RichFaces wrapper function of JSF 2.0 original method jsf.ajax.request 53 * @function 54 * @name jsf.ajax.request 55 * 56 * @param {string|DOMElement} source - The DOM element or an id that triggered this ajax request 57 * @param {object} [event] - The DOM event that triggered this ajax request 58 * @param {object} [options] - The set name/value pairs that can be sent as request parameters to control client and/or server side request processing 59 * */ 60 jsf.ajax.request = function(source, event, options) { 61 rf.queue.push(source, event, options); 62 }; 63 64 rf.ajaxContainer.jsfResponse = jsf.ajax.response; 65 66 rf.ajaxContainer.isIgnoreResponse = function() { 67 return rf.queue.isIgnoreResponse(); 68 }; 69 70 71 jsf.ajax.response = function(request, context) { 72 rf.queue.response(request, context); 73 }; 74 75 var QUEUE_MODE_PULL = 'pull'; 76 var QUEUE_MODE_PUSH = 'push'; 77 var QUEUE_MODE = QUEUE_MODE_PULL; 78 var DEFAULT_QUEUE_ID = "org.richfaces.queue.global"; 79 80 /** 81 * RichFaces Queue API container 82 * @class 83 * @memberOf RichFaces 84 * @static 85 * @name queue 86 * */ 87 rf.queue = (function() { 88 89 var defaultQueueOptions = {}; 90 //defaultQueueOptions[DEFAULT_QUEUE_ID] = {requestDelay:0, ignoreDupResponse:false, timeout:0}; 91 var eventHandlers = {}; 92 93 var QueueEntry = function(queue, source, event, options) { 94 this.queue = queue; 95 this.source = source; 96 this.options = $.extend({}, options || {}); 97 this.queueOptions = {} 98 var id; 99 100 // find default options for QueueEntry 101 if (this.options.queueId) { 102 if (defaultQueueOptions[this.options.queueId]) { 103 id = this.options.queueId; 104 } 105 delete this.options.queueId; 106 } else { 107 var element = rf.getDomElement(source); 108 var form; 109 if (element) { 110 element = $(element).closest("form"); 111 if (element.length > 0) { 112 form = element.get(0); 113 } 114 } 115 if (form && form.id && defaultQueueOptions[form.id]) { 116 id = form.id; 117 } else { 118 id = DEFAULT_QUEUE_ID; 119 } 120 } 121 if (id) { 122 this.queueOptions = defaultQueueOptions[id] || {}; 123 if (this.queueOptions.queueId) { 124 this.queueOptions = $.extend({}, (defaultQueueOptions[this.queueOptions.queueId] || {}), this.queueOptions); 125 } else { 126 // TODO: clean duplicated code 127 var element = rf.getDomElement(source); 128 var form; 129 if (element) { 130 element = $(element).closest("form"); 131 if (element.length > 0) { 132 form = element.get(0); 133 } 134 } 135 if (form && form.id && defaultQueueOptions[form.id]) { 136 id = form.id; 137 } else { 138 id = DEFAULT_QUEUE_ID; 139 } 140 if (id) { 141 this.queueOptions = $.extend({}, (defaultQueueOptions[id] || {}), this.queueOptions); 142 } 143 } 144 } 145 146 if (typeof this.queueOptions.requestGroupingId == "undefined") { 147 this.queueOptions.requestGroupingId = typeof this.source == "string" ? this.source : this.source.id; 148 } 149 150 // Remove the layerX and layerY events (generated in WebKit browsers) 151 if (event && event instanceof Object) { 152 if ('layerX' in event) delete event.layerX; 153 if ('layerY' in event) delete event.layerY; 154 } 155 156 // copy of event should be created otherwise IE will fail 157 this.event = $.extend({}, event); 158 159 //requestGroupingId is mutable, thus we need special field for it 160 this.requestGroupingId = this.queueOptions.requestGroupingId; 161 this.eventsCount = 1; 162 }; 163 164 $.extend(QueueEntry.prototype, { 165 // now unused functions: ondrop, clearEntry 166 isIgnoreDupResponses: function() { 167 return this.queueOptions.ignoreDupResponses; 168 }, 169 170 getRequestGroupId: function() { 171 return this.requestGroupingId; 172 }, 173 174 setRequestGroupId: function(id) { 175 this.requestGroupingId = id; 176 }, 177 178 resetRequestGroupId: function() { 179 this.requestGroupingId = undefined; 180 }, 181 182 setReadyToSubmit: function(isReady) { 183 this.readyToSubmit = isReady; 184 }, 185 186 getReadyToSubmit: function() { 187 return this.readyToSubmit; 188 }, 189 190 ondrop: function() { 191 var callback = this.queueOptions.onqueuerequestdrop; 192 if (callback) { 193 callback.call(this.queue, this.source, this.options, this.event); 194 } 195 }, 196 197 onRequestDelayPassed: function() { 198 this.readyToSubmit = true; 199 submitFirstEntry.call(this.queue); 200 }, 201 202 startTimer: function() { 203 var delay = this.queueOptions.requestDelay; 204 if (typeof delay != "number") { 205 delay = this.queueOptions.requestDelay || 0; 206 } 207 208 rf.log.debug("Queue will wait " + (delay || 0) + "ms before submit"); 209 210 if (delay) { 211 var _this = this; 212 this.timer = window.setTimeout(function() { 213 try { 214 _this.onRequestDelayPassed(); 215 } finally { 216 _this.timer = undefined; 217 _this = undefined; 218 } 219 }, delay); 220 } else { 221 this.onRequestDelayPassed(); 222 } 223 }, 224 225 stopTimer: function() { 226 if (this.timer) { 227 window.clearTimeout(this.timer); 228 this.timer = undefined; 229 } 230 }, 231 232 clearEntry: function() { //??? 233 this.stopTimer(); 234 if (this.request) { 235 this.request.shouldNotifyQueue = false; 236 this.request = undefined; 237 } 238 }, 239 240 getEventsCount: function() { 241 return this.eventsCount; 242 }, 243 244 setEventsCount: function(newCount) { 245 this.eventsCount = newCount; 246 } 247 }); 248 249 // TODO: add this two variables to richfaces and report bug to jsf about constants 250 var JSF_EVENT_TYPE = 'event'; 251 var JSF_EVENT_SUCCESS = 'success'; 252 var JSF_EVENT_COMPLETE = 'complete'; 253 254 var items = []; 255 var lastRequestedEntry; 256 257 //TODO: instance of this function will be created for each queue 258 var onError = function (data) { 259 var message = "richfaces.queue: ajax submit error"; 260 if (data) { 261 var description = data.message || data.description; 262 if (description) { 263 message += ": " + description; 264 } 265 } 266 rf.log.warn(message); 267 268 lastRequestedEntry = null; 269 //TODO: what if somebody is going to clear queue on error? 270 submitFirstEntry(); 271 }; 272 273 var removeStaleEntriesFromQueue = function () { 274 var entry; 275 var foundValidEntry = false; 276 while (items.length > 0 && !foundValidEntry) { 277 entry = items[0]; 278 var element = rf.getDomElement(entry.source); 279 if (element == null || $(element).closest("form").length == 0) { 280 var removedEntry = items.shift(); 281 removedEntry.stopTimer(); 282 rf.log.debug("richfaces.queue: removing stale entry from the queue (source element: " + element + ")"); 283 } else { 284 foundValidEntry = true; 285 } 286 } 287 } 288 289 var onComplete = function (data) { 290 if (data.type == JSF_EVENT_TYPE && data.status == JSF_EVENT_SUCCESS) { // or JSF_EVENT_COMPLETE will be rather 291 rf.log.debug("richfaces.queue: ajax submit successfull"); 292 lastRequestedEntry = null; 293 removeStaleEntriesFromQueue(); 294 submitFirstEntry(); 295 } 296 }; 297 298 jsf.ajax.addOnEvent(onComplete); 299 jsf.ajax.addOnError(onError); 300 301 var submitFirstEntry = function() { 302 if (QUEUE_MODE == QUEUE_MODE_PULL && lastRequestedEntry) { 303 rf.log.debug("richfaces.queue: Waiting for previous submit results"); 304 return; 305 } 306 if (isEmpty()) { 307 rf.log.debug("richfaces.queue: Nothing to submit"); 308 return; 309 } 310 var entry; 311 if (items[0].getReadyToSubmit()) { 312 try { 313 entry = lastRequestedEntry = items.shift(); 314 rf.log.debug("richfaces.queue: will submit request NOW"); 315 var o = lastRequestedEntry.options; 316 o["AJAX:EVENTS_COUNT"] = lastRequestedEntry.eventsCount; 317 rf.ajaxContainer.jsfRequest(lastRequestedEntry.source, lastRequestedEntry.event, o); 318 319 // call event handlers 320 if (o.queueonsubmit) { 321 o.queueonsubmit.call(entry); 322 } 323 callEventHandler("onrequestdequeue", entry); 324 } catch (error) { 325 onError(error); 326 } 327 } 328 }; 329 330 var isEmpty = function() { 331 return (getSize() == 0) 332 }; 333 var getSize = function() { 334 return items.length; 335 }; 336 337 var getLastEntry = function () { 338 var lastIdx = items.length - 1; 339 return items[lastIdx]; 340 }; 341 342 var updateLastEntry = function (entry) { 343 var lastIdx = items.length - 1; 344 items[lastIdx] = entry; 345 }; 346 347 var callEventHandler = function (handlerName, entry) { 348 var handler = entry.queueOptions[handlerName]; 349 if (handler) { 350 if (typeof(handler) == "string") { 351 new Function(handler).call(null, entry); 352 } else { 353 handler.call(null, entry); 354 } 355 } 356 var opts, handler2; 357 if (entry.queueOptions.queueId && 358 (opts = defaultQueueOptions[entry.queueOptions.queueId]) && 359 (handler2 = opts[handlerName]) 360 && handler2 != handler) { 361 // the same about context 362 handler2.call(null, entry); 363 } 364 } 365 366 var pushEntry = function (entry) { 367 items.push(entry); 368 rf.log.debug("New request added to queue. Queue requestGroupingId changed to " + entry.getRequestGroupId()); 369 // call event handlers 370 callEventHandler("onrequestqueue", entry); 371 } 372 373 return { 374 /** 375 * @constant 376 * @name RichFaces.queue.DEFAULT_QUEUE_ID 377 * @type string 378 * */ 379 DEFAULT_QUEUE_ID: DEFAULT_QUEUE_ID, 380 381 /** 382 * Get current queue size 383 * @function 384 * @name RichFaces.queue.getSize 385 * 386 * @return {number} size of items in the queue 387 * */ 388 getSize: getSize, 389 390 /** 391 * Check if queue is empty 392 * @function 393 * @name RichFaces.queue.isEmpty 394 * 395 * @return {boolean} returns true if queue is empty 396 * */ 397 isEmpty: isEmpty, 398 399 /** 400 * Extract and submit first QueueEntry in the queue if QueueEntry is ready to submit 401 * @function 402 * @name RichFaces.queue.submitFirst 403 * */ 404 submitFirst: function () { 405 if (!isEmpty()) { 406 var entry = items[0]; 407 entry.stopTimer(); 408 entry.setReadyToSubmit(true); 409 submitFirstEntry(); 410 } 411 }, 412 413 /** 414 * Create and push QueueEntry to the queue for ajax requests 415 * @function 416 * @name RichFaces.queue.push 417 * 418 * @param {string|DOMElement} source - The DOM element or an id that triggered this ajax request 419 * @param {object} [event] - The DOM event that triggered this ajax request 420 * @param {object} [options] - The set name/value pairs that can be sent as request parameters to control client and/or server side request processing 421 * */ 422 push: function (source, event, options) { 423 var entry = new QueueEntry(this, source, event, options); 424 var requestGroupingId = entry.getRequestGroupId(); 425 426 var lastEntry = getLastEntry(); 427 428 if (lastEntry) { 429 if (lastEntry.getRequestGroupId() == requestGroupingId) { 430 rf.log.debug("Similar request currently in queue"); 431 432 rf.log.debug("Combine similar requests and reset timer"); 433 434 lastEntry.stopTimer(); 435 entry.setEventsCount(lastEntry.getEventsCount() + 1); 436 437 updateLastEntry(entry); 438 callEventHandler("onrequestqueue", entry); 439 } else { 440 rf.log.debug("Last queue entry is not the last anymore. Stopping requestDelay timer and marking entry as ready for submission") 441 442 lastEntry.stopTimer(); 443 lastEntry.resetRequestGroupId(); 444 lastEntry.setReadyToSubmit(true); 445 446 pushEntry(entry); 447 submitFirstEntry(); 448 } 449 } else { 450 pushEntry(entry); 451 } 452 453 // start timer 454 entry.startTimer(); 455 456 }, 457 458 response: function (request, context) { 459 if (this.isIgnoreResponse()) { 460 lastRequestedEntry = null; 461 submitFirstEntry(); 462 } else { 463 rf.ajaxContainer.jsfResponse(request, context); 464 } 465 }, 466 467 isIgnoreResponse: function () { 468 var entry = items[0]; 469 return entry && lastRequestedEntry.isIgnoreDupResponses() 470 && lastRequestedEntry.queueOptions.requestGroupingId == entry.queueOptions.requestGroupingId; 471 }, 472 473 /** 474 * Remove all QueueEntry from the queue 475 * @function 476 * @name RichFaces.queue.clear 477 * */ 478 clear: function () { 479 var lastEntry = getLastEntry(); 480 if (lastEntry) { 481 lastEntry.stopTimer(); 482 } 483 items = []; 484 }, 485 486 /** 487 * Set queue default options 488 * @function 489 * @name RichFaces.queue.setQueueOptions 490 * 491 * @param {string||object} [id] - Queue id for storing options or hash with options for multiple options set 492 * @param {object} options - Queue options object 493 * */ 494 setQueueOptions: function (id, options) { 495 var tid = typeof id; 496 if (tid == "string") { 497 // add named queue options 498 if (defaultQueueOptions[id]) { 499 throw "Queue already registered"; 500 } else { 501 defaultQueueOptions[id] = options; 502 } 503 } else if (tid == "object") { 504 // first parameter is hash with queue names and options 505 $.extend(defaultQueueOptions, id); 506 } 507 return rf.queue; 508 }, 509 510 getQueueOptions: function (id) { 511 return defaultQueueOptions[id] || {}; 512 } 513 } 514 }()); 515 }(RichFaces.jQuery, RichFaces, jsf)); 516