
Agnostic, Common, Component, CrossBrowser, Independent, Portable
Dave has developed a cross-browser sprite engine, allowing users to easily drag a sprite around the screen. The library provides a browser-independent interface, taking into account browser incompatibilities regarding detection of mouse position and positioning of icons. When a new drag-and-drop requirement comes along, programmers can code to Dave's library in blissful ignorance of the underlying browser incompatibilities.
A major selling point of Ajax is portability - the ability to run an application on any platform with a modern browser.
There are numerous browsers on the market, and while IE still dominates, the market is becoming increasingly fragmented. IE currently has around 85-90% share on average websites, but within certain user groups, Mozilla is actually far more popular. A recent survey of AjaxPatterns.org traffic showed 47.5% hits from Firefox, 30.1% from IE, and 2.7% from Safari.
Browsers vary from each other in subtle and not-so-subtle ways. Sometimes, a browser simply doesn't supprt certain functionality. Other times, two browsers offer the same functionality, but it must be accessed in different ways. While the W3C establishes standards for browsers to follow, not all are followed.
When creating a web program, you want to focus on business and application logic, without being distracted by idiosyncracies of underlying browsers. Cross-browser compatibility is a cross-cutting concern, best dealt with as a separate task at a separate time.
Create Cross-Browser Components, allowing programmers to reuse them without regard for browser idiosyncracies. A Cross-Browser component abstracts away from underlying browser issues, offering a single API which can be used on any supported browser.
Following are a couple of examples of cross-browser incompatibilities. For simplicity, only the two most popular browsers are discussed: IE and Firefox. Other browsers are also important, and not surprisingly, introduce further incompatibility issues. Following are two examples.
XMLHttpRequest, a critical Ajax component, is constructed in a browser-specific way (as established in “XMLHttpRequest Call”). Most browsers include a specific XMLHttpRequest class, but with IE prior to version 7, it's created as an ActiveX component instead. Furthermore, there are some subtle behaviour differences. For example, XMLHttpRequest on Opera lagged behind in terms of support for features like authentication. An even greater problem is that many older browsers simply don't support this object, even though similar functionality can nonetheless be achieved using IFrames.
Opacity - or transparency - is becoming important in Ajax applications, as discussed in patterns like “One-Second Spotlight” and “Popup”. However, portability is poor. Recent versions of Firefox, Opera and Safari allow for the CSS3 opacity style, older versions use their own different names, and IE uses a completely different approach based on a DirectX filter.
Any application using the features above must take into account cross-browser portability. Business and application logic is complex enough, without tangling it further with if-then portability statements. A better solution, and a common approach in the general area of portability, is to isolate cross-browser concerns.
Some browsers are available in multiple operating systems - Firefox, for example, runs in MS-Windows, Apple, Linux, and many other operating systems. It would be nice to assume a given browser was identical across all operating systems, but that's not the case. Some aspects of browsers rely on OS-specific features, and that's where further incompatibilities arise. For example, IE's drag-and-drop capability not only depends on which version of IE is being used, but whether it's running on Apple or MS-Windows.
As a simple example, many libraries exist to make XMLHttpRequest calls portable. These libraries are examples of Cross-Browser Components, because the user doesn't have to worry about the browser being used. Most of these libraries internally use another form of Cross-Browser Component - a factory function which retrieves a suitable XMLHttpRequest object.
AjaxCaller, for example, contains the following factory function:
createXMLHttpRequest: function() {
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
try { return new XMLHttpRequest(); } catch(e) {}
alert("XMLHttpRequest not supported");
return null;
}
The function allows any code to create a new XMLHttpRequest object without worrying about browser specifics. The returned object is a Cross-Browser Component.
Note that this pattern applies as much to server-side code as browser-side Javascript. Whatever HTML comes from from the server also needs to be portable. For that reason, you can also create server-side components that will generate portable HTML. So if you create custom JSP tags for your project, the JSP coder should be able to use them without having to make any browser-specific checks.
What criteria do you use to decide on a creation strategy? There are, broadly speaking, two approaches:
Behaviour is based on the browser version.
Behaviour is based on checking for the existence of specific features - specifically whether a property or method exists.
The XMLHttpRequest example in the Solution above is an example of feature-driven behaviour. Before trying to create a new window.XMLHttpRequest, we check if window.XMLHttpRequest exists. An equivalent version-dependent check would do something like this:
/* Version-Dependent Check (Unadvisable!!!) */
return determineIfCurrentBrowserIsIE() ?
new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
In the above example, a function determines if the current browser is IE. Based on that knowledge, the right mechanism can be used to create an XMLHttpRequest object. How might the version be determined? There are a couple of ways. First, the navigator object reports directly on this information. Useful properties include:
Browser name, e.g. "Netscape"
Browser version, e.g. "5.0 (X11; en-US)"
Detailed browser and platform details, e.g. "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.9) Gecko/20050711 Firefox/1.0.5"
Ironically, the navigator object's behaviour is itself somewhat inconsistent across versions, so you need to handle strings like the userAgent differently. If you go this route, consider a browser detection library like TechPatterns' Javascript Browser & OS Detection or Webreference's Javascript Browser Sniffer. Portability aside, the problem with direct interrogation is that the browser sometimes tells fibs - Opera, for example, lets users manually choose which browser to self-identify as, because some sites refuse to support browsers reporting as Opera.
An alternative way to detect browser version is to rely on a distinctive feature. For example, many scripts use document.all as a simple check for Internet Explorer:
isIE=document.all;
Version-dependent behaviour is also feasible, quite common, and often recommended as the appropriate solution. One benefit over feature-dependent behaviour is that you only have one or two variables to deal with; with feature-dependent behaviour, you have to consider many combinations - what if the browser supports X but not Y?
However, there do remain some fundamental problems with any version-dependent behaviour. First, it's cumbersome to track all the browser versions that support a particular feature. In many cases, you risk ruling out other browsers which may well support the feature. Also, version checks are often used in a binary way: either your browser's supported and you can run the app, or it's not supported and you're out of luck. But if you focus on specific features, you can support progressive degradation: have a basic application that works for all browsers, with certain additional features only available to those browsers that support them.
Sometimes, a browser simply doesn't support a feature. What does a cross-browser API do when asked for functionality the browser doesn't provide? There are a couple of options:
Throw an exception or return null to indicate the object couldn't be created.
The browser may not support an object natively, but you might be able to create a custom version from other supported objects.
This section contains one example that showcases a particularly interesting type of cross-browser compatibility. For more examples of cross-browser components, refer to the ??? appendix. Many of the Javascript libraries mentioned there, like Dojo and Scriptaculous, aim to provide components that work on all major browsers.
Angus Turnbull's HTMLHttpRequest is a cross-browser library for ???. Unlike many of the recent Ajax libraries, it's able to gracefully degrade to IFrame usage when executed on older browsers. The API's still the same, so a programmer can access web remoting in the same way, regardless of the browser in question.
HTMLHttpRequest is a cross-browser remoting component. It works by wrapping around a real, concrete, remoting object - either an XMLHttpRequest object or an IFrame, only one of which will be set. The construction code below makes extensive use of feature-dependent behaviour to decide, at each stage, whether to proceed with a particular strategy. There is also some use of feature-dependent behaviour to ensure that Opera and IE5 don't follow a particular path.
function HTMLHttpRequest(myName,callback){
this.myName=myName;
this.callback=callback;
this.xmlhttp=null;
this.iframe=null;
...
if(window.XMLHttpRequest){
xmlhttp=new XMLHttpRequest();
if(xmlhttp.overrideMimeType)
xmlhttp.overrideMimeType('text/xml')
}
if(!xmlhttp) {
if( document.createElement && document.documentElement
&& (window.opera||navigator.userAgent.indexOf('MSIE 5.0')==-1)) {
...
} else if (document.body && document.body.insertAdjacentHTML) {
...
}
...
}
return this;
}
“Server-Side Code Generation” is often a convenient way to generate Cross-Browser Components, allowing you to perform all the abstraction logic server-side.
If you design carefully, you can separate out the implementations for each platform. Use the Lazy Loading technique of “On-Demand Javascript” to ensure that only code required for the user's own platform is downloaded.
Using a Cross-Browser Component is like working with a certified professional. The professional may have gained their certification in different ways and might offer different services, but there remains a basic set of services that you can expect to be carried out adequately.
Good discussion on cross-browser issues and specifically enhancing IE applications to support Mozilla by Doron Rosenberg.
Quirksmode, by Peter-Paul Koch, is a website providing comprehensive compatibility resources.