/*global define*/
!(function (name, definition) {
// Check define
var hasDefine = typeof define === 'function',
// Check exports
hasExports = typeof module !== 'undefined' && module.exports;
if (hasDefine) {
// AMD Module or CMD Module
define('eventproxy_debug', function () {return function () {};});
define(['eventproxy_debug'], definition);
} else if (hasExports) {
// Node.js Module
module.exports = definition(require('debug')('eventproxy'));
} else {
// Assign to common namespaces or simply the global object (window)
this[name] = definition();
}
})('EventProxy', function (debug) {
debug = debug || function () {};
/*!
* refs
*/
var SLICE = Array.prototype.slice;
var CONCAT = Array.prototype.concat;
var ALL_EVENT = '__all__';
/**
* EventProxy. An implementation of task/event based asynchronous pattern.
* A module that can be mixed in to *any object* in order to provide it with custom events.
* You may `bind` or `unbind` a callback function to an event;
* `trigger`-ing an event fires all callbacks in succession.
* Examples:
* ```js
* var render = function (template, resources) {};
* var proxy = new EventProxy();
* proxy.assign("template", "l10n", render);
* proxy.trigger("template", template);
* proxy.trigger("l10n", resources);
* ```
*/
var EventProxy = function () {
if (!(this instanceof EventProxy)) {
return new EventProxy();
}
this._callbacks = {};
this._fired = {};
};
/**
* Bind an event, specified by a string name, `ev`, to a `callback` function.
* Passing __ALL_EVENT__ will bind the callback to all events fired.
* Examples:
* ```js
* var proxy = new EventProxy();
* proxy.addListener("template", function (event) {
* // TODO
* });
* ```
* @param {String} eventname Event name.
* @param {Function} callback Callback.
*/
EventProxy.prototype.addListener = function (ev, callback) {
debug('Add listener for %s', ev);
this._callbacks[ev] = this._callbacks[ev] || [];
this._callbacks[ev].push(callback);
return this;
};
/**
* `addListener` alias, `bind`
*/
EventProxy.prototype.bind = EventProxy.prototype.addListener;
/**
* `addListener` alias, `on`
*/
EventProxy.prototype.on = EventProxy.prototype.addListener;
/**
* `addListener` alias, `subscribe`
*/
EventProxy.prototype.subscribe = EventProxy.prototype.addListener;
/**
* Bind an event, but put the callback into head of all callbacks.
* @param {String} eventname Event name.
* @param {Function} callback Callback.
*/
EventProxy.prototype.headbind = function (ev, callback) {
debug('Add listener for %s', ev);
this._callbacks[ev] = this._callbacks[ev] || [];
this._callbacks[ev].unshift(callback);
return this;
};
/**
* Remove one or many callbacks.
*
* - If `callback` is null, removes all callbacks for the event.
* - If `eventname` is null, removes all bound callbacks for all events.
* @param {String} eventname Event name.
* @param {Function} callback Callback.
*/
EventProxy.prototype.removeListener = function (eventname, callback) {
var calls = this._callbacks;
if (!eventname) {
debug('Remove all listeners');
this._callbacks = {};
} else {
if (!callback) {
debug('Remove all listeners of %s', eventname);
calls[eventname] = [];
} else {
var list = calls[eventname];
if (list) {
var l = list.length;
for (var i = 0; i < l; i++) {
if (callback === list[i]) {
debug('Remove a listener of %s', eventname);
list[i] = null;
}
}
}
}
}
return this;
};
/**
* `removeListener` alias, unbind
*/
EventProxy.prototype.unbind = EventProxy.prototype.removeListener;
/**
* Remove all listeners. It equals unbind()
* Just add this API for as same as Event.Emitter.
* @param {String} event Event name.
*/
EventProxy.prototype.removeAllListeners = function (event) {
return this.unbind(event);
};
/**
* Bind the ALL_EVENT event
*/
EventProxy.prototype.bindForAll = function (callback) {
this.bind(ALL_EVENT, callback);
};
/**
* Unbind the ALL_EVENT event
*/
EventProxy.prototype.unbindForAll = function (callback) {
this.unbind(ALL_EVENT, callback);
};
/**
* Trigger an event, firing all bound callbacks. Callbacks are passed the
* same arguments as `trigger` is, apart from the event name.
* Listening for `"all"` passes the true event name as the first argument.
* @param {String} eventname Event name
* @param {Mix} data Pass in data
*/
EventProxy.prototype.trigger = function (eventname, data) {
var list, ev, callback, i, l;
var both = 2;
var calls = this._callbacks;
debug('Emit event %s with data %j', eventname, data);
while (both--) {
ev = both ? eventname : ALL_EVENT;
list = calls[ev];
if (list) {
for (i = 0, l = list.length; i < l; i++) {
if (!(callback = list[i])) {
list.splice(i, 1);
i--;
l--;
} else {
var args = [];
var start = both ? 1 : 0;
for (var j = start; j < arguments.length; j++) {
args.push(arguments[j]);
}
callback.apply(this, args);
}
}
}
}
return this;
};
/**
* `trigger` alias
*/
EventProxy.prototype.emit = EventProxy.prototype.trigger;
/**
* `trigger` alias
*/
EventProxy.prototype.fire = EventProxy.prototype.trigger;
/**
* Bind an event like the bind method, but will remove the listener after it was fired.
* @param {String} ev Event name
* @param {Function} callback Callback
*/
EventProxy.prototype.once = function (ev, callback) {
var self = this;
var wrapper = function () {
callback.apply(self, arguments);
self.unbind(ev, wrapper);
};
this.bind(ev, wrapper);
return this;
};
var later = (typeof setImmediate !== 'undefined' && setImmediate) ||
(typeof process !== 'undefined' && process.nextTick) || function (fn) {
setTimeout(fn, 0);
};
/**
* emitLater
* make emit async
*/
EventProxy.prototype.emitLater = function () {
var self = this;
var args = arguments;
later(function () {
self.trigger.apply(self, args);
});
};
/**
* Bind an event, and trigger it immediately.
* @param {String} ev Event name.
* @param {Function} callback Callback.
* @param {Mix} data The data that will be passed to calback as arguments.
*/
EventProxy.prototype.immediate = function (ev, callback, data) {
this.bind(ev, callback);
this.trigger(ev, data);
return this;
};
/**
* `immediate` alias
*/
EventProxy.prototype.asap = EventProxy.prototype.immediate;
var _assign = function (eventname1, eventname2, cb, once) {
var proxy = this;
var argsLength = arguments.length;
var times = 0;
var flag = {};
// Check the arguments length.
if (argsLength < 3) {
return this;
}
var events = SLICE.call(arguments, 0, -2);
var callback = arguments[argsLength - 2];
var isOnce = arguments[argsLength - 1];
// Check the callback type.
if (typeof callback !== "function") {
return this;
}
debug('Assign listener for events %j, once is %s', events, !!isOnce);
var bind = function (key) {
var method = isOnce ? "once" : "bind";
proxy[method](key, function (data) {
proxy._fired[key] = proxy._fired[key] || {};
proxy._fired[key].data = data;
if (!flag[key]) {
flag[key] = true;
times++;
}
});
};
var length = events.length;
for (var index = 0; index < length; index++) {
bind(events[index]);
}
var _all = function (event) {
if (times < length) {
return;
}
if (!flag[event]) {
return;
}
var data = [];
for (var index = 0; index < length; index++) {
data.push(proxy._fired[events[index]].data);
}
if (isOnce) {
proxy.unbindForAll(_all);
}
debug('Events %j all emited with data %j', events, data);
callback.apply(null, data);
};
proxy.bindForAll(_all);
};
/**
* Assign some events, after all events were fired, the callback will be executed once.
*
* Examples:
* ```js
* proxy.all(ev1, ev2, callback);
* proxy.all([ev1, ev2], callback);
* proxy.all(ev1, [ev2, ev3], callback);
* ```
* @param {String} eventname1 First event name.
* @param {String} eventname2 Second event name.
* @param {Function} callback Callback, that will be called after predefined events were fired.
*/
EventProxy.prototype.all = function (eventname1, eventname2, callback) {
var args = CONCAT.apply([], arguments);
args.push(true);
_assign.apply(this, args);
return this;
};
/**
* `all` alias
*/
EventProxy.prototype.assign = EventProxy.prototype.all;
/**
* Assign the only one 'error' event handler.
* @param {Function(err)} callback
*/
EventProxy.prototype.fail = function (callback) {
var that = this;
that.once('error', function () {
that.unbind();
// put all arguments to the error handler
// fail(function(err, args1, args2, ...){})
callback.apply(null, arguments);
});
return this;
};
/**
* A shortcut of ep#emit('error', err)
*/
EventProxy.prototype.throw = function () {
var that = this;
that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
};
/**
* Assign some events, after all events were fired, the callback will be executed first time.
* Then any event that predefined be fired again, the callback will executed with the newest data.
* Examples:
* ```js
* proxy.tail(ev1, ev2, callback);
* proxy.tail([ev1, ev2], callback);
* proxy.tail(ev1, [ev2, ev3], callback);
* ```
* @param {String} eventname1 First event name.
* @param {String} eventname2 Second event name.
* @param {Function} callback Callback, that will be called after predefined events were fired.
*/
EventProxy.prototype.tail = function () {
var args = CONCAT.apply([], arguments);
args.push(false);
_assign.apply(this, args);
return this;
};
/**
* `tail` alias, assignAll
*/
EventProxy.prototype.assignAll = EventProxy.prototype.tail;
/**
* `tail` alias, assignAlways
*/
EventProxy.prototype.assignAlways = EventProxy.prototype.tail;
/**
* The callback will be executed after the event be fired N times.
* @param {String} eventname Event name.
* @param {Number} times N times.
* @param {Function} callback Callback, that will be called after event was fired N times.
*/
EventProxy.prototype.after = function (eventname, times, callback) {
if (times === 0) {
callback.call(null, []);
return this;
}
var proxy = this,
firedData = [];
this._after = this._after || {};
var group = eventname + '_group';
this._after[group] = {
index: 0,
results: []
};
debug('After emit %s times, event %s\'s listenner will execute', times, eventname);
var all = function (name, data) {
if (name === eventname) {
times--;
firedData.push(data);
if (times < 1) {
debug('Event %s was emit %s, and execute the listenner', eventname, times);
proxy.unbindForAll(all);
callback.apply(null, [firedData]);
}
}
if (name === group) {
times--;
proxy._after[group].results[data.index] = data.result;
if (times < 1) {
debug('Event %s was emit %s, and execute the listenner', eventname, times);
proxy.unbindForAll(all);
callback.call(null, proxy._after[group].results);
}
}
};
proxy.bindForAll(all);
return this;
};
/**
* The `after` method's helper. Use it will return ordered results.
* If you need manipulate result, you need callback
* Examples:
* ```js
* var ep = new EventProxy();
* ep.after('file', files.length, function (list) {
* // Ordered results
* });
* for (var i = 0; i < files.length; i++) {
* fs.readFile(files[i], 'utf-8', ep.group('file'));
* }
* ```
* @param {String} eventname Event name, shoule keep consistent with `after`.
* @param {Function} callback Callback function, should return the final result.
*/
EventProxy.prototype.group = function (eventname, callback) {
var that = this;
var group = eventname + '_group';
var index = that._after[group].index;
that._after[group].index++;
return function (err, data) {
if (err) {
// put all arguments to the error handler
return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
}
that.emit(group, {
index: index,
// callback(err, args1, args2, ...)
result: callback ? callback.apply(null, SLICE.call(arguments, 1)) : data
});
};
};
/**
* The callback will be executed after any registered event was fired. It only executed once.
* @param {String} eventname1 Event name.
* @param {String} eventname2 Event name.
* @param {Function} callback The callback will get a map that has data and eventname attributes.
*/
EventProxy.prototype.any = function () {
var proxy = this,
callback = arguments[arguments.length - 1],
events = SLICE.call(arguments, 0, -1),
_eventname = events.join("_");
debug('Add listenner for Any of events %j emit', events);
proxy.once(_eventname, callback);
var _bind = function (key) {
proxy.bind(key, function (data) {
debug('One of events %j emited, execute the listenner');
proxy.trigger(_eventname, {"data": data, eventName: key});
});
};
for (var index = 0; index < events.length; index++) {
_bind(events[index]);
}
};
/**
* The callback will be executed when the event name not equals with assigned event.
* @param {String} eventname Event name.
* @param {Function} callback Callback.
*/
EventProxy.prototype.not = function (eventname, callback) {
var proxy = this;
debug('Add listenner for not event %s', eventname);
proxy.bindForAll(function (name, data) {
if (name !== eventname) {
debug('listenner execute of event %s emit, but not event %s.', name, eventname);
callback(data);
}
});
};
/**
* Success callback wrapper, will handler err for you.
*
* ```js
* fs.readFile('foo.txt', ep.done('content'));
*
* // equal to =>
*
* fs.readFile('foo.txt', function (err, content) {
* if (err) {
* return ep.emit('error', err);
* }
* ep.emit('content', content);
* });
* ```
*
* ```js
* fs.readFile('foo.txt', ep.done('content', function (content) {
* return content.trim();
* }));
*
* // equal to =>
*
* fs.readFile('foo.txt', function (err, content) {
* if (err) {
* return ep.emit('error', err);
* }
* ep.emit('content', content.trim());
* });
* ```
* @param {Function|String} handler, success callback or event name will be emit after callback.
* @return {Function}
*/
EventProxy.prototype.done = function (handler, callback) {
var that = this;
return function (err, data) {
if (err) {
// put all arguments to the error handler
return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
}
// callback(err, args1, args2, ...)
var args = SLICE.call(arguments, 1);
if (typeof handler === 'string') {
// getAsync(query, ep.done('query'));
// or
// getAsync(query, ep.done('query', function (data) {
// return data.trim();
// }));
if (callback) {
// only replace the args when it really return a result
return that.emit(handler, callback.apply(null, args));
} else {
// put all arguments to the done handler
//ep.done('some');
//ep.on('some', function(args1, args2, ...){});
return that.emit.apply(that, [handler].concat(args));
}
}
// speed improve for mostly case: `callback(err, data)`
if (arguments.length <= 2) {
return handler(data);
}
// callback(err, args1, args2, ...)
handler.apply(null, args);
};
};
/**
* make done async
* @return {Function} delay done
*/
EventProxy.prototype.doneLater = function (handler, callback) {
var _doneHandler = this.done(handler, callback);
return function (err, data) {
var args = arguments;
later(function () {
_doneHandler.apply(null, args);
});
};
};
/**
* Create a new EventProxy
* Examples:
* ```js
* var ep = EventProxy.create();
* ep.assign('user', 'articles', function(user, articles) {
* // do something...
* });
* // or one line ways: Create EventProxy and Assign
* var ep = EventProxy.create('user', 'articles', function(user, articles) {
* // do something...
* });
* ```
* @return {EventProxy} EventProxy instance
*/
EventProxy.create = function () {
var ep = new EventProxy();
var args = CONCAT.apply([], arguments);
if (args.length) {
var errorHandler = args[args.length - 1];
var callback = args[args.length - 2];
if (typeof errorHandler === 'function' && typeof callback === 'function') {
args.pop();
ep.fail(errorHandler);
}
ep.assign.apply(ep, args);
}
return ep;
};
// Backwards compatibility
EventProxy.EventProxy = EventProxy;
return EventProxy;
});
网友评论