1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.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: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @class 26 * This static class serves as a central location for registering and calling 27 * public API methods. For example, the registration of a public API method for 28 * pulling up the compose form might look something like this: 29 * 30 * <pre> 31 * AjxDispatcher.register("Compose", "Mail", new AjxCallback(this, this.doAction)); 32 * </pre> 33 * 34 * and a client call of it like this: 35 * 36 * <pre> 37 * AjxDispatcher.run("Compose", {action:ZmOperation.NEW_MESSAGE, inNewWindow:false}); 38 * </pre> 39 * 40 * Registration will most likely need to happen in a constructor, when 'this' is 41 * available, since you'll most likely want the function call to happen in that 42 * object's context. 43 * <p> 44 * A package can also register a callback to be run once the package has loaded. One use case for 45 * that is to register newly-defined classes as drop targets. There is a wrapper method around 46 * AjxPackage.require() that will call that method and then run the post-load callback (if any): 47 * 48 * <pre> 49 * AjxDispatcher.require("Calendar"); 50 * </pre> 51 * 52 * @author Conrad Damon 53 */ 54 AjxDispatcher = function() {} 55 56 // Table of API names, packages, and associated function calls 57 AjxDispatcher._registry = {}; 58 59 // Table of package names and callbacks to run after loading (optional) 60 AjxDispatcher._package = {}; 61 62 AjxDispatcher._preLoad = []; 63 AjxDispatcher._postLoad = []; 64 AjxDispatcher._loadFunctionsEnabled = false; 65 AjxDispatcher._timedAction = null; 66 67 /** 68 * Adds a function to be called after the given package has been loaded. 69 * 70 * @param {string} pkg the name of package 71 * @param {AjxCallback} callback the callback to run after package has loaded 72 */ 73 AjxDispatcher.addPackageLoadFunction = 74 function(pkg, callback) { 75 var pkgData = AjxDispatcher._getPackageData(pkg); 76 if (!pkgData._loaded && !AjxPackage.isDefined(pkg)) { 77 pkgData.callback.push(callback); 78 } else { 79 AjxTimedAction.scheduleAction(new AjxTimedAction(callback, callback.run), 0); 80 } 81 }; 82 83 /** 84 * Adds a function to be called while a package is being loaded. A typical use 85 * is to display a "Loading..." screen. 86 * 87 * @param {AjxCallback} callback the callback to run after package has loaded 88 */ 89 AjxDispatcher.addPreLoadFunction = 90 function(callback) { 91 AjxDispatcher._preLoad.push(callback); 92 }; 93 94 /** 95 * Adds a function to be called after a package has been loaded. A typical use 96 * is to clear a "Loading..." screen. 97 * 98 * @param {AjxCallback} callback the callback to run after package has loaded 99 */ 100 AjxDispatcher.addPostLoadFunction = 101 function(callback) { 102 AjxDispatcher._postLoad.push(callback); 103 }; 104 105 /** 106 * @deprecated Use addPackageLoadFunction instead. 107 */ 108 AjxDispatcher.setPackageLoadFunction = AjxDispatcher.addPackageLoadFunction; 109 110 /** 111 * @deprecated Use addPreLoadFunction instead. 112 */ 113 AjxDispatcher.setPreLoadFunction = AjxDispatcher.addPreLoadFunction; 114 115 /** 116 * @deprecated Use addPostLoadFunction instead. 117 */ 118 AjxDispatcher.setPostLoadFunction = AjxDispatcher.addPostLoadFunction; 119 120 /** 121 * Enables/disables the running of the pre/post load functions. 122 * 123 * @param {boolean} enabled if <code>true</code>, run pre/post load functions 124 */ 125 AjxDispatcher.enableLoadFunctions = 126 function(enable) { 127 AjxDispatcher._loadFunctionsEnabled = enable; 128 }; 129 130 /** 131 * Checks if the given package has been loaded. 132 * 133 * @param {string} pkg the name of package 134 * @return {boolean} <code>true</code> if the package is loaded 135 */ 136 AjxDispatcher.loaded = 137 function(pkg) { 138 var pkgData = AjxDispatcher._getPackageData(pkg); 139 return (pkgData && pkgData._loaded) || AjxPackage.isDefined(pkg); 140 }; 141 142 /** 143 * Programmatically sets whether the given packages has been loaded. Use with care! 144 * 145 * @param {string} pkg the name of package 146 * @return {boolean} if <code>true</code>, the package is loaded 147 */ 148 AjxDispatcher.setLoaded = 149 function(pkg, loaded) { 150 var pkgData = AjxDispatcher._getPackageData(pkg); 151 pkgData._loaded = loaded; 152 if (loaded) { 153 var callbacks = pkgData.callback || []; 154 for (var i = 0; i < callbacks.length; i++) { 155 callbacks[i].run(); 156 } 157 } 158 }; 159 160 /** 161 * Registers an API method so that it may be called. 162 * 163 * @param {string} method the name of the API method 164 * @param {string} pkg the name of required package(s) 165 * @param {AjxCallback} callback the callback to run for this API call 166 */ 167 AjxDispatcher.registerMethod = 168 function(method, pkg, callback) { 169 AjxDispatcher._registry[method] = {pkg:pkg, callback:callback}; 170 }; 171 172 /** 173 * Calls the given API method with the given arguments. It can be passed any 174 * number of arguments (provided after the API name), and they will be passed 175 * to the function that gets called. 176 * 177 * PS: You are in a maze of twisty callbacks, all alike. 178 * 179 * @param {string} method the name of the API method 180 * @param {boolean} async if <code>true</code>, load package asynchronously 181 * @param {AjxCallback} callback the callback to run with results (must be present 182 * if there are pre- or post-load functions) 183 * @param {boolean} preLoadOk if <code>true</code>, okay to run registered pre-load function 184 * 185 * @private 186 */ 187 AjxDispatcher.run = 188 function(params /*, arg1 ... argN */) { 189 if (!params) { return; } 190 var method, noLoad, async, callback, preLoadOk; 191 if (typeof(params) == "string") { 192 method = params; 193 async = false; 194 preLoadOk = false; 195 } else { 196 method = params.method; 197 noLoad = params.noLoad; 198 callback = params.callback; 199 async = params.async != null ? params.async : Boolean(callback); 200 preLoadOk = params.preLoadOk != null ? params.preLoadOk : (callback != null); 201 } 202 var item = AjxDispatcher._registry[method]; 203 if (!item) { 204 // method hasn't been registered 205 AjxPackage.__log("API method '" + method + "' not found"); 206 return; 207 } 208 AjxPackage.__log("Run method: " + method); 209 var pkg = item.pkg; 210 var args = []; 211 for (var i = 1; i < arguments.length; ++i) { 212 args.push(arguments[i]); 213 } 214 if (callback) { 215 args.push(callback); 216 } 217 218 return AjxDispatcher.require(pkg, async, item.callback, args, preLoadOk); 219 }; 220 221 /** 222 * Loads the given package, and runs its requested post-load callback. Clients should 223 * be careful not to mix async and sync calls for the same package, in order to avoid 224 * race conditions. 225 * 226 * @param {string} pkg the name of the API method 227 * @param {boolean} async if <code>true</code>, load package asynchronously 228 * @param {AjxCallback} callback the callback to run after pkg load 229 * @param {array} args the args to pass to callback 230 * @param {boolean} preLoadOk if <code>true</code>, okay to run registered pre-load function 231 */ 232 AjxDispatcher.require = 233 function(pkg, async, callback, args, preLoadOk) { 234 if (!pkg) { return; } 235 236 if (typeof(pkg) == "string") { 237 pkg = [pkg]; 238 } 239 var unloaded = []; 240 for (var i = 0; i < pkg.length; i++) { 241 var p = pkg[i]; 242 if (!AjxDispatcher._getPackageData(p)._loaded) { 243 unloaded.push(p); 244 } 245 } 246 if (unloaded.length == 0) { 247 return AjxDispatcher._postLoadCallback(pkg, false, callback, args); 248 } else { 249 // need callback in order to run pre-load function 250 var preLoad = AjxDispatcher._preLoad; 251 if (preLoadOk && AjxDispatcher._loadFunctionsEnabled && preLoad.length) { 252 AjxPackage.__log("pre-load function"); 253 AjxDispatcher._timedAction = new AjxCallback(null, AjxDispatcher._continueRequire, [unloaded, async, callback, args]); 254 for (var i = 0; i < preLoad.length; i++) { 255 preLoad[i].run(); 256 } 257 window.setTimeout('AjxDispatcher._timedAction.run()', 0); 258 } else { 259 return AjxDispatcher._continueRequire(unloaded, async, callback, args); 260 } 261 } 262 }; 263 264 AjxDispatcher._continueRequire = 265 function(pkg, async, callback, args) { 266 var pkgString = pkg.join(", "); 267 AjxPackage.__log("------------------------------------- Loading package: " + pkgString); 268 if (window.console && window.console.log) { console.log("------------------------------------- Loading package: " + pkgString); } 269 if (async && callback) { 270 var postLoadCallback = new AjxCallback(null, AjxDispatcher._postLoadCallback, [pkg, true, callback, args]); 271 AjxPackage.require({name:pkg, callback:postLoadCallback}); 272 } else { 273 var _st = new Date(); 274 for (var i = 0; i < pkg.length; i++) { 275 AjxPackage.require(pkg[i]); 276 } 277 var _en = new Date(); 278 var t = _en.getTime() - _st.getTime(); 279 AjxPackage.__log("LOAD TIME for " + pkgString + ": " + t); 280 281 return AjxDispatcher._postLoadCallback(pkg, true, callback, args); 282 } 283 }; 284 285 AjxDispatcher._postLoadCallback = 286 function(pkg, pkgWasLoaded, callback, args) { 287 for (var i = 0; i < pkg.length; i++) { 288 AjxDispatcher._getPackageData(pkg[i])._loaded = true; 289 } 290 for (var i = 0; i < pkg.length; i++) { 291 var pkgData = AjxDispatcher._getPackageData(pkg[i]); 292 if (pkgWasLoaded && pkgData.callback.length && !pkgData.callbackDone) { 293 pkgData.callbackDone = true; 294 AjxPackage.__log("Running post-load package function for " + pkg[i]); 295 var callbacks = pkgData.callback; 296 for (var j = 0; j < callbacks.length; j++) { 297 callbacks[j].run(); 298 } 299 pkgData.callback.length = 0; 300 } 301 } 302 if (pkgWasLoaded) { 303 var postLoad = AjxDispatcher._postLoad; 304 if (AjxDispatcher._loadFunctionsEnabled && postLoad.length) { 305 for (var i = 0; i < postLoad.length; i++) { 306 postLoad[i].run(); 307 } 308 } 309 } 310 311 if (callback) { 312 return callback.isAjxCallback ? callback.run1(args) : callback(args); 313 } 314 }; 315 316 AjxDispatcher._getPackageData = function(pkg) { 317 if (!AjxDispatcher._package[pkg]) { 318 AjxDispatcher._package[pkg] = { callback: [] }; 319 } 320 return AjxDispatcher._package[pkg]; 321 }; 322