Coverage

95%
170
163
7

eventproxy.js

95%
170
163
7
LineHitsSource
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 */
71;(function (name, definition) {
8 // this is considered "safe":
91 var hasDefine = typeof define === 'function',
10 // hasDefine = typeof define === 'function',
11 hasExports = typeof module !== 'undefined' && module.exports;
12
131 if (hasDefine) { // AMD Module or CMD Module
140 define(definition);
151 } else if (hasExports) { // Node.js Module
161 module.exports = definition();
17 } else { // Assign to common namespaces or simply the global object (window)
180 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 */
361 var EventProxy = function () {
3719 if (!(this instanceof EventProxy)) {
380 return new EventProxy();
39 }
4019 this._callbacks = {};
4119 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 */
501 EventProxy.prototype.addListener = function (ev, callback) {
5142 this._callbacks = this._callbacks || {};
5242 this._callbacks[ev] = this._callbacks[ev] || [];
5342 this._callbacks[ev].push(callback);
5442 return this;
55 };
56 /**
57 * `addListener` alias
58 */
591 EventProxy.prototype.bind = EventProxy.prototype.addListener;
60 /**
61 * `addListener` alias
62 */
631 EventProxy.prototype.on = EventProxy.prototype.addListener;
64 /**
65 * `addListener` alias
66 */
671 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 */
761 EventProxy.prototype.removeListener = function (ev, callback) {
7729 var calls = this._callbacks, i, l;
7829 if (!ev) {
791 this._callbacks = {};
8028 } else if (calls) {
8128 if (!callback) {
821 calls[ev] = [];
83 } else {
8427 var list = calls[ev];
8527 if (!list) {
861 return this;
87 }
8826 l = list.length;
8926 for (i = 0; i < l; i++) {
9026 if (callback === list[i]) {
9126 list[i] = null;
9226 break;
93 }
94 }
95 }
96 }
9728 return this;
98 };
99 /**
100 * `removeListener` alias
101 */
1021 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 */
1091 EventProxy.prototype.removeAllListeners = function (event) {
1100 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 */
1201 EventProxy.prototype.trigger = function (eventName, data) {
121113 var list, calls, ev, callback, args, i, l;
122113 var both = 2;
123113 if (!(calls = this._callbacks)) {
1240 return this;
125 }
126113 while (both--) {
127226 ev = both ? eventName : 'all';
128226 list = calls[ev];
129226 if (list) {
130136 for (i = 0, l = list.length; i < l; i++) {
131134 if (!(callback = list[i])) {
1329 list.splice(i, 1); i--; l--;
133 } else {
134125 args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
135125 callback.apply(this, args);
136 }
137 }
138 }
139 }
140113 return this;
141 };
142 /**
143 * `trigger` alias
144 */
1451 EventProxy.prototype.emit = EventProxy.prototype.trigger;
146 /**
147 * `trigger` alias
148 */
1491 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 */
1561 EventProxy.prototype.once = function (ev, callback) {
15722 var self = this;
15822 var wrapper = function () {
15919 callback.apply(self, arguments);
16019 self.unbind(ev, wrapper);
161 };
16222 this.bind(ev, wrapper);
16322 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 */
1721 EventProxy.prototype.immediate = function (ev, callback, data) {
1732 this.bind(ev, callback);
1742 this.trigger(ev, data);
1752 return this;
176 };
177 /**
178 * `immediate` alias
179 */
1801 EventProxy.prototype.asap = EventProxy.prototype.immediate;
181
1821 var _assign = function (eventname1, eventname2, cb, once) {
1838 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.
1888 if (argsLength < 3) {
1890 return this;
190 }
191
1928 events = Array.prototype.slice.apply(arguments, [0, argsLength - 2]);
1938 callback = arguments[argsLength - 2];
1948 isOnce = arguments[argsLength - 1];
195
196 // Check the callback type.
1978 if (typeof callback !== "function") {
1980 return this;
199 }
200
2018 length = events.length;
2028 bind = function (key) {
20319 var method = isOnce ? "once" : "bind";
20419 proxy[method](key, function (data) {
20519 proxy._fired[key] = proxy._fired[key] || {};
20619 proxy._fired[key].data = data;
20719 if (!flag[key]) {
20818 flag[key] = true;
20918 times++;
210 }
211 });
212 };
213
2148 for (index = 0; index < length; index++) {
21519 bind(events[index]);
216 }
217
2188 _all = function (event) {
21921 if (times < length) {
22012 return;
221 }
2229 if (!flag[event]) {
2231 return;
224 }
2258 var data = [];
2268 for (index = 0; index < length; index++) {
22718 data.push(proxy._fired[events[index]].data);
228 }
2298 if (isOnce) {
2306 proxy.unbind("all", _all);
231 }
2328 callback.apply(null, data);
233 };
2348 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 */
2491 EventProxy.prototype.all = function (eventname1, eventname2, callback) {
2507 var args = Array.prototype.concat.apply([], arguments);
2517 args.push(true);
2527 _assign.apply(this, args);
2537 return this;
254 };
255 /**
256 * `all` alias
257 */
2581 EventProxy.prototype.assign = EventProxy.prototype.all;
259
260 /**
261 * Assign the only one 'error' event handler.
262 * @param {Function(err)} callback
263 */
2641 EventProxy.prototype.fail = function (callback) {
2653 var that = this;
2663 that.once('error', function (err) {
2671 that.unbind();
2681 callback(err);
269 });
2703 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 */
2861 EventProxy.prototype.tail = function () {
2871 var args = Array.prototype.concat.apply([], arguments);
2881 args.push(false);
2891 _assign.apply(this, args);
2901 return this;
291 };
292 /**
293 * `tail` alias
294 */
2951 EventProxy.prototype.assignAll = EventProxy.prototype.tail;
296 /**
297 * `tail` alias
298 */
2991 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 */
3071 EventProxy.prototype.after = function (eventName, times, callback) {
3083 if (times === 0) {
3091 callback.call(null, []);
3101 return this;
311 }
3122 var proxy = this,
313 firedData = [],
314 all;
3152 all = function (name, data) {
31665 if (name === eventName) {
31765 times--;
31865 firedData.push(data);
31965 if (times < 1) {
3202 proxy.unbind("all", all);
3212 callback.apply(null, [firedData]);
322 }
323 }
324 };
3252 proxy.bind("all", all);
3262 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 */
3351 EventProxy.prototype.any = function () {
3361 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
3451 proxy.once(_eventName, callback);
346
3471 _bind = function (key) {
3482 proxy.bind(key, function (data) {
3493 proxy.trigger(_eventName, {"data": data, eventName: key});
350 });
351 };
352
3531 for (index = 0; index < count; index++) {
3542 _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 */
3631 EventProxy.prototype.not = function (eventName, callback) {
3641 var proxy = this;
3651 proxy.bind("all", function (name, data) {
3663 if (name !== eventName) {
3672 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 */
3911 EventProxy.prototype.done = function (handler) {
3929 var that = this;
3939 return function (err, data) {
39410 if (err) {
3951 return that.emit('error', err);
396 }
397
398 // getAsync(query, ep.done('query'));
3999 if (typeof handler === 'string') {
4006 return that.emit(handler, data);
401 }
402
403 // speed improve for mostly case: `callback(err, data)`
4043 if (arguments.length <= 2) {
4052 return handler(data);
406 }
407
408 // callback(err, args1, args2, ...)
4091 var args = Array.prototype.slice.call(arguments, 1);
4101 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
4301 EventProxy.create = function () {
43118 var ep = new EventProxy();
43218 var args = Array.prototype.concat.apply([], arguments);
43318 if (args.length) {
4344 var errorHandler = args[args.length - 1];
4354 var callback = args[args.length - 2];
4364 if (typeof errorHandler === 'function' && typeof callback === 'function') {
4372 args.pop();
4382 ep.fail(errorHandler);
439 }
4404 ep.assign.apply(ep, Array.prototype.slice.call(args));
441 }
44218 return ep;
443 };
444
445 // Backwards compatibility
4461 EventProxy.EventProxy = EventProxy;
447
4481 return EventProxy;
449});