1 /*global exports */ 2 /** 3 * @fileoverview 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 (function () { 8 /** 9 * @description EventProxy. A module that can be mixed in to *any object* in order to provide it with 10 * custom events. You may `bind` or `unbind` a callback function to an event; 11 * `trigger`-ing an event fires all callbacks in succession. 12 * @constructor 13 * @name EventProxy 14 * @class EventProxy. An implementation of task/event based asynchronous pattern. 15 * @example 16 * var render = function (template, resources) {}; 17 * var proxy = new EventProxy(); 18 * proxy.assign("template", "l10n", render); 19 * proxy.trigger("template", template); 20 * proxy.trigger("l10n", resources); 21 */ 22 var EventProxy = function () { 23 if (!(this instanceof EventProxy)) { 24 return new EventProxy(); 25 } 26 this._callbacks = {}; 27 this._fired = {}; 28 }; 29 30 /** 31 * @description Bind an event, specified by a string name, `ev`, to a `callback` function. 32 * Passing `"all"` will bind the callback to all events fired. 33 * @memberOf EventProxy# 34 * @param {string} eventName Event name. 35 * @param {function} callback Callback. 36 */ 37 EventProxy.prototype.addListener = function (ev, callback) { 38 this._callbacks = this._callbacks || {}; 39 this._callbacks[ev] = this._callbacks[ev] || []; 40 this._callbacks[ev].push(callback); 41 return this; 42 }; 43 EventProxy.prototype.bind = EventProxy.prototype.addListener; 44 EventProxy.prototype.on = EventProxy.prototype.addListener; 45 EventProxy.prototype.await = EventProxy.prototype.addListener; 46 47 /** 48 * @description Remove one or many callbacks. If `callback` is null, removes all 49 * callbacks for the event. If `ev` is null, removes all bound callbacks 50 * for all events. 51 * @memberOf EventProxy# 52 * @param {string} eventName Event name. 53 * @param {function} callback Callback. 54 */ 55 EventProxy.prototype.removeListener = function (ev, callback) { 56 var calls = this._callbacks, i, l; 57 if (!ev) { 58 this._callbacks = {}; 59 } else if (calls) { 60 if (!callback) { 61 calls[ev] = []; 62 } else { 63 var list = calls[ev]; 64 if (!list) { 65 return this; 66 } 67 l = list.length; 68 for (i = 0; i < l; i++) { 69 if (callback === list[i]) { 70 list[i] = null; 71 break; 72 } 73 } 74 } 75 } 76 return this; 77 }; 78 EventProxy.prototype.unbind = EventProxy.prototype.removeListener; 79 80 /** 81 * @description Remove all listeners. 82 * It equals unbind(); Just add this API for as same as Event.Emitter. 83 * @memberOf EventProxy# 84 * @param {string} event Event name. 85 */ 86 EventProxy.prototype.removeAllListeners = function (event) { 87 return this.unbind(event); 88 }; 89 90 /** 91 * @description Trigger an event, firing all bound callbacks. Callbacks are passed the 92 * same arguments as `trigger` is, apart from the event name. 93 * Listening for `"all"` passes the true event name as the first argument. 94 * @param {string} eventName Event name. 95 * @param {mix} data Pass in data. 96 */ 97 EventProxy.prototype.trigger = function (eventName, data) { 98 var list, calls, ev, callback, args, i, l; 99 var both = 2; 100 if (!(calls = this._callbacks)) { 101 return this; 102 } 103 while (both--) { 104 ev = both ? eventName : 'all'; 105 list = calls[ev]; 106 if (list) { 107 for (i = 0, l = list.length; i < l; i++) { 108 if (!(callback = list[i])) { 109 list.splice(i, 1); i--; l--; 110 } else { 111 args = both ? Array.prototype.slice.call(arguments, 1) : arguments; 112 callback.apply(this, args); 113 } 114 } 115 } 116 } 117 return this; 118 }; 119 EventProxy.prototype.emit = EventProxy.prototype.trigger; 120 EventProxy.prototype.fire = EventProxy.prototype.trigger; 121 122 /** 123 * @description Bind an event like the bind method, but will remove the listener after it was fired. 124 * @param {string} ev Event name. 125 * @param {function} callback Callback. 126 */ 127 EventProxy.prototype.once = function (ev, callback) { 128 var self = this, 129 wrapper = function () { 130 callback.apply(self, arguments); 131 self.unbind(ev, wrapper); 132 }; 133 this.bind(ev, wrapper); 134 return this; 135 }; 136 137 /** 138 * @description Bind an event, and trigger it immediately. 139 * @param {string} ev Event name. 140 * @param {function} callback Callback. 141 * @param {mix} data The data that will be passed to calback as arguments. 142 */ 143 EventProxy.prototype.immediate = function (ev, callback, data) { 144 this.bind(ev, callback); 145 this.trigger(ev, data); 146 return this; 147 }; 148 149 var _assign = function (eventname1, eventname2, cb, once) { 150 var proxy = this, length, index = 0, argsLength = arguments.length, 151 bind, _all, 152 callback, events, isOnce, times = 0, flag = {}; 153 154 // Check the arguments length. 155 if (argsLength < 3) { 156 return this; 157 } 158 159 events = Array.prototype.slice.apply(arguments, [0, argsLength - 2]); 160 callback = arguments[argsLength - 2]; 161 isOnce = arguments[argsLength - 1]; 162 163 // Check the callback type. 164 if (typeof callback !== "function") { 165 return this; 166 } 167 168 length = events.length; 169 bind = function (key) { 170 var method = isOnce ? "once" : "bind"; 171 proxy[method](key, function (data) { 172 proxy._fired[key] = proxy._fired[key] || {}; 173 proxy._fired[key].data = data; 174 if (!flag[key]) { 175 flag[key] = true; 176 times++; 177 } 178 }); 179 }; 180 181 for (index = 0; index < length; index++) { 182 bind(events[index]); 183 } 184 185 _all = function () { 186 if (times < length) { 187 return; 188 } 189 var data = []; 190 for (index = 0; index < length; index++) { 191 data.push(proxy._fired[events[index]].data); 192 } 193 if (isOnce) { 194 proxy.unbind("all", _all); 195 } 196 callback.apply(null, data); 197 }; 198 proxy.bind("all", _all); 199 }; 200 201 /** 202 * @description Assign some events, after all events were fired, the callback will be executed once. 203 * @example 204 * proxy.all(ev1, ev2, callback); 205 * proxy.all([ev1, ev2], callback); 206 * proxy.all(ev1, [ev2, ev3], callback); 207 * @param {string} eventName1 First event name. 208 * @param {string} eventName2 Second event name. 209 * @param {function} callback Callback, that will be called after predefined events were fired. 210 */ 211 EventProxy.prototype.all = function (eventname1, eventname2, cb) { 212 var args = Array.prototype.concat.apply([], arguments); 213 args.push(true); 214 _assign.apply(this, args); 215 return this; 216 }; 217 EventProxy.prototype.assign = EventProxy.prototype.all; 218 219 /** 220 * @description Assign some events, after all events were fired, the callback will be executed first time. 221 * then any event that predefined be fired again, the callback will executed with the newest data. 222 * @example 223 * proxy.tail(ev1, ev2, callback); 224 * proxy.tail([ev1, ev2], callback); 225 * proxy.tail(ev1, [ev2, ev3], callback); 226 * @memberOf EventProxy# 227 * @param {string} eventName1 First event name. 228 * @param {string} eventName2 Second event name. 229 * @param {function} callback Callback, that will be called after predefined events were fired. 230 */ 231 EventProxy.prototype.tail = function () { 232 var args = Array.prototype.concat.apply([], arguments); 233 args.push(false); 234 _assign.apply(this, args); 235 return this; 236 }; 237 EventProxy.prototype.assignAll = EventProxy.prototype.tail; 238 EventProxy.prototype.assignAlways = EventProxy.prototype.tail; 239 240 /** 241 * @description The callback will be executed after the event be fired N times. 242 * @memberOf EventProxy# 243 * @param {string} eventName Event name. 244 * @param {number} times N times. 245 * @param {function} callback Callback, that will be called after event was fired N times. 246 */ 247 EventProxy.prototype.after = function (eventName, times, callback) { 248 var proxy = this, 249 firedData = [], 250 all; 251 all = function (name, data) { 252 if (name === eventName) { 253 times--; 254 firedData.push(data); 255 if (times < 1) { 256 proxy.unbind("all", all); 257 callback.apply(null, [firedData]); 258 } 259 } 260 }; 261 proxy.bind("all", all); 262 return this; 263 }; 264 265 /** 266 * @description The callback will be executed after any registered event was fired. It only executed once. 267 * @memberOf EventProxy# 268 * @param {string} eventName1 Event name. 269 * @param {string} eventName2 Event name. 270 * @param {function} callback The callback will get a map that has data and eventName attributes. 271 */ 272 EventProxy.prototype.any = function () { 273 var proxy = this, 274 index, 275 _bind, 276 len = arguments.length, 277 callback = arguments[len - 1], 278 events = Array.prototype.slice.apply(arguments, [0, len - 1]), 279 count = events.length, 280 _eventName = events.join("_"); 281 282 proxy.once(_eventName, callback); 283 284 _bind = function (key) { 285 proxy.bind(key, function (data) { 286 proxy.trigger(_eventName, {"data": data, eventName: key}); 287 }); 288 }; 289 290 for (index = 0; index < count; index++) { 291 _bind(events[index]); 292 } 293 }; 294 295 /** 296 * @description The callback will be executed when the evnet name not equals with assigned evnet. 297 * @memberOf EventProxy# 298 * @param {string} eventName Event name. 299 * @param {function} callback Callback. 300 */ 301 EventProxy.prototype.not = function (eventName, callback) { 302 var proxy = this; 303 proxy.bind("all", function (name, data) { 304 if (name !== eventName) { 305 callback(data); 306 } 307 }); 308 }; 309 310 /** 311 * Create a new EventProxy 312 * @example 313 * var ep = EventProxy.create(); 314 * ep.assign('user', 'articles', function(user, articles) { 315 * // do something... 316 * }); 317 * 318 * // or one line ways: Create EventProxy and Assign 319 * 320 * var ep = EventProxy.create('user', 'articles', function(user, articles) { 321 * // do something... 322 * }); 323 * 324 * @returns {EventProxy} 325 */ 326 EventProxy.create = function () { 327 var ep = new EventProxy(); 328 if (arguments.length) { 329 ep.assign.apply(ep, Array.prototype.slice.call(arguments)); 330 } 331 return ep; 332 }; 333 334 // Event proxy can be used in browser and Nodejs both. 335 if (typeof exports !== "undefined") { 336 exports.EventProxy = EventProxy; 337 } else { 338 this.EventProxy = EventProxy; 339 } 340 341 }()); 342