|
|
/* eslint-disable */
|
|
|
|
|
|
/*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
|
|
|
// FIXME:移除debug模块依赖
|
|
|
//module.exports = definition(require('debug')('eventproxy'));
|
|
|
module.exports = definition();
|
|
|
} 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;
|
|
|
});
|
|
|
// eslint- |
|
|
\ No newline at end of file |