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