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