Implementing a cross-browser event handling system


JavaScript, in the browser, is an event-driven programming language – with no consistent event handling support. While adding and removing listeners might work in almost all JavaScript-enabled browsers, triggering events, to this date, has been a nightmare for many JavaScript programmers out there and I’m no exception.

While I’ve been working on Hilo a few months ago, I had to deal with this situation. I started working on it but the thing which I came up with only worked in a few modern browsers. It did not work in Internet Explorer, no wonder. Even after a few days of looking into other libraries, borrowing ideas from them, I could not implement a consistent system, whatever the reason might be. Recently I started rewriting Hilo all over again. And as before, I had to deal with those events. As I already had a pretty bad experience implementing an event handling system, I wanted to do it the right way and the modern way this time whatsoever.

I went on and made methods for adding and removing event listeners which are pretty easy to do. There came the problem — How to trigger events? Well, I write this code.

function triggerEvent () {
}

I know how to trigger events, but I wanted my handling system to support custom events, which is really hard to. Listening for custom events can be done in the same way as you listen to other events, but there will be a problem when you need to trigger them. While some browsers provide support for Custom Events, some do not. To handle this situation I came up with an idea:

Store all the events somewhere and call them when that event is fired on that element. Good Idea! But.. I wanted something more.. support for multiple events.

After scratching my head for more than ten minutes (for no reason), I came up with my implementation of cross-browser event delegation system (Yeah!).

For the sake of this article I’ve made a gist and made it available at https://gist.github.com/erikroyall/6618740.

// Evento — v1.0.0
// by Erik Royall (http://erikroyall.github.io)
// Dual licensed under MIT and GPL
// Array.prototype.indexOf shim
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
‘use strict’;
if (this == null) {
throw new TypeError();
}
 var n, k, t = Object(this)
, len = t.length >>> 0;
 if (len === 0) {
return -1;
}
 n = 0;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it’s NaN
n = 0;
} else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
for (k = n >= 0 ? n : Math.max(len — Math.abs(n), 0); k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
};
}
var evento = (function (window) {
var win = window
, doc = win.document
, _handlers = {}
, addEvent
, removeEvent
, triggerEvent;
 addEvent = (function () {
if (typeof doc.addEventListener === “function”) {
return function (el, evt, fn) {
el.addEventListener(evt, fn, false);
_handlers[el] = _handlers[el] || {};
_handlers[el][evt] = _handlers[el][evt] || [];
_handlers[el][evt].push(fn);
 };
} else if (typeof doc.attachEvent === “function”) {
return function (el, evt, fn) {
el.attachEvent(evt, fn);
_handlers[el] = _handlers[el] || {};
_handlers[el][evt] = _handlers[el][evt] || [];
_handlers[el][evt].push(fn);
};
} else {
return function (el, evt, fn) {
el[“on” + evt] = fn;
_handlers[el] = _handlers[el] || {};
_handlers[el][evt] = _handlers[el][evt] || [];
_handlers[el][evt].push(fn);
};
}
}());
 // removeEvent
removeEvent = (function () {
if (typeof doc.removeEventListener === “function”) {
return function (el, evt, fn) {
el.removeEventListener(evt, fn, false);
Helio.each(_handlers[el][evt], function (fun) {
if (fun === fn) {
_handlers[el] = _handlers[el] || {};
_handlers[el][evt] = _handlers[el][evt] || [];
_handlers[el][evt][_handlers[el][evt].indexOf(fun)] = undefined;
}
});
 };
} else if (typeof doc.detachEvent === “function”) {
return function (el, evt, fn) {
el.detachEvent(evt, fn);
Helio.each(_handlers[el][evt], function (fun) {
if (fun === fn) {
_handlers[el] = _handlers[el] || {};
_handlers[el][evt] = _handlers[el][evt] || [];
_handlers[el][evt][_handlers[el][evt].indexOf(fun)] = undefined;
}
});
};
} else {
return function (el, evt, fn) {
el[“on” + evt] = undefined;
Helio.each(_handlers[el][evt], function (fun) {
if (fun === fn) {
_handlers[el] = _handlers[el] || {};
_handlers[el][evt] = _handlers[el][evt] || [];
_handlers[el][evt][_handlers[el][evt].indexOf(fun)] = undefined;
}
});
};
}
}());
 // triggerEvent
triggerEvent = function (el, evt) {
_handlers[el] = _handlers[el] || {};
_handlers[el][evt] = _handlers[el][evt] || [];
 for (var _i = 0, _l = _handlers[el][evt].length; _i < _l; _i += 1) {
_handlers[el][evt][_i]();
}
};
 return {
add: addEvent,
remove: removeEvent,
trigger: triggerEvent,
_handlers: _handlers
};
}(this));

If you want to use it, then you could do some thing like:

function someFunction () {
// do something…
}
// Add an event listener
Evento.add(“someEvent”, someFunction);
// Trigger (fire) an event
Event.trigger(“someEvent”);
// Remove event listener
Evento.remove(“someEvent”, someFunction);

And now we reach the end of this tutorial. This is actually a very simple system and this can be greatly improved if you think. The source code is also available at https://gist.github.com/erikroyall/6618740 as a gist so you can fork it or play around with it.

The original source for this post can be found here.

Email me when Erik Royall publishes or recommends stories