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