1 /** 2 * @fileOverview 3 * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>) 4 * @license Copyright (c) 2009-2016 LabKey Corporation 5 * <p/> 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * <p/> 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * <p/> 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 * <p/> 18 */ 19 20 /** 21 * Make multiple ajax requests and invokes a callback when all are complete. 22 * Requests are added as [function, config] array pairs where the config object 23 * is passed as the argument to the request function. The request function's config 24 * object argument must accept a success callback named 'success' and a failure 25 * callback named 'failure'. 26 * @class Make multiple ajax requests and fires an event when all are complete. 27 * @memberOf LABKEY 28 * 29 * @param [config] Either an array of [function, config] array pairs 30 * to be added or a config object with the shape: 31 * <ul> 32 * <li>listeners: a config object containing event handlers. 33 * <li>requests: an array of [function, config] array pairs to be added. 34 * </ul> 35 * @example 36 var config = { 37 schemaName : "assay", 38 queryName : protocolName + " Data", 39 containerPath : "/Test", 40 success: function (data, options, response) { 41 console.log("selectRows success: " + data.rowCount); 42 }, 43 failure: function (response, options) { 44 console.log("selectRows failure"); 45 }, 46 scope: scope // scope to execute success and failure callbacks in. 47 }; 48 49 // add the requests and config arguments one by one 50 var multi = new LABKEY.MultiRequest(); 51 var requestScope = ... // scope to execute the request function in. 52 multi.add(LABKEY.Query.selectRows, config, requestScope); 53 multi.add(LABKEY.Query.selectRows, config, requestScope); 54 multi.add(LABKEY.Query.selectRows, config, requestScope); 55 multi.send( 56 function () { console.log("send complete"); }, 57 sendCallbackScope // scope to execute 'send complete' callback in. 58 ); 59 60 // additional requests won't be sent while other requests are in progress 61 multi.add(LABKEY.Query.selectRows, config); 62 multi.send(function () { console.log("send complete"); }, sendCallbackScope); 63 64 // constructor can take an array of requests [function, config] pairs 65 multi = new LABKEY.MultiRequest([ 66 [ LABKEY.Query.selectRows, config ], 67 [ LABKEY.Query.selectRows, config ], 68 [ LABKEY.Query.selectRows, config ] 69 ]); 70 multi.send(); 71 72 // constructor can take a config object with listeners and requests. 73 // if there is a 'done' listener, the requests will be sent immediately. 74 multi = new LABKEY.MultiRequest({ 75 listeners : { 'done': function () { console.log("send complete"); }, scope: sendCallbackScope }, 76 requests : [ [ LABKEY.Query.selectRows, config ], 77 [ LABKEY.Query.selectRows, config ], 78 [ LABKEY.Query.selectRows, config ] ] 79 }); 80 81 // Alternate syntax for adding the 'done' event listener. 82 multi = new LABKEY.MultiRequest({ 83 listeners : { 84 'done': { 85 fn: function () { console.log("send complete"); } 86 scope: sendCallbackScope 87 } 88 }, 89 }); 90 * </pre> 91 */ 92 LABKEY.MultiRequest = function (config) { 93 config = config || {}; 94 95 var self = this; 96 var sending = false; 97 var waitQ = []; 98 var sendQ = []; 99 100 var requests; 101 var listeners; 102 if (LABKEY.Utils.isArray(config)) { 103 requests = config; 104 } else { 105 requests = config.requests; 106 listeners = config.listeners; 107 } 108 109 if (requests) { 110 for (var i = 0; i < requests.length; i++) { 111 var request = requests[i]; 112 this.add(request[0], request[1]); 113 } 114 } 115 116 var doneCallbacks = []; 117 if (listeners && listeners.done) { 118 if (typeof listeners.done == "function") { 119 doneCallbacks.push({fn: listeners.done, scope: listeners.scope}); 120 } 121 else if (typeof listeners.done.fn == "function") { 122 doneCallbacks.push({fn: listeners.done.fn, scope: listeners.done.scope || listeners.scope}); 123 } 124 } 125 126 if (waitQ.length && doneCallbacks.length > 0) { 127 this.send(); 128 } 129 130 function fireDone() { 131 //console.log("fireDone:"); 132 for (var i = 0; i < doneCallbacks.length; i++) { 133 var cb = doneCallbacks[i]; 134 //console.log(" invoking done callback: ", cb); 135 if (cb.fn && typeof cb.fn == "function") { 136 cb.fn.call(cb.scope || window); 137 } 138 } 139 } 140 141 function checkDone() { 142 //console.log("checkDone: sendQ.length=" + sendQ.length); 143 sendQ.pop(); 144 if (sendQ.length == 0) { 145 sending = false; 146 fireDone(); 147 self.send(); 148 } 149 return true; 150 } 151 152 function createSequence(fn1, fn2, scope) { 153 return function () { 154 var ret = fn1.apply(scope || this || window, arguments); 155 fn2.apply(scope || this || window, arguments); 156 return ret; 157 } 158 } 159 160 /** 161 * Adds a request to the queue. 162 * @param fn {Function} A request function which takes single config object. 163 * @param config {Object} The config object that will be passed to the request <code>fn</code> 164 * and must contain success and failure callbacks. 165 * @param [scope] {Object} The scope in which to execute the request <code>fn</code>. 166 * Note that the config success and failure callbacks will execute in the <code>config.scope</code> and not the <code>scope</code> argument. 167 * @returns {LABKEY.MultiRequest} this object so add calls can be chained. 168 * @example 169 * <pre> 170 * new MultiRequest().add(Ext.Ajax.request, { 171 * url: LABKEY.ActionURL.buildURL("controller", "action1", "/container/path"), 172 * success: function () { console.log("success 1!"); }, 173 * failure: function () { console.log("failure 1!"); }, 174 * scope: this // The scope of the success and failure callbacks. 175 * }).add({Ext.Ajax.request, { 176 * url: LABKEY.ActionURL.buildURL("controller", "action2", "/container/path"), 177 * success: function () { console.log("success 2!"); }, 178 * failure: function () { console.log("failure 2!"); }, 179 * scope: this // The scope of the success and failure callbacks. 180 * }).send(function () { console.log("all done!") }); 181 * </pre> 182 */ 183 this.add = function (fn, config, scope) { 184 config = config || {}; 185 186 var success = LABKEY.Utils.getOnSuccess(config); 187 if (!success) success = function () { }; 188 if (!success._hookInstalled) { 189 config.success = createSequence(success, checkDone, config.scope); 190 config.success._hookInstalled = true; 191 } 192 193 var failure = LABKEY.Utils.getOnFailure(config); 194 if (!failure) failure = function () { }; 195 if (!failure._hookInstalled) { 196 config.failure = createSequence(failure, checkDone, config.scope); 197 config.failure._hookInstalled = true; 198 } 199 200 waitQ.push({fn: fn, args: [config], scope: scope}); 201 return this; 202 }; 203 204 /** 205 * Send the queued up requests. When all requests have returned, the send callback 206 * will be called. 207 * @param callback {Function} A function with a single argument of 'this'. 208 * @param [scope] {Object} The scope in which to execute the callback. 209 * 210 * Alternatively, a single config Object argument: 211 * <ul> 212 * <li>fn: The send callback function. 213 * <li>scope: The scope to execute the send callback function in. 214 * </ul> 215 */ 216 this.send = function (callback, scope) { 217 if (sending || waitQ.length == 0) 218 return; 219 sending = true; 220 sendQ = waitQ; 221 waitQ = new Array(); 222 223 var len = sendQ.length; 224 for (var i = 0; i < len; i++) { 225 var q = sendQ[i]; 226 q.fn.apply(q.scope || window, q.args); 227 } 228 229 var self = this; 230 if (typeof callback == "function") { 231 doneCallbacks.push({fn: callback, scope: scope}); 232 } else if (typeof callback.fn == "function") { 233 doneCallbacks.push({fn: callback.fn, scope: callback.scope||scope}); 234 } 235 }; 236 }; 237