20.1 Platform and Browser CompatibilityWhen developing production-quality JavaScript code, testing and knowledge of platform-specific, vendor-specific, and version-specific incompatibilities are your chief allies. If you know, for example, that Netscape 2 on Macintosh platforms always gets the time wrong by about an hour, you can take steps to deal with this problem. If you know that Netscape 2 and 3 on Windows platforms do not automatically clear your setting of the status line when the mouse moves off a hypertext link, you can provide an appropriate event handler to explicitly clear the status line. If you know that Internet Explorer 4 and Netscape 4 support vastly different Dynamic HTML models, you can write pages that use the appropriate mechanism depending on the browser in use. Knowledge of existing incompatibilities is crucial to writing compatible code. Unfortunately, producing a definitive listing of all known vendor, version, and platform incompatibilities would be an enormous task. It is beyond the scope and mission of this book, and it has apparently never even been seriously attempted. You may find some assistance on the Internet, but you will have to rely primarily on your own experience and testing. Once you have identified an area of incompatibility, however, there are a number of basic approaches you can take to coping with it, as described in the following sections. 20.1.1 The Least-Common-Denominator ApproachOne technique for dealing with incompatibilities is to avoid them like the plague. For example, the Date object is notoriously buggy in Netscape 2. If you want Netscape 2 users to be able to use your programs, you can simply avoid relying on the Date object at all.[1]
As another example, Netscape 3 and IE 3 both support the opener property of the Window object, but Netscape 2 does not. The least-common-denominator approach says that you should not use this property if compatibility with Netscape 2 is a goal. Instead, you can create an equivalent property of your own whenever you open a new window: newwin = window.open("", "new", "width=500, height=300"); newwin.creator = self; If you consistently set a creator property for each new window you create, you can rely on that property instead of the nonportable opener property. (Another alternative, as we'll see later, is to give up on compatibility with Netscape 2 and require a browser that supports JavaScript 1.1 or later, as all such browsers support the opener property.) With this technique, you use only features that are known to work on all your target platforms. It doesn't allow you to write cutting-edge programs or push the envelope, but it results in portable, safe programs that can serve many important functions. 20.1.2 Defensive CodingWith the defensive coding approach to compatibility, you write code that contains platform-independent workarounds for platform-specific incompatibilities. For example, if you set the status property of a Window object from the onmouseover event handler to display a custom message in the status line, the status line is cleared when you move the mouse off the hyperlink, except in Windows versions of Netscape 2 and 3. To correct for this problem, you could get in the habit of including an onmouseout event handler to clear the status line. This precaution fixes the bug in current (and future) platforms that have it and doesn't do any harm on platforms that don't have the bug. 20.1.3 Feature TestingFeature testing is a powerful technique for coping with incompatibilities. If you want to use a feature that may not be supported by all browsers, include code in your script that tests to see whether that feature is supported. If the desired feature is not supported on the current platform, either do not use it on that platform or provide alternative code that works on all platforms. Consider again the opener property. In the least-common-denominator approach, we simply avoided the use of this property and used an alternative on all platforms. With the feature-testing approach, we provide the alternative only when the current platform does not support opener: newwin = window.open("", "new", "width=500, height=300"); if (!newwin.opener) newwin.opener = self; Note how we tested for the existence of the opener property. The same technique works to test for the existence of methods. For example, the split( ) method of the String object exists only for JavaScript 1.1 implementations. We can write our own version of this function that works in all versions of JavaScript, but for efficiency we'd like to use the fast, built-in method on those platforms that do support it. Thus, our feature-testing code to split( ) a string might end up looking like this: if (s.split) // Check if the method exists, without invoking it a = s.split(":"); // If it does exist, it is safe to invoke it else // Otherwise: a = mysplit(s, ":"); // use our alternative implementation Feature testing is commonly used for performing DHTML effects that are supported only on some browsers or are implemented differently in different browsers. For example, if you are designing a site that includes image rollover effects, you can use feature testing with code like this: if (document.images) { // If the browser defines an images[] array, // we include image rollover code here } // Otherwise, we simply omit the image rollover effect As another example, suppose we want to work with a dynamically positioned document element. Different browsers have different APIs for doing this, so we first use feature testing to see which API is supported by the current browser with code like this: if (document.getElementById) { // If the W3C DOM API is supported, // do our DHTML using the W3C DOM API } else if (document.all) { // If the IE 4 API is supported, // do our DHTML using the IE 4 API } else if (document.layers) { // If the Netscape 4 API is supported, // do the DHTML effect (as best we can) using the Netscape 4 API } else { // Otherwise, DHTML is not supported, // so provide a static alternative to DHTML, if we can } The nice thing about the feature-testing technique is that it results in code that is not tied to a specific list of browser vendors or browser version numbers. It works with the set of browsers that exist today and should continue to work with future browsers, whatever feature sets they implement. 20.1.4 Platform-Specific WorkaroundsFeature testing is well suited to checking for support of large functional areas. You can use it to determine whether a browser supports image rollovers or the W3C DOM API, for example. On the other hand, sometimes you may need to work around individual bugs or quirks in a particular browser, and there may be no easy way to test for the existence of the bug. In this case, you will need to create a platform-specific workaround that is tied to a particular browser vendor, version, or operating system (or some combination of the three). Recall from Chapter 13 that the navigator property of the Window object provides information about the vendor and version of the browser and the operating system on which it is running. You can use this information to insert platform-specific code into your program. An example of a platform-specific workaround involves the bgColor property of the Document object. On Windows and Macintosh platforms, you can set this property at runtime to change the background color of a document. Unfortunately, when you do this on Unix versions of Netscape 2 and 3, the color changes but the document contents temporarily disappear. If you wanted to create a special effect using a changing background color, you could use the Netscape object to test for Unix platforms and simply skip the special effect for those platforms. The code could look like this: // Check whether we're running Netscape 2 or 3 on a Unix platform var nobg = (parseInt(navigator.appVersion) < 4) && // Version (navigator.appName.indexOf("Netscape") != -1) && // Vendor (navigator.appVersion.indexOf("X11") != -1); // OS // If we're not, then go ahead and animate the page background color if (!nobg) animate_bg_color( ); When writing platform-specific workarounds, it is common to use " client-sniffer" code to determine what the current platform is, based (typically) on the properties of the navigator object. You run your client-sniffer code once, and it sets variables that describe the current platform. Then you don't have to reparse the properties of navigator for each platform-specific bit of code you write; you can simply use the variables set by the sniffer code. A simple sniffer that may be sufficient for many purposes might look like this: var browserVersion = parseInt(navigator.appVersion); var isNetscape = navigator.appName.indexOf("Netscape") != -1; var isIE = navigator.appName.indexOf("Microsoft") != -1; var agent = navigator.userAgent.toLowerCase( ); var isWindows = agent.indexOf("win") != -1; var isMac = agent.indexOf("mac") != -1; var isUnix = agent.indexOf("X11") != -1; With variables like these defined, you might write code like the following: if (isNetscape && browserVersion < 4 && isUnix) { // Work around a bug in Netscape 3 on Unix platforms here } A variety of prewritten client sniffers are available on the Internet. You can find a thorough one (along with a helpful discussion of its use) at http://www.mozilla.org/docs/web-developer/sniffer/browser_type.html. 20.1.5 Compatibility Through Server-Side ScriptsAnother platform-specific approach to compatibility is possible if your web application includes the use of server-side scripts, such as CGI scripts or server-side JavaScript. A program on the server side can inspect the User-Agent field of the HTTP request header, which allows it to determine exactly what browser the user is running. With this information, the program can generate customized JavaScript code that is known to work correctly on that browser. Or, if the server-side script detects that the user's browser does not support JavaScript, it can generate web pages that do not require JavaScript at all. An important drawback to this approach is that a server-side script cannot detect when a user has disabled JavaScript support in her browser. Note that the topics of CGI programming and server-side scripting in general are beyond the scope of this book. 20.1.6 Ignore the ProblemAn important question to ask when considering any incompatibility is, how important is it? If the incompatibility is minor or cosmetic, affects a browser or platform that is not widely used, or affects only an out-of-date version of a browser, you might simply decide to ignore the problem and let the users affected by it cope with it on their own. For example, earlier I suggested defining an onmouseout event handler to correct for the fact that Netscape 2 and 3 for Windows do not correctly clear the status line. Unfortunately, the onmouseout event handler is not supported in Netscape 2, so this workaround won't work for that platform. If you expect your application to have a lot of users who use Netscape 2 on Windows and you think that it is really important to get that status line cleared, you'll have to develop some other workaround. You could use setTimeout( ) in your onmouseover event handler to arrange for the status line to be cleared in two seconds. But this solution brings problems with it: what if the mouse is still over the hypertext link and the status line shouldn't be cleared in two seconds? In this case, a simpler approach might be to simply ignore the problem. This approach can easily be justified, because Netscape 2 is by now well out of date; any users still relying on it should be encouraged to upgrade. 20.1.7 Fail GracefullyFinally, there are some incompatibilities that cannot be ignored and cannot be worked around. In these cases, your program should work correctly on all platforms, browsers, and versions that provide the needed features and fail gracefully on all others. Failing gracefully means recognizing that the required features are not available and informing the user that he will not be able to use your JavaScript program. For example, the image-replacement technique we saw during the discussion of images in Chapter 14 does not work in Netscape 2 or Internet Explorer 3, and there is really no workaround that can simulate it. Therefore, we should not even attempt to run the program on those platforms; instead, we should politely notify the user of the incompatibility. Failing gracefully can be harder than it sounds. Much of the rest of this chapter explains techniques for doing so. |