Object Detection

The pace of new browser releases may be
slower than it was in the early days, but developers must still
confront a bemusing array of browser versions and brands that
support some JavaScript features but not others. To combat the
problem, scripters commonly provide two or more code branches so
that a browser follows an execution path containing statements that
it supports. Browser sniffing -- the task of inspecting navigator object properties for version
information -- has become largely unmanageable given the browser
version permutations available today. This article presents details
on an alternative solution -- object detection -- that frees
JavaScript developers from most of this versioning mess.

Browser Sniffing Woes

The fundamental belief behind branching code for a
particular browser brand and/or version (and possibly operating
system) is that Version N of Browser Brand X implements a known set
of core scripting language and object model features. Scripters
frequently extend this belief to mean that Versions N and later of
the browser support a particular feature. Unfortunately, these
beliefs have several flaws.

Let's start by looking at the browser
version number. The navigator.appVersion property returns a
string that starts with a number associated with the "Mozilla"
version. In the early graphical browser days, when most commercial
browsers were built on the University of Illinois NCSA Mosaic
browser engine, the browsers quietly identified themselves to
servers (with each server file request) as belonging to the Mozilla
family (not to be confused with the modern Mozilla.org
browser). Unfortunately for scripters, over time the correlation
between the Mozilla family version number and actual browser product
version number weakened. IE5 says it's Mozilla 4.0; Netscape 6
thinks it's Mozilla 5.0. These days, a script must parse the more
detailed navigator.userAgent or navigator.appVersion property to discover
precisely which product version is running. Netscape 6 includes new
navigator object properties that
report the browser product version, but you have to branch your code
just to access those properties. No matter what, it takes a lot of
string parsing code to establish a set of global variables
indicating the browser's version.

Next, you have the "equals" and "greater
than or equals" version conundrum. When scripters started sniffing
browsers in earnest for Netscape and IE brands of Version 4
browsers, some scripts simply checked whether the version number
(regardless of how it was extracted) equaled 4. When IE5
appeared, the scripts failed to use the IE4 powers because 5 does
not equal 4. Conversely, those who thought they would be
smart and write their Netscape 4 sniffers to accept versions 4 or
later got into trouble when NN6 appeared and their NN4
layer-dependent code broke left and right.

On the one hand, the "equals 4" camp failed
to think beyond solving a problem for the current browser. On the
other hand, the "greater than or equals 4" group got caught in a
rare, but obviously not impossible, case of an object model feature
(the layer object) being yanked by the browser's maker in its drive
to adopt the W3C DOM standard. While it's easy to forecast that
there will be a succeeding version of a browser, it's not easy to
predict feature deprecation or removal in future browsers. It should
be clear, in retrospect, that both browser sniffing approaches
required emergency code repair as new browser versions starting
knocking on site doors.

Lastly, what most browser sniffers tend to
ignore are scriptable browsers other than IE and NN. Opera is
perhaps the most popular of the "other" category, but more are out
there. We also need to prepare for future browsers that are built
from the core code that goes into IE and NN (the latter from the
mozilla.org group) but that won't necessarily identify themselves by
the familiar brands we've come to know and love. If Frobnitz Corp.
should produce the Frobigator 1.0 browser from the latest
mozilla.org engine, the browser could offer extraordinary W3C DOM
scripting facilities; but a typical browser sniffer would relegate
it to the "dumb and dumber" category, because string parsing of
navigator.userAgent would fail to
uncover a well-known brand name or sufficiently high version
number.

Simple Object Detection

You can solve many of these problems with a technique
called object detection. This is a very broad name for an approach
that allows scripts to create execution branches based on whether
the current browser supports a desired object, property, or
method.

You have probably seen object detection at
work already without recognizing how powerful it can be. Netscape
Navigator 2 (all OS versions) and IE3/Windows did not support
scripting for the image rollover effect because IMG elements were
not recognized as objects in the DOM. But subsequent browsers
implemented the image object, exposing all IMG elements within a
page as an array of image objects belonging to the document object. Therefore, if the browser
supported the image object, the document object contained an images array (collection) of image objects
on the page. By enclosing image object manipulation statements
inside a test for the existence of the document.images array, earlier browsers
simply bypassed the nested statements without errors:

if (document.images) {
// image object manipulations here
}

This syntax works because in non-supporting
browsers, the expression document.images evaluates to undefined. An if construction's conditional expression
evaluates to the equivalent of false
when the expression evaluates to undefined.

Pay close attention to the elegance of this
admittedly simple example. Mouse-related event handlers operate on
all scriptable browsers, but only those browsers that manipulate
images as objects attempt to act on those objects. This is an
excellent example of using scripting to add value to a page for
those browsers that support specific functionality.

Detecting Supported Properties

With today's more complex
object models, it's not uncommon to branch code based on support for
an object's property and/or method. For example, in IE4 and later,
the document.body object contains a
scrollTop property, which is used to
determine a mouse event's y-axis location on the page (not just
within the visible part of the page). In an effort to ensure support
for the scrollTop property, you might
rush to use the following construction:

// maybe headed for trouble
if (document.body.scrollTop) {
// statements that work with scrollTop property
}

Problems (script errors) occur, however,
when the if construction runs in a
browser (such as NN4 or IE3) that does not support the document.body object. In attempting to
evaluate the conditional expression, the "two-dot" evaluation
results in a script error. To prevent the error, the expression must
first test for the existence of the document.body object, and then for the
existence of the property:

if (document.body && document.body.scrollTop) {
// statements that work with scrollTop property
}

The reason this works is that when an && (AND) operator is part of a
conditional expression, a failure of the left-most expression
short-circuits the entire expression. Thus, if document.body isn't supported (that is to
say, it is undefined), then the entire
conditional expression evaluates to the equivalent of false, without attempting to evaluate the second expression.

Since object methods (the method name
without trailing parentheses) return a value when evaluated, you can
easily test for a browser's support of a method. You can reference a
method in a conditional expression, just as if it were a property of
an object. For example, to see if the browser supports the document.getElementById() method, you can
test for it with the following construction:

if (document.getElementById) {
// supports the method -- go for it!
}

The only glitches with verifying methods
occurs with several earlier browsers, notably NN2, IE3/Windows, and
IE/Mac prior to version 5. The problems occur when the method is, in
truth, supported by the browser: Testing for the presence of an
existing method in conditional expressions causes a script error in
IE and a misleading evaluation in NN2. The lowest common denominator
of browsers that permit method detection are those that support the
W3C DOM in some fashion. In other words, for maximum compatibility,
you should perform method testing for methods that belong to the W3C
DOM. Because support for various W3C DOM objects and methods varies
so widely among browser versions, this testing can help scripts
gracefully weave their way through potential minefields.

Are you convinced that object detection might be a better way to go over browser sniffing? What are your experiences?Post your comments

One Gotcha and Its Workaround

Before you use property
checking, you need to be aware of the data types and default values
of the properties you're checking. If the value of a supported
property is zero or an empty string, a reference to the property in
an if conditional expression evaluates
to false, giving the incorrect
impression that the property does not exist when, in truth, it
does.

This is where a JavaScript core language
operator: typeof comes to the rescue.
When you precede any expression with this operator, the result is a
string name of the data type of the value. For example, if a
property you're testing for always returns a string (whether an
empty string or one with some text in it), the conditional
expression can look like the following:

if (typeof someObject.someProperty == "string") {
// property exists, so use it
}

As a kind of lazy alternative, you can test
for the presence of a property by seeing if its type is anything
other than undefined, as
follows:

if (typeof someObject.someProperty != "undefined") {
// property exists, so use it
}

You're not completely off the hook with this
operator, however. It is missing from the core language in Netscape
Navigator 2. Therefore, you should use this technique only within
execution branches that have passed tests indicating the browser is
something other than NN2 -- or assume that no one using that
admittedly ancient browser will venture to your site.