Friday, November 11. 2011
DOM Event Model Compatibility (or Why fireEvent Doesn't Trigger addEventListener)
I've recently been updating an intranet web application that I wrote to fix compatibility issues with Internet Explorer 9 and came across some interesting behavior. IE9 introduces support for DOM Level 2 Events (and to some extent Level 3), a feature that many of us have been hoping to see in IE for a long time. Hurrah! Unfortunately, the addition of this feature is not as smooth as we might like. Presumably due to the incompatibilities between the Microsoft Event Model and the DOM Level 2 Event Model, Microsoft has chosen to implement the DOM model separately from their existing event model. This has some serious implications for web developers, and particularly for JavaScript framework developers.
So, what is the impact of this decision for web developers? IE9 and later versions, in Standards Mode, now support 3 event models: DOM Level 2 Events, the Microsoft Event Model, and the so-called DOM Level 0 or legacy
event model. But to what extent are these models compatible? It turns out that both DOM2+ and MS events will be received by DOM0 handlers, but that DOM2+ events are not received by MS handlers or vice versa.
An Example
To illustrate the problem, consider the following example fragment:
element.addEventListener('click', function () { alert('DOM Level 2 Event'); }, false);
element.onclick = function () { alert('DOM Level 0 Event'); };
Now consider what happens if the user clicks on the element. What happens if element.fireEvent('onclick')
is called? What about element.dispatchEvent(clickevent)
? If the user clicks the element, each of the three alert boxes appears in the order it was attached, exactly what you would expect. If element.fireEvent('onclick')
is called, the DOM0 alert box appears first followed by the MS alert box and the DOM2+ alert never appears, regardless of the order in which they were registered. If element.dispatchEvent(clickevent)
is called, the DOM0 alert box and the DOM2+ alert boxes appear in the order they were attached and the MS alert box never appears.
The Takeaway Message
Ok, that's interesting, what are web developers supposed to do about it? The answer is simple, never mix event models. Yet, this simple advice is not so easily taken for several reasons. First, in order to support IE8 and its predecessors, code must be written for the MS event model. In this simple case, all that is required is to ensure that feature tests are performed with the same precedence order everywhere. If addEventListener
is present, always use both addEventListener
and dispatchEvent
, if not use attachEvent
and fireEvent
. The real problem with sticking to a single event model appears when we consider using JavaScript frameworks and other library code and the decision of which event model to use, and even knowledge of which will be used, is not available to the developer.
JavaScript libraries must choose a single event model in which to trigger events, since triggering DOM0 listeners twice is almost always unacceptable. Keeping in mind that if one library uses the DOM2+ event model and the other uses the MS model they won't receive each other's events, I strongly urge all libraries to use addEventListener
in preference to attachEvent
, both because it is standardized and because attachEvent
throws errors on pages served as XHTML. Unfortunately, this is not standard practice for many Microsoft and IE-centric libraries (e.g. the ASP.NET AJAX Control Toolkit, which spurred my original inquiry) and without a de facto standard for event model preference, it will be difficult to convince such projects to change and risk breaking user code (often IE-only) which uses attachEvent
.
Further Testing
For the curious, I have coded up a test page to test event model compatibility on different browsers and different compatibility levels. The Event Model Compatibility Test Page tests each of the native event creation and handling functions as well as the event functions of several popular JavaScript frameworks. I hope you find it useful, or at least a bit interesting.
An Update
For anyone in the unenviable position of needing to use/interoperate with frameworks that send events in both the Microsoft Event Model and the DOM Level 2 event model, my suggestion is to register event listeners using the legacy DOM Level 0 event model on Internet Explorer (note that the Dojo Toolkit appears to be the only framework which does this). This is the only event model which receives all events and by using it (rather than using both the DOM2+ and MS event models) events are only received once. I typically use code similar to the following:
element.addEventListener(evtType, listener);
} else {
addLegacyEventListener(element, evtType, listener);
}
In the above snippet, addLegacyEventListener
can be any function that hooks up the listener to the DOM Level 0 event model. It can be as simple as element['on' + evtType] = listener
, if there is ever at most one listener, or it can chain listeners in whatever style is preferred. Here is an example of an addLegacyEventListener function that I commonly use.