Introduction

You are probably starting to get used to that Mozilla is the browser with the best standard support (as I write this at least). This is true when it comes to the event model as well. (Even if the standard are not good enough for the real world).

addEventListener

The standard way to add and remove event listeners is to use the methods addEventListener and removeEventListener. These methods takes 3 arguments. The first one is the type of the event. The second is a function taking one argument and that is the Event object. The last argument tells whether to use capture for the event. We will get back to capturing in a later article.

element.addEventListener("click", onClick, false);
element.removeEventListener("click", onClick, false);

function onClick(e) {
   // ...
}

attachEvent

This is the IE5 proprietary way of adding event listeners. It differs in several small ways but these will not stop us from adding support for it in Mozilla. The first difference is that the first argument is not the type of the event but the string "on" plus the type. The second argument is a funtion like before but with one crucial difference. The function does not take any arguments. The event object is instead bound to the window that the event was triggered in. This is a bad design decision by Microsoft but we just have to live with it :-(. The attacEvent (and detachEvent) only takes two arguments so there is no way to tell whether to use capturing or not. All events use bubbling only.

element.attachEvent("onclick", onClick);
element.detachEvent("onclick", onClick);

function onClick() {
   var e = window.event;  // will not work if code and event
                          // reside in different frames
   // ...
}

The emulation

First we extend the prototype of HTMLElement with the two methods. The thing to do for both methods is to remove the "on" at the beginning of the type. Then we add a new function as a listener using addEventListener. Inside that function we bind the event object to window.event and finally we call the real function.

HTMLElement.prototype.attachEvent = function (sType, fHandler) {
   var shortTypeName = sType.replace(/on/, "");
   this.addEventListener(shortTypeName, function (e) {
      window.event = e;
      return fHandler();
   }, false);
};

This code would be enough if we did not want detachEvent to work. The problem is that to be able to remove the event listener we need the function object to remove. Therefore we bind the new anonymous function as an expando property on the original function object.

HTMLElement.prototype.attachEvent = function (sType, fHandler) {
   var shortTypeName = sType.replace(/on/, "");
   fHandler._ieEmuEventHandler = function (e) {
      window.event = e;
      return fHandler();
   };
   this.addEventListener(shortTypeName, fHandler._ieEmuEventHandler, false);
};

HTMLElement.prototype.detachEvent = function (sType, fHandler) {
   var shortTypeName = sType.replace(/on/, "");
   if (typeof fHandler._ieEmuEventHandler == "function")
      this.removeEventListener(shortTypeName, fHandler._ieEmuEventHandler, false);
   else   // we can always try :-)
      this.removeEventListener(shortTypeName, fHandler, true);
};

Usage

To allow Mozilla to support attachEvent and detachEvent you need to include the ie emu file as well as call a function called emulateAttachEvent.

<script type="text/javascript" src="ieemu.js"></script>
<script type="text/javascript">

if (moz) {
   emulateAttachEvent();
}

</script>

The reason why it has been done in this way is fairly simple. It allows you to define what emulations you need in your page.

Demo

Introduction
The power of JS
Event Listeners
Classic Event Handlers
Event Object
InnerHTML Model
Element Model
Document All Model
Current Style Model
???

Author: Erik Arvidsson