[edit] [comment] [remove] |2005-11-02| e1 # Multiple event registration issue

DOM events

Today's browsers tend to agree on the standardized W3C DOM API. In order to allow event-driven webpages the W3C defined a standard for DOM events.

Crossbrowser Events

Peter Paul Koch has written a good and comprehensive introduction to events in HTML/XML documents and a comparison of the DOM and Microsoft event registration model. The problem with Internet Explorer 5/6 is, that it currently doesn't implement W3C DOM events. So a common solution here is to use wrapper functions as a workaround. See Scott Andrew's article for an often referenced example.

Multiple registration of a single event

I want to focus here on an issue with a single crossbrowser event, which is multiply registered on the same object. This is not unlikely to happen on webpages, when the user is interactively involved in the event registration process. At first we take a look at the code and a live example.

function addListener(obj, type, listener, capture) {
   if (obj.addEventListener)   // W3C DOM registering ..
      obj.addEventListener(type, listener, capture);
   else if (obj.attachEvent)   // IE 5/6 registering ..
      obj.attachEvent("on"+type, listener);
}
function removeListener(obj, type, listener, capture) {
   if (obj.removeEventListener) // W3C DOM unregistering ..
      obj.removeEventListener(type, listener, capture);
   else if (obj.detachEvent)    // IE 5/6 unregistering ..
      obj.detachEvent("on"+type, listener);
}

Event registration and trigger

  1. When you press the Trigger Event button as your first action, nothing happens.
  2. Pressing the Register Event button and then pressing the Trigger Event button results in an alert box popping up.
  3. Pressing the Register Event button and then pressing the Trigger Event button again results in …
    1. … one single alert box with Firefox and Opera.
    2. … two consecutive alert boxes with IE.

So which behaviour is correct? Well, let's read the W3C specification:

Invoking addEventListener or addEventListenerNS multiple times on the same EventTarget with the same parameters (namespaceURI, type, listener, and useCapture) is considered to be a no-op and thus independently of the event group. They do not cause the EventListener to be called more than once and do not cause a change in the triggering order.

So Firefox 1.0+ and Opera 8.0+ behave DOM-conform and differently to IE 6 (these are the browsers I tested).

In order to resolve this issue with IE, we need a way to ask an object, if it has an event handler registered for the specified type. The DOM Events Level 3 specification indeed defines a method hasEventListenerNS exactly for that purpose. Unfortunately it doesn't help here with IE6.

So we simply extend the IE object (event target) by an event list in order to store the registered event types by itself.

function addListener(obj, type, listener, capture) {
   if (obj.addEventListener)   // W3C DOM registering ..
      obj.addEventListener(type, listener, capture);
   else if (obj.attachEvent) { // IE 5/6 registering ..
      if (!obj.eventListeners)
         obj.eventListeners = [];
      if (!obj.eventListeners[type]) {
         obj.eventListeners[type] = true;
         obj.attachEvent("on"+type, listener);
      }
   }
}
function removeListener(obj, type, listener, capture) {
   if (obj.removeEventListener) // W3C DOM unregistering ..
      obj.removeEventListener(type, listener, capture);
   else if (obj.detachEvent) {  // IE 5/6 unregistering ..
      if (obj.eventListeners && obj.eventListeners[type]) {
         obj.eventListeners[type] = false;
         obj.detachEvent("on"+type, listener);
      }
   }
}

Now the above example behaves crossbrowser and DOM-conform:

Event registration and trigger

There is another point to take care of with DOM event registration. Using anonymous functions might not work as expected when registering them multiple times - even when using the DOM method directly. For example

target.addEventListener("click", function(){alert("clicked");}, false);
target.addEventListener("click", function(){alert("clicked");}, false);

won't recognize the consecutive registered functions reliably as identical. See here for a deeper discussion.