Hitting the limits of Object Detection
Repeat after me: ‘object detection good, browser detection bad’. Right? This is one of the core mantras of standards-aware DOM coding, and I’m right behind it. However, I came upon a case recently where object detection doesn’t deliver.
Let’s back up and explain the terms first. Browser detection and Object detection are both ways round cross-browser inconsistencies, of which there are many, many. One provides two or more branches of code, one for each way of doing things, and then detect which to use.
With Browser detection, you work out which browser your code is running in by reading the navigator.userAgent string, or similar. If we then know that browser X is broken in a particular way, we divert the code through some workaround when running browser X, otherwise follow the main path of the code. The big problem with this is that browser X may release a new version, which fixes the bug, and maybe doesn’t work with the workaround. Slowly, any browser-detection code will get out of date.
Object detection is the nice way of doing things. If you want to use the document.stuff property, don’t try to figure out whether browser X supports it or not, just test for the property itself:
if (document.stuff){
document.stuff.somethingAstoundinglyCool();
}else{
doSomeCrummyWorkaround();
}
And most of the time it works.
Most of the time. Here’s a case where it doesn’t. The Select object, representing the drop down list HTML form widget, has a method add(), which allows us to programmatically populate the contents of the drop-down list. add() takes two arguments, the first of which is an Option object. Now, in Internet Explorer, the second argument is a numerical index, indicating where the new option should go relative to existing ones (-1, or omitting the argument appends it at the end of the list). In Mozilla and Safari, the same is achieved by passing a reference to the Options object to insert before/after (null appends it at the end, but missing the argument causes an error to be thrown). If we pass an Options object to IE or a number to Mozilla, both will complain loudly.
Object detection can only query whether a method with a name exists, not what the argument numbers or types are, because JavaScript Function arguments can be variable length, and are untyped. We can’t tell in advance what to do until the exception has been thrown. So, the solution is to catch the exception. Let’s wrap this up in a little function:
function appendSelectOpt(mySelect,myOption){
try{
mySelect.add(myOption,null);
}catch (e){
mySelect.add(myOption,-1);
}
}
My training as a Java developer makes me twitch a bit when I read this - throwing and catching exceptions is relatively expensive in Java. If I pinch myself, and remember that I’m writing JavaScript (in which case doing anything is relatively expensive, particularly on IE), then I can live with this as a solution. At least it’s easy to understand the intent of the code.
I have been looking for a way to code
var newAC = document.createElement(”INPUT”);
newAC.setAttribute(”onblur”,”javascript:somefunc(this);”);
…
which works in Firefox but not IE. IE will allow you to set a “onblur” attribute but will set it as a text object and not a function, and hence will never be called.
I have found a way to do it via browser detection but not object detection.
–
JB
Comment by John Buck — November 28, 2005 @ 2:42 pm
Hi John,
Yes, reality bites sometimes. There are cases where browser detection is the only way to go, particularly where implementation bugs are concerned.
It sounds like you have explored the alternatives, and, in this case, browser detection is the only way to go. How tightly are you detecting the browser type? Does it go by version? You need to consider whether a future version of IE, in which this bug is fixed, will behave. Will it still use the workaround on the assumption that the ‘correct’ behaviour is broken?
Happy coding, and thanks for dropping by!
Dave
Comment by dave — November 28, 2005 @ 3:52 pm
Hi John,
Just reading your comment again, doesn’t it work cross-browser if you set the attribute directly rather than via setAttribute(), i.e.
var newAC = document.createElement(”INPUT”);
newAC.onblur=somefunc;
HTH
Dave
Comment by dave — November 29, 2005 @ 2:59 pm