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 data, has been a nightmare for many JavaScripters 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. And 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 peeking into other libraries 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, this time I wanted to do it the right way and the modern way whatsoever.

I went on and made addEvent and removeEvent methods which are pretty easy to do and then I wrote this code and started at it for more than 60 seconds…


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. GREAT IDEA! But.. I wanted something more.. support for multiple events.

After scratching my head for more than ten minutes, I came up with my implementation of cross-browser event delegation system.

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));

I used callbacks in this article to keep it simple. I’ll write another article which purely uses events (fake events) to do this. Thanks for reading tis article. Any suggestions go below…

Advertisements

One thought on “Implementing a cross-browser event handling system

  1. Pingback: Implementing a cross-browser event handling system | AzevedoRafaela

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s