Cross-Browser Component - Ajax Patterns

Cross-Browser Component

From Ajax Patterns

Evidence: 3/3

Tags: Agnostic Common Component CrossBrowser Independent Portable


Contents

In A Blink

TODO annotated UML diagram - 2 concrete types, 1 abstract


Technical Story

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.

Forces

  • 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.
  • Rarely is one browser dominant enough to be the sole target for your application. For some time, many developers targeted only the most dominant browser: Internet Explorer. However, Internet Explorer has slipped in popularity due to the rise of Firefox and Safari. Recent figures showed IE with 89% market share: still very dominant, but by no means a monopoly, and the trend is towards a more even distribution; the figure is down from 95% a year earlier. A May, 2005 report showed the following statistics:
    • IE: 88.86%
    • Firefox: 6.75%
    • Non-Firefox Netscape and Mozilla: 2.23%
    • Other: 2.06%
  • In some cases, particularly technical sites, the distribution is heavily skewed towards other browsers, leading to a very diverse browser base. For example, a recent survey of ajaxpatterns.org traffic (August, 2005) 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, there is no mandatory requirement and most browsers lack support in at least some ways.
  • Researching and testing for cross-browser compatibilities is both time-consuming and cumbersome.
  • When creating a web program, you want to focus on business and application logic, without being distracted by idiosyncracies of undrlying browsers. Cross-browser compatibility is a cross-cutting concern, best dealt with as a separate task at a separate time.
  • Google Chrome also one of the fastest web browser and getting market share very quickly


Solution

Create cross-browser components, allowing programmers to reuse them without regard for browser compatibility. 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: XMLHttpRequest, a critical Ajax component, is actually referenced in a browser-specific way. Most browsers include a specific XMLHttpRequest class. In IE prior to version 7, it's a special instance of the ActiveXObject class. (See below for more details.) 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: 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.

Many browsers run in multiple operating systems - Windows, Apple, Linux, and often many others too. It would be nice to assume "Browser X, Version Y" was identical on all platforms, but that's simply not the case. Some apsects 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 IE.

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:

 // Browser-agnostic factory function
 _createXMLHttpRequest: function() {
   if (window.XMLHttpRequest) {
     return new XMLHttpRequest();
   } else if (window.ActiveXObject) {
     return new ActiveXObject('Microsoft.XMLHTTP')
   } else {
     _error("Could not create XMLHttpRequest on this browser");
     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 is outputted 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.


Decisions

What browser-specific criteria is used to create the Cross-Browser Component?

What criteria do you use to decide on a creation strategy? There are, broadly speaking, two approaches:

  • Version-dependent behaviour: Behaviour is based on the browser version.
  • Feature-dependent behaviour: 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:

  • navigator.appName: Browser name, e.g. "Netscape"
  • navigator.appVersion: Browser version, e.g. "5.0 (X11; en-US)"
  • navigator.UserAgent: 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 lies - Opera, for example, lets users manually choose which browser to report, 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 feasible and quite common. 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 version. 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 you're 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.

What if a feature isn't supported by the current browser?

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:

  • Do Nothing: Throw an exception or return null to indicate the object couldn't be created.
  • Provide a Custom Version: The browser may not support an object natively, but you might be able to create a custom version from other supported objects.


Real-World Examples

Tooltip Library

Walter Zorn's Tooltip library provides a tooltip library, and claims to work on IE (4+), Konqueror, Mozilla, Firefox, Netscape, Galeon, Opera. The homepage serves as a demo.

X Library

Cross-Browser.com's X Library provides a wide range of functionality: visual effects, drag-and-drop, widgets. It's claimed to work on IE (4+), Konqueror, Mozilla, Firefox, Netscape, Opera, Safari, and others. The homepage links to numerous demos.

HTMLHttpRequest

Angus Turnbull's HTMLHttpRequest is a cross-browser library for Web Remoting. Unlike many of the recent Ajax libraries, it is able to gracefully degrade to IFrame usage when executed on older browsers. The API is the same, so a programmer can access web remoting in the same way, regardless of the browser in question.


Code Examples

HTMLHttpRequest

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)) {
         var ifr=document.createElement('iframe');
         ifr.setAttribute('id',iframeID);
         ifr.setAttribute('name',iframeID);
         ifr.style.visibility='hidden';
         ifr.style.position='absolute';
         ifr.style.width=ifr.style.height=ifr.borderWidth='0px';
         iframe=document.getElementsByTagName('body')[0].appendChild(ifr)
     } else if (document.body&&document.body.insertAdjacentHTML) {
       document.body.insertAdjacentHTML('beforeEnd',
           '<iframe name="'+iframeID+'" id="'+iframeID
         + '" style="display:none"></iframe>')
     }
     if(window.frames && window.frames[iframeID])
       iframe=window.frames[iframeID];
     iframe.name=iframeID
   }
   return this;
 }


Alternatives

Alternative


Related Patterns

Server-Side Code Generation

Server-Side Code Generation is often a convenient way to generate Cross-Browser Components, allowing you to perform all the abstraction logic server-side.

On-Demand Javascript

Why download component Javascript for all supported platforms? Use On-Demand Javascript to ensure only the Javascript for a component on the current platform is downloaded.


Visual Metaphor

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 portfolio of services and expectations.


Want to Know More?