Wednesday, April 23, 2008

Prototype DOM Extension Browser Quirks

Documentation is really helpful. Seriously. In fact, anybody who failed to read the section entitled 'Native Extensions' in the article on How Prototype extends the DOM would be surprised to find that much of their code would not work in IE while working fine in many browsers. Until recently, I was one of those people that had not read that article. This is an easy pitfall for developers whose primary development browser is Firefox, or even Opera.

The problem is that Firefox allows adding methods to prototype of native objects, such as HTMLElement. Thus, all DOM extensions are are available on all elements, including dynamically created elements. However, this is not the case with IE. The syntactic shorthand DOM extensions I'm referring to are constructs such as myDiv.getWidth() instead of the longer Element.getWidth(myDiv).

IE does not let anyone touch HTMLElement.prototype, thus DOM extensions are not available by default on elements in IE. To get around this, Prototype has two constructs to enable DOM extensions on an element:
  1. Element.extend('my_div'), or
  2. $('my_div')
Prototype's $ function automatically extends any element passed through it, while still being smart enough to not doubly extend any element. Thus, assuming that you access all elements through $('elem'), you won't have to worry about the Element extensions not being available on a given object.

The simplest, though more annoying solution, is to not use syntactic extensions and use Element methods directly. This means always using Element.doSomething(myElem,...) rather than myElem.doSomething(...). That's not an ideal solution, and we can do better assuming we can remember to extend elements ourselves, which is as simple as calling Element.extend(myElem), or wrapping access to the element in a $ call, such as $(myElem).

There are three sticky situations to watch out for: (1) dynamically created objects, (2) going up or down the DOM chain from an extended element to non-extended elements, and (3) browser objects.

Dynamically Created Elements
In IE, dynamically created objects are not extended by default, unlike Firefox. In Firefox, the following will work:

var myDiv = document.createElement('div');
myDiv.setStyle({width: '50px'});

Note the DOM extension .setStyle({}) is available by default. IE requires an additional step to achieve the same:

var myDiv = document.createElement('div');
myDiv.setStyle({width: '50px'});

It's easy to forget the call to Element.extend(myDiv) when dynamically creating elements, especially because the code will work just fine without that in a few more developer friendly browsers.

Traversing the DOM
Extending an element does not confer that extension upon elements referenced from said element. parentNode is a big one to watch out for. It's really easy to want to do:


That will work fine in Firefox, but only because all elements are extended by default (as described above). In IE, you'll have to remember to extend the parent element as well:


I agree, it is annoying. On the good side, since $ is smart enough not to re-extend a previously extended element, we don't have to worry about browsers that extent all elements by default running into problems from the constructs necessary to make IE behave nicely.

Browser Objects
document is an object, and thus must be extended to enable the syntactic shortcuts. In Firefox, the following will work fine:


In IE, that will result in the error message, "Object doesn't support this property of method." To make it work in IE, just wrap the call:


On the whole, I like the syntactic shortcuts, but as everyone stresses, you have to test the code in multiple browsers. If my code using Prototype works in Firefox but not IE, these are the first three things I look for. Happy scripting!

1 comment:

Brandon said...

You might enjoy this blog.

It will keep you up to date with many of the internal bugs in IE, like the inability to prototype (which MS has declared will not be fixed in IE8) on Elements, or "properly" on the Array.

It also helps a lot with those "why doesn't this attribute get set in IE!" questions that pop up.

good luck.