      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