Personally, I'd simply wrap the addCustomEvent
and removeCustomEvent
to a single module, and keep an object that tracks the bound handlers. You consider this "a waste of resources", but really, the impact of this approach would be negligible.
The upsides are: you have the beginning of a module that can easily be expanded on, to handle more complex event handlers (like simulating a tab event for mobile devices using the touchstart
and touchend
events).
An alternative approach would be to unbind the event handler internally, depending on the event object itself.
Then, you'll have to re-write your removeCustomEvent
function to trigger a special event, that lets the bound handler know that you want to remove the event listener.
//in the wrappedHandler:
var wrappedHandler = function(e)
{
e = e || window.event;
if (e.synthetic === true)
{
e.preventDefault();
e.stopPropagation();
element.removeEventListener(eventName, wrappedHandler, useCapture);//<-- use closure vars
return e;//or return false.
}
//do normal event
handler.apply(this, [e]);//pass event object, and call handler in the same context!
};
var removeCustomEvent = function(event, node, capture)
{
var e, eClass,
doc = node.ownerDocument || (node.nodeType === (document.DOCUMENT_NODE || 9) ? node : document);
if (node.dispatchEvent)
{
if (event === 'click' || event.indexOf('mouse') >= 0)
eClass = 'MouseEvents';
else
eClass = 'HTMLEvents';
e = doc.createEvent(eClass);
e.initEvent(event, !(event === 'change'), true);
//THIS IS THE TRICK:
e.synthetic = true;
node.dispatchEvent(e, true);
return true;
}
if (node.fireEvent)
{
e = doc.createEventObject();
e.synthetic = true;
node.fireEvent('on' + event, e);
return true;
}
event = 'on' + event;
return node[event]();
};
here's a version of this code that is actually documented
I've set a synthetic
property on the event object that will be passed to the event handler. the handler checks for this property, and if it's set to true
, it will unbind the listener and return. This doesn't require you to keep DOM references and handlers in an object, but this is, I think you'll agree, quite a lot of work, too.
It also feels quite hacky, if you don't mind my saying so...
Compared to:
var binderModule = (function()
{
var module = {},
eventMap = {},
addEvent = function (elem, eventName, handler, capture)
{
var i, wrappedHandler;
if (!eventMap.hasOwnProperty(eventName))
eventMap[eventName] = [];
for (i=0;i<eventMap[eventName].length;++i)
{//look for elem reference
if (eventMap[eventName][i].node === elem)
break;
}
if (i>= eventMap[eventName].length)
{
i = eventMap[eventName].length;//set i to key
eventMap[eventName].push({
node: elem,
handlers: []//keep handlers here, in array for multiple handlers
});
}
wrappedHandler = function(e)
{
//stuff
return handler.apply(this, [e || window.event]);//pass arguments!
};
eventMap[eventNAme][i].handlers.push(wrappedHandler);
return elem.addEventListener(eventName, wrappedHandler, capture);
},
removeEvent(elem, eventName, capture)
{
var i, temp;
if (!eventMap.hasOwnProperty(eventName))
return;//no handlers bound, end here
for (i=0;i<eventMap[eventName].length;++i)
if (eventMap[eventName][i].node === elem)
break;
if (i < eventMap[eventName].length)
{//found element, remove listeners!
//get handlers
temp = eventMap[eventName][i].handlers;
//remove element + handlers from eventMap:
eventMap[evetnName][i] = undefined;
for (i=0;i<temp.length;++i)
elem.removeEventListener(eventName, temp[i], capture);
}
};
module.addCustomEvent = addEvent;
module.removeCustomEvent = removeEvent;
//or, perhaps better:
Object.defineProperty(module, 'addCustomEvent', {value: addEvent});//read-only
Object.defineProperty(module, 'removeCustomEvent', {value: removeEvent});
return module;
}());
Note that this is the basic setup to keep track of event handlers that are bound to particular DOM nodes, and how to mangage them. This code is not finished and is not tested. It probably contains typo's, syntax errors and some consistency issues. But this should be more than enough to get you started.