Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is difference between a callback-based and an event-based event handling system?

I was working on a cross-browser event handling system. And I asked some developers to review my code. One of the developers said that my implementation is based on callbacks and not real events. What is the difference?

I have provided the source code of my implementation below for your convenience (and also as a gist). So far, I haven't found any problems with it. It works fine with all browsers that I tested with.

I'm sorry for the bad description of the problem, I am not familiar with that pure-event part.

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));
like image 923
little pootis Avatar asked Nov 02 '22 12:11

little pootis


1 Answers

I could not care less about your system being callback, event or lambda-calculus based. For the bit you expose here, it seems rather well-written and promises to do a good job (although I'm curious about how you handled removeEvent() ;)).

However, I have a few remarks about your implementation:

  • it is unnecessary to check on which browser you execute each time you add an event handler.

I am amazed at the number of people accepting the practice of checking existence of properties each time they are about to call them. Noone will swap your IE for a FF in the middle of a function call (and anyone stupid enough to define a document.addEventListener property other than an actual ECMA-5 replacement deserves to be flogged to death, if you ask me), so check which platform you're on at start and be done with it, like so:

if (doc.addEventListener) {
    addEvent  = // ...
    freeEvent = // ...
}
else if (doc.attachEvent) {
    addEvent  =  // ...
    freeEvent =  // ...
}
/* etc. */
  • you offer an unified interface to attach a handler but, depending on the browser your code will execute on, the actual handler will behave differently.

In IE8-, for instance, the target of the event will not be available the same way as in ECMA-5 conventions.

If you want to offer a real cross-browser interface, you should provide an unified execution context to the event handlers. This could include a "cancel" function that would translate to something like:

cancel = function (e) { e.returnValue = false; }; // IE8-
cancel = function (e) { e.preventDefault();    }; // ECMA-5

you should also restore this to the target object under IE8-, and unify the target and event.target semantics.

If you really want to be nice to programmers, you could also tackle a few oddities, like

  • the load event not firing in IE8- when an image is already cached
  • the absurdly complicated mouse wheel report system

and probably a few others.

The way I did it for my own purposes was to have a wrapper generated around the actual handler, that could take care of all the platform pecularities and establish a consistent execution context before calling the actual user code.

A last remark: except for the beauty of it, I am not quite sure it is still necessary to support Netscape4-style events. But that is a matter of faith, so...

like image 73
kuroi neko Avatar answered Nov 10 '22 16:11

kuroi neko