| 1 | | /*global exports */ |
| 2 | | /*! |
| 3 | | * This file is used for define the EventProxy library. |
| 4 | | * @author <a href="mailto:shyvo1987@gmail.com">Jackson Tian</a> |
| 5 | | * @version 0.1.0 |
| 6 | | */ |
| 7 | 1 | ;(function (name, definition) { |
| 8 | | // this is considered "safe": |
| 9 | 1 | var hasDefine = typeof define === 'function', |
| 10 | | // hasDefine = typeof define === 'function', |
| 11 | | hasExports = typeof module !== 'undefined' && module.exports; |
| 12 | | |
| 13 | 1 | if (hasDefine) { // AMD Module or CMD Module |
| 14 | 0 | define(definition); |
| 15 | 1 | } else if (hasExports) { // Node.js Module |
| 16 | 1 | module.exports = definition(); |
| 17 | | } else { // Assign to common namespaces or simply the global object (window) |
| 18 | 0 | this[name] = definition(); |
| 19 | | } |
| 20 | | })('EventProxy', function () { |
| 21 | | |
| 22 | | /** |
| 23 | | * EventProxy. An implementation of task/event based asynchronous pattern. |
| 24 | | * A module that can be mixed in to *any object* in order to provide it with custom events. |
| 25 | | * You may `bind` or `unbind` a callback function to an event; |
| 26 | | * `trigger`-ing an event fires all callbacks in succession. |
| 27 | | * Examples: |
| 28 | | * ``` |
| 29 | | * var render = function (template, resources) {}; |
| 30 | | * var proxy = new EventProxy(); |
| 31 | | * proxy.assign("template", "l10n", render); |
| 32 | | * proxy.trigger("template", template); |
| 33 | | * proxy.trigger("l10n", resources); |
| 34 | | * ``` |
| 35 | | */ |
| 36 | 1 | var EventProxy = function () { |
| 37 | 19 | if (!(this instanceof EventProxy)) { |
| 38 | 0 | return new EventProxy(); |
| 39 | | } |
| 40 | 19 | this._callbacks = {}; |
| 41 | 19 | this._fired = {}; |
| 42 | | }; |
| 43 | | |
| 44 | | /** |
| 45 | | * Bind an event, specified by a string name, `ev`, to a `callback` function. |
| 46 | | * Passing `all` will bind the callback to all events fired. |
| 47 | | * @param {String} eventName Event name. |
| 48 | | * @param {Function} callback Callback. |
| 49 | | */ |
| 50 | 1 | EventProxy.prototype.addListener = function (ev, callback) { |
| 51 | 42 | this._callbacks = this._callbacks || {}; |
| 52 | 42 | this._callbacks[ev] = this._callbacks[ev] || []; |
| 53 | 42 | this._callbacks[ev].push(callback); |
| 54 | 42 | return this; |
| 55 | | }; |
| 56 | | /** |
| 57 | | * `addListener` alias |
| 58 | | */ |
| 59 | 1 | EventProxy.prototype.bind = EventProxy.prototype.addListener; |
| 60 | | /** |
| 61 | | * `addListener` alias |
| 62 | | */ |
| 63 | 1 | EventProxy.prototype.on = EventProxy.prototype.addListener; |
| 64 | | /** |
| 65 | | * `addListener` alias |
| 66 | | */ |
| 67 | 1 | EventProxy.prototype.await = EventProxy.prototype.addListener; |
| 68 | | |
| 69 | | /** |
| 70 | | * Remove one or many callbacks. If `callback` is null, removes all callbacks for the event. |
| 71 | | * If `ev` is null, removes all bound callbacks |
| 72 | | * for all events. |
| 73 | | * @param {String} eventName Event name. |
| 74 | | * @param {Function} callback Callback. |
| 75 | | */ |
| 76 | 1 | EventProxy.prototype.removeListener = function (ev, callback) { |
| 77 | 29 | var calls = this._callbacks, i, l; |
| 78 | 29 | if (!ev) { |
| 79 | 1 | this._callbacks = {}; |
| 80 | 28 | } else if (calls) { |
| 81 | 28 | if (!callback) { |
| 82 | 1 | calls[ev] = []; |
| 83 | | } else { |
| 84 | 27 | var list = calls[ev]; |
| 85 | 27 | if (!list) { |
| 86 | 1 | return this; |
| 87 | | } |
| 88 | 26 | l = list.length; |
| 89 | 26 | for (i = 0; i < l; i++) { |
| 90 | 26 | if (callback === list[i]) { |
| 91 | 26 | list[i] = null; |
| 92 | 26 | break; |
| 93 | | } |
| 94 | | } |
| 95 | | } |
| 96 | | } |
| 97 | 28 | return this; |
| 98 | | }; |
| 99 | | /** |
| 100 | | * `removeListener` alias |
| 101 | | */ |
| 102 | 1 | EventProxy.prototype.unbind = EventProxy.prototype.removeListener; |
| 103 | | |
| 104 | | /** |
| 105 | | * Remove all listeners. It equals unbind() |
| 106 | | * Just add this API for as same as Event.Emitter. |
| 107 | | * @param {String} event Event name. |
| 108 | | */ |
| 109 | 1 | EventProxy.prototype.removeAllListeners = function (event) { |
| 110 | 0 | return this.unbind(event); |
| 111 | | }; |
| 112 | | |
| 113 | | /** |
| 114 | | * Trigger an event, firing all bound callbacks. Callbacks are passed the |
| 115 | | * same arguments as `trigger` is, apart from the event name. |
| 116 | | * Listening for `"all"` passes the true event name as the first argument. |
| 117 | | * @param {String} eventName Event name |
| 118 | | * @param {Mix} data Pass in data |
| 119 | | */ |
| 120 | 1 | EventProxy.prototype.trigger = function (eventName, data) { |
| 121 | 113 | var list, calls, ev, callback, args, i, l; |
| 122 | 113 | var both = 2; |
| 123 | 113 | if (!(calls = this._callbacks)) { |
| 124 | 0 | return this; |
| 125 | | } |
| 126 | 113 | while (both--) { |
| 127 | 226 | ev = both ? eventName : 'all'; |
| 128 | 226 | list = calls[ev]; |
| 129 | 226 | if (list) { |
| 130 | 136 | for (i = 0, l = list.length; i < l; i++) { |
| 131 | 134 | if (!(callback = list[i])) { |
| 132 | 9 | list.splice(i, 1); i--; l--; |
| 133 | | } else { |
| 134 | 125 | args = both ? Array.prototype.slice.call(arguments, 1) : arguments; |
| 135 | 125 | callback.apply(this, args); |
| 136 | | } |
| 137 | | } |
| 138 | | } |
| 139 | | } |
| 140 | 113 | return this; |
| 141 | | }; |
| 142 | | /** |
| 143 | | * `trigger` alias |
| 144 | | */ |
| 145 | 1 | EventProxy.prototype.emit = EventProxy.prototype.trigger; |
| 146 | | /** |
| 147 | | * `trigger` alias |
| 148 | | */ |
| 149 | 1 | EventProxy.prototype.fire = EventProxy.prototype.trigger; |
| 150 | | |
| 151 | | /** |
| 152 | | * Bind an event like the bind method, but will remove the listener after it was fired. |
| 153 | | * @param {String} ev Event name |
| 154 | | * @param {Function} callback Callback |
| 155 | | */ |
| 156 | 1 | EventProxy.prototype.once = function (ev, callback) { |
| 157 | 22 | var self = this; |
| 158 | 22 | var wrapper = function () { |
| 159 | 19 | callback.apply(self, arguments); |
| 160 | 19 | self.unbind(ev, wrapper); |
| 161 | | }; |
| 162 | 22 | this.bind(ev, wrapper); |
| 163 | 22 | return this; |
| 164 | | }; |
| 165 | | |
| 166 | | /** |
| 167 | | * Bind an event, and trigger it immediately. |
| 168 | | * @param {String} ev Event name. |
| 169 | | * @param {Function} callback Callback. |
| 170 | | * @param {Mix} data The data that will be passed to calback as arguments. |
| 171 | | */ |
| 172 | 1 | EventProxy.prototype.immediate = function (ev, callback, data) { |
| 173 | 2 | this.bind(ev, callback); |
| 174 | 2 | this.trigger(ev, data); |
| 175 | 2 | return this; |
| 176 | | }; |
| 177 | | /** |
| 178 | | * `immediate` alias |
| 179 | | */ |
| 180 | 1 | EventProxy.prototype.asap = EventProxy.prototype.immediate; |
| 181 | | |
| 182 | 1 | var _assign = function (eventname1, eventname2, cb, once) { |
| 183 | 8 | var proxy = this, length, index = 0, argsLength = arguments.length, |
| 184 | | bind, _all, |
| 185 | | callback, events, isOnce, times = 0, flag = {}; |
| 186 | | |
| 187 | | // Check the arguments length. |
| 188 | 8 | if (argsLength < 3) { |
| 189 | 0 | return this; |
| 190 | | } |
| 191 | | |
| 192 | 8 | events = Array.prototype.slice.apply(arguments, [0, argsLength - 2]); |
| 193 | 8 | callback = arguments[argsLength - 2]; |
| 194 | 8 | isOnce = arguments[argsLength - 1]; |
| 195 | | |
| 196 | | // Check the callback type. |
| 197 | 8 | if (typeof callback !== "function") { |
| 198 | 0 | return this; |
| 199 | | } |
| 200 | | |
| 201 | 8 | length = events.length; |
| 202 | 8 | bind = function (key) { |
| 203 | 19 | var method = isOnce ? "once" : "bind"; |
| 204 | 19 | proxy[method](key, function (data) { |
| 205 | 19 | proxy._fired[key] = proxy._fired[key] || {}; |
| 206 | 19 | proxy._fired[key].data = data; |
| 207 | 19 | if (!flag[key]) { |
| 208 | 18 | flag[key] = true; |
| 209 | 18 | times++; |
| 210 | | } |
| 211 | | }); |
| 212 | | }; |
| 213 | | |
| 214 | 8 | for (index = 0; index < length; index++) { |
| 215 | 19 | bind(events[index]); |
| 216 | | } |
| 217 | | |
| 218 | 8 | _all = function (event) { |
| 219 | 21 | if (times < length) { |
| 220 | 12 | return; |
| 221 | | } |
| 222 | 9 | if (!flag[event]) { |
| 223 | 1 | return; |
| 224 | | } |
| 225 | 8 | var data = []; |
| 226 | 8 | for (index = 0; index < length; index++) { |
| 227 | 18 | data.push(proxy._fired[events[index]].data); |
| 228 | | } |
| 229 | 8 | if (isOnce) { |
| 230 | 6 | proxy.unbind("all", _all); |
| 231 | | } |
| 232 | 8 | callback.apply(null, data); |
| 233 | | }; |
| 234 | 8 | proxy.bind("all", _all); |
| 235 | | }; |
| 236 | | |
| 237 | | /** |
| 238 | | * Assign some events, after all events were fired, the callback will be executed once. |
| 239 | | * Examples: |
| 240 | | * ``` |
| 241 | | * proxy.all(ev1, ev2, callback); |
| 242 | | * proxy.all([ev1, ev2], callback); |
| 243 | | * proxy.all(ev1, [ev2, ev3], callback); |
| 244 | | * ``` |
| 245 | | * @param {String} eventName1 First event name. |
| 246 | | * @param {String} eventName2 Second event name. |
| 247 | | * @param {Function} callback Callback, that will be called after predefined events were fired. |
| 248 | | */ |
| 249 | 1 | EventProxy.prototype.all = function (eventname1, eventname2, callback) { |
| 250 | 7 | var args = Array.prototype.concat.apply([], arguments); |
| 251 | 7 | args.push(true); |
| 252 | 7 | _assign.apply(this, args); |
| 253 | 7 | return this; |
| 254 | | }; |
| 255 | | /** |
| 256 | | * `all` alias |
| 257 | | */ |
| 258 | 1 | EventProxy.prototype.assign = EventProxy.prototype.all; |
| 259 | | |
| 260 | | /** |
| 261 | | * Assign the only one 'error' event handler. |
| 262 | | * @param {Function(err)} callback |
| 263 | | */ |
| 264 | 1 | EventProxy.prototype.fail = function (callback) { |
| 265 | 3 | var that = this; |
| 266 | 3 | that.once('error', function (err) { |
| 267 | 1 | that.unbind(); |
| 268 | 1 | callback(err); |
| 269 | | }); |
| 270 | 3 | return this; |
| 271 | | }; |
| 272 | | |
| 273 | | /** |
| 274 | | * Assign some events, after all events were fired, the callback will be executed first time. |
| 275 | | * Then any event that predefined be fired again, the callback will executed with the newest data. |
| 276 | | * Examples: |
| 277 | | * ``` |
| 278 | | * proxy.tail(ev1, ev2, callback); |
| 279 | | * proxy.tail([ev1, ev2], callback); |
| 280 | | * proxy.tail(ev1, [ev2, ev3], callback); |
| 281 | | * ``` |
| 282 | | * @param {String} eventName1 First event name. |
| 283 | | * @param {String} eventName2 Second event name. |
| 284 | | * @param {Function} callback Callback, that will be called after predefined events were fired. |
| 285 | | */ |
| 286 | 1 | EventProxy.prototype.tail = function () { |
| 287 | 1 | var args = Array.prototype.concat.apply([], arguments); |
| 288 | 1 | args.push(false); |
| 289 | 1 | _assign.apply(this, args); |
| 290 | 1 | return this; |
| 291 | | }; |
| 292 | | /** |
| 293 | | * `tail` alias |
| 294 | | */ |
| 295 | 1 | EventProxy.prototype.assignAll = EventProxy.prototype.tail; |
| 296 | | /** |
| 297 | | * `tail` alias |
| 298 | | */ |
| 299 | 1 | EventProxy.prototype.assignAlways = EventProxy.prototype.tail; |
| 300 | | |
| 301 | | /** |
| 302 | | * The callback will be executed after the event be fired N times. |
| 303 | | * @param {String} eventName Event name. |
| 304 | | * @param {Mumber} times N times. |
| 305 | | * @param {Function} callback Callback, that will be called after event was fired N times. |
| 306 | | */ |
| 307 | 1 | EventProxy.prototype.after = function (eventName, times, callback) { |
| 308 | 3 | if (times === 0) { |
| 309 | 1 | callback.call(null, []); |
| 310 | 1 | return this; |
| 311 | | } |
| 312 | 2 | var proxy = this, |
| 313 | | firedData = [], |
| 314 | | all; |
| 315 | 2 | all = function (name, data) { |
| 316 | 65 | if (name === eventName) { |
| 317 | 65 | times--; |
| 318 | 65 | firedData.push(data); |
| 319 | 65 | if (times < 1) { |
| 320 | 2 | proxy.unbind("all", all); |
| 321 | 2 | callback.apply(null, [firedData]); |
| 322 | | } |
| 323 | | } |
| 324 | | }; |
| 325 | 2 | proxy.bind("all", all); |
| 326 | 2 | return this; |
| 327 | | }; |
| 328 | | |
| 329 | | /** |
| 330 | | * The callback will be executed after any registered event was fired. It only executed once. |
| 331 | | * @param {string} eventName1 Event name. |
| 332 | | * @param {string} eventName2 Event name. |
| 333 | | * @param {function} callback The callback will get a map that has data and eventName attributes. |
| 334 | | */ |
| 335 | 1 | EventProxy.prototype.any = function () { |
| 336 | 1 | var proxy = this, |
| 337 | | index, |
| 338 | | _bind, |
| 339 | | len = arguments.length, |
| 340 | | callback = arguments[len - 1], |
| 341 | | events = Array.prototype.slice.apply(arguments, [0, len - 1]), |
| 342 | | count = events.length, |
| 343 | | _eventName = events.join("_"); |
| 344 | | |
| 345 | 1 | proxy.once(_eventName, callback); |
| 346 | | |
| 347 | 1 | _bind = function (key) { |
| 348 | 2 | proxy.bind(key, function (data) { |
| 349 | 3 | proxy.trigger(_eventName, {"data": data, eventName: key}); |
| 350 | | }); |
| 351 | | }; |
| 352 | | |
| 353 | 1 | for (index = 0; index < count; index++) { |
| 354 | 2 | _bind(events[index]); |
| 355 | | } |
| 356 | | }; |
| 357 | | |
| 358 | | /** |
| 359 | | * The callback will be executed when the evnet name not equals with assigned evnet. |
| 360 | | * @param {string} eventName Event name. |
| 361 | | * @param {function} callback Callback. |
| 362 | | */ |
| 363 | 1 | EventProxy.prototype.not = function (eventName, callback) { |
| 364 | 1 | var proxy = this; |
| 365 | 1 | proxy.bind("all", function (name, data) { |
| 366 | 3 | if (name !== eventName) { |
| 367 | 2 | callback(data); |
| 368 | | } |
| 369 | | }); |
| 370 | | }; |
| 371 | | |
| 372 | | /** |
| 373 | | * Success callback wraper, will handler err for you. |
| 374 | | * |
| 375 | | * ```js |
| 376 | | * fs.readFile('foo.txt', ep.done('content')); |
| 377 | | * |
| 378 | | * // equal to => |
| 379 | | * |
| 380 | | * fs.readFile('foo.txt', function (err, content) { |
| 381 | | * if (err) { |
| 382 | | * return ep.emit('error', err); |
| 383 | | * } |
| 384 | | * ep.emit('content', content); |
| 385 | | * }); |
| 386 | | * ``` |
| 387 | | * |
| 388 | | * @param {Function|String} handler, success callback or event name will be emit after callback. |
| 389 | | * @return {Function} |
| 390 | | */ |
| 391 | 1 | EventProxy.prototype.done = function (handler) { |
| 392 | 9 | var that = this; |
| 393 | 9 | return function (err, data) { |
| 394 | 10 | if (err) { |
| 395 | 1 | return that.emit('error', err); |
| 396 | | } |
| 397 | | |
| 398 | | // getAsync(query, ep.done('query')); |
| 399 | 9 | if (typeof handler === 'string') { |
| 400 | 6 | return that.emit(handler, data); |
| 401 | | } |
| 402 | | |
| 403 | | // speed improve for mostly case: `callback(err, data)` |
| 404 | 3 | if (arguments.length <= 2) { |
| 405 | 2 | return handler(data); |
| 406 | | } |
| 407 | | |
| 408 | | // callback(err, args1, args2, ...) |
| 409 | 1 | var args = Array.prototype.slice.call(arguments, 1); |
| 410 | 1 | handler.apply(null, args); |
| 411 | | }; |
| 412 | | }; |
| 413 | | |
| 414 | | /** |
| 415 | | * Create a new EventProxy |
| 416 | | * Examples: |
| 417 | | * ``` |
| 418 | | * var ep = EventProxy.create(); |
| 419 | | * ep.assign('user', 'articles', function(user, articles) { |
| 420 | | * // do something... |
| 421 | | * }); |
| 422 | | * // or one line ways: Create EventProxy and Assign |
| 423 | | * var ep = EventProxy.create('user', 'articles', function(user, articles) { |
| 424 | | * // do something... |
| 425 | | * }); |
| 426 | | * |
| 427 | | * @returns {EventProxy} EventProxy instance |
| 428 | | */ |
| 429 | | |
| 430 | 1 | EventProxy.create = function () { |
| 431 | 18 | var ep = new EventProxy(); |
| 432 | 18 | var args = Array.prototype.concat.apply([], arguments); |
| 433 | 18 | if (args.length) { |
| 434 | 4 | var errorHandler = args[args.length - 1]; |
| 435 | 4 | var callback = args[args.length - 2]; |
| 436 | 4 | if (typeof errorHandler === 'function' && typeof callback === 'function') { |
| 437 | 2 | args.pop(); |
| 438 | 2 | ep.fail(errorHandler); |
| 439 | | } |
| 440 | 4 | ep.assign.apply(ep, Array.prototype.slice.call(args)); |
| 441 | | } |
| 442 | 18 | return ep; |
| 443 | | }; |
| 444 | | |
| 445 | | // Backwards compatibility |
| 446 | 1 | EventProxy.EventProxy = EventProxy; |
| 447 | | |
| 448 | 1 | return EventProxy; |
| 449 | | }); |