emitter-b
Version:
An enhanced EventEmitter with extra methods for detecting whether an event has any handlers or not for efficient event handler attachment.
213 lines (184 loc) • 7.76 kB
JavaScript
var EventEmitter = require('events').EventEmitter
var proto = require("proto")
module.exports = proto(EventEmitter, function(superclass) {
this.init = function() {
superclass.apply(this, arguments)
this.ifonHandlers = {}
this.ifoffHandlers = {}
this.ifonAllHandlers = []
this.ifoffAllHandlers = []
}
// callback will be triggered immediately if there is already a listener attached, or
// callback will be triggered when the first listener for the event is added
// (regardless of whether its done through on or once)
// parameters can be:
// event, callback - attach an ifon handler for the passed event
// callback - attach an ifon handler for all events
this.ifon = function(event, callback) {
if(event instanceof Function) { // event not passed, only a callback
callback = event // fix the argument
for(var eventName in this._events) {
if(this.listeners(eventName).length > 0) {
callback(eventName)
}
}
} else if(this.listeners(event).length > 0) {
callback(event)
}
addHandlerToList(this, 'ifonHandlers', event, callback)
}
// removes either:
// removeIfon() - all ifon handlers (if no arguments are passed), or
// removeIfon(event) - all ifon handlers for the passed event, or
// removeIfon(callback) - the passed ifon-all handler (if the first parameter is the callback)
// removeIfon(event, callback) - the specific passed callback for the passed event
this.removeIfon = function(event, callback) {
removeFromHandlerList(this, 'ifonHandlers', event, callback)
}
// callback will be triggered when the last listener for the 'click' event is removed (will not trigger immediately if there is no event listeners on call of ifoff)
// (regardless of whether this is done through removeListener or as a result of 'once' being fulfilled)
// parameters can be:
// event, callback - attach an ifoff handler for the passed event
// callback - attach an ifoff handler for all events
this.ifoff = function(event, callback) {
addHandlerToList(this, 'ifoffHandlers', event, callback)
}
// removes either:
// removeIfoff() - all ifoff handlers (if no arguments are passed), or
// removeIfoff(event) - all ifoff handlers for the passed event, or
// removeIfoff(callback) - the passed ifoff-all handler (if the first parameter is the callback)
// removeIfoff(event, callback) - the specific passed callback for the passed event
this.removeIfoff = function(event, callback) {
removeFromHandlerList(this, 'ifoffHandlers', event, callback)
}
// emitter is the emitter to proxy handler binding to
// options can have one of the following properties:
// only - an array of events to proxy
// except - an array of events to *not* proxy
this.proxy = function(emitter, options) {
if(options === undefined) options = {}
if(options.except !== undefined) {
var except = arrayToMap(options.except)
var handleIt = function(event){return !(event in except)}
} else if(options.only !== undefined) {
var only = arrayToMap(options.only)
var handleIt = function(event){return event in only}
} else {
var handleIt = function(){return true}
}
var that = this, handler;
this.ifon(function(event) {
if(handleIt(event)) {
emitter.on(event, handler = function() {
that.emit.apply(that, [event].concat(Array.prototype.slice.call(arguments)))
})
}
})
this.ifoff(function(event) {
if(handleIt(event))
emitter.off(event, handler)
})
}
/*override*/ this.on = this.addListener = function(event, callback) {
var triggerIfOn = this.listeners(event).length === 0
superclass.prototype.on.apply(this,arguments)
if(triggerIfOn) triggerIfHandlers(this, 'ifonHandlers', event)
}
/*override*/ this.off = this.removeListener = function(event, callback) {
var triggerIfOff = this.listeners(event).length === 1
superclass.prototype.removeListener.apply(this,arguments)
if(triggerIfOff) triggerIfHandlers(this, 'ifoffHandlers', event)
}
/*override*/ this.removeAllListeners = function(event) {
var triggerIfOffForEvents = []
if(event !== undefined) {
if(this.listeners(event).length > 0) {
triggerIfOffForEvents.push(event)
}
} else {
for(var event in this._events) {
if(this.listeners(event).length > 0) {
triggerIfOffForEvents.push(event)
}
}
}
superclass.prototype.removeAllListeners.apply(this,arguments)
for(var n=0; n<triggerIfOffForEvents.length; n++) {
triggerIfHandlers(this, 'ifoffHandlers', triggerIfOffForEvents[n])
}
}
})
// triggers the if handlers from the normal list and the "all" list
function triggerIfHandlers(that, handlerListName, event) {
triggerIfHandlerList(that[handlerListName][event], event)
triggerIfHandlerList(that[normalHandlerToAllHandlerProperty(handlerListName)], event)
}
// triggers the if handlers from a specific list
// ya these names are confusing, sorry : (
function triggerIfHandlerList(handlerList, event) {
if(handlerList !== undefined) {
for(var n=0; n<handlerList.length; n++) {
handlerList[n](event)
}
}
}
function addHandlerToList(that, handlerListName, event, callback) {
if(event instanceof Function) {
// correct arguments
callback = event
event = undefined
}
if(event !== undefined && callback !== undefined) {
var handlerList = that[handlerListName][event]
if(handlerList === undefined) {
handlerList = that[handlerListName][event] = []
}
handlerList.push(callback)
} else {
that[normalHandlerToAllHandlerProperty(handlerListName)].push(callback)
}
}
function removeFromHandlerList(that, handlerListName, event, callback) {
if(event instanceof Function) {
// correct arguments
callback = event
event = undefined
}
if(event !== undefined && callback !== undefined) {
removeCallbackFromList(that[handlerListName][event], callback)
} else if(event !== undefined) {
delete that[handlerListName][event]
} else if(callback !== undefined) {
var allHandlerListName = normalHandlerToAllHandlerProperty(handlerListName)
removeCallbackFromList(that[allHandlerListName], callback)
} else {
var allHandlerListName = normalHandlerToAllHandlerProperty(handlerListName)
that[handlerListName] = {}
that[allHandlerListName] = []
}
}
function normalHandlerToAllHandlerProperty(handlerListName) {
if(handlerListName === 'ifonHandlers')
return 'ifonAllHandlers'
if(handlerListName === 'ifoffHandlers')
return 'ifoffAllHandlers'
}
function removeCallbackFromList(list, callback) {
var index = list.indexOf(callback)
list.splice(index,1)
}
function getTrace() {
try {
throw new Error()
} catch(e) {
return e
}
}
// turns an array of values into a an object where those values are all keys that point to 'true'
function arrayToMap(array) {
var result = {}
array.forEach(function(v) {
result[v] = true
})
return result
}