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.
Tuesday, November 16. 2010
Failed to map the path '/' After Installing IIS
Over the past weekend I installed SQL Server 2005 Express Reporting Services according to the directions in KB934164 on my Windows 7 machine (which did not go particularly smoothly due to problems with the default Application Pool Identity that caused some Reporting Services Configuration steps to fail... but that is another story). Returning to developing ASP.NET in VS2010 I found that most pages of the site I am working on would no longer load in the ASP.NET Development Server for debugging. The failing pages produced the following error message:
The error could easily be reproduced by calling System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath)
.
After a bit of searching, I found several discussions of the problem (or very similar problems) and all of the posts that contained a solution involved running Visual Studio as Administrator (e.g. Gabe Sumner on Sitefinity or Miha Markič on Righthand Blogs). I found this solution unacceptable (for a number of reasons) and decided to dig deeper.
After much tracing and browsing around in .NET Reflector, I determined that System.Web.Configuration.ProcessHostConfigUtils
makes calls to several external functions in System.Web.Hosting.UnsafeIISMethods
which were failing. Although these calls failed, the code in ProcessHostConfigUtils
ignores the failures up until it throws the nearly useless message quoted above (rather than using the failure message from the UnsafeIISMethods
methods). Using reflection, I invoked the external methods directly, then converted the HRESULT to an exception using System.Runtime.InteropServices.Marshal.GetExceptionForHR
and received the following error message:
Finally! Something useful. So I granted my user account read permission on C:\Windows\System32\inetsrv\config and the problem was solved (note that I don't have any sensitive information in this directory which would need to remain private, so I don't have a problem with extra read-only access).
It seems very odd that this file would be loaded at all, given that IIS is not configured with the "shared configuration" feature that uses redirection.config (in fact it has the default configuration in every way). It also seems strange that the code would need to consult the global IIS configuration, although this could be due to any number of reasons (legitimate or due to code which makes incorrect assumptions about IIS and the ASP.NET Development Server co-existing on a machine). However, since all of the code necessary to reproduce the problem is outside of my control, there isn't much more I can do. Enjoy the workaround.
Sunday, October 17. 2010
CSS3 Flexible Boxes Don't Always Work
While working on a web page for a web-based intranet application, I ran into the problem of vertically sizing an HTML element such that it fills the remaining vertical space within its parent (given an unknown amount of space already filled by its preceding siblings). Although may sound like a simple problem, I have yet to find a CSS-only solution. There is a very useful (although now a bit dated) discussion of the problem on Patrick van Bergen's Blog, although the Internet Explorer portion of his solution can not be used in IE8 Strict Mode due to the removal of dynamic properties. However, this post lead me to discover the CSS3 Flexible Box Layout Module.
The purpose of this post is not to describe the flexible box layout module or how to use it, for that there are many excellent resources such as The CSS 3 Flexible Box Model on Mozilla Hacks and Introducing the Flexible Box Layout module on CSS3.info, the point of this post is to highlight one important caveat of flexible boxes in Firefox: They don't work when positioned absolutely. This fact isn't mentioned in the documentation that I have come across (or, if it is, I have managed to overlook it multiple times) and I am unsure if it is a bug or intended behavior. In either case, it can be very confusing for developers who are new to flexible boxes and unsure what to expect. For a quick example of the problem, consider the following code:
<div style="border: 2px solid green; display: -moz-box; height: 5em; -moz-box-orient: vertical">
<div style="background-color: red">Child 1</div>
<div style="background-color: blue; -moz-box-flex: 1">Child 2</div>
</div>
This produces the following output (best viewed in a Gecko-based browser):
Yet if we make the container absolutely positioned (and place in a relatively positioned container so it appears below this text) we get the following:
Keep this in mind if you are working with flexible boxes.