4.4. Cross-Platform Position ScriptingReconciling the differences among scriptable, CSS-enabled browsers is far easier than in the days when Navigator 4 was a part of the equation. In fact, if you prefer to support only those browsers that use W3C DOM element and CSS attribute referencing syntax, your primary job is fashioning your code to allow other browsers to degrade gracefully. The following sections discuss three basic techniques you can use to implement position scripting in a document that may be viewed by various browser generations:
Browser support flagging and explicit branching place the branching code directly in your scripts. For a limited amount of scripted positioning, having all the branching code in your scripts is manageable and actually easier to debug. But if you are doing a serious amount of scripted positioning, a custom API (Application Program Interface) lets you push the ugly branching code off to the side in an external library. In essence, you create a metalanguage that gives you control over the specific syntax used in both browsers. A custom API requires a lot more work up front, but once the API code is debugged, the API simplifies not only the current scripting job, but any subsequent pages that need the same level of scriptability. 4.4.1. Browser Support FlaggingIf you intend to support multiple browser classes, you will likely prefer to set up global variable Boolean flags (JavaScript global variables scope only within the current document) that indicate browser features that are important to your scripts. In the same code that establishes those variables, you should include code that redirects browsers not capable of rendering positionable elements to another page that explains the browser requirements. Unlike pages that use regular style sheets, which generally degrade acceptably for older browsers, pages with positioned elements fare very poorly when viewed with older browsers, especially if the intended design includes overlapping and/or hidden elements. As I discussed in Chapter 2, you are best served using object detection to establish whether the current browser supports what you need. In the case of combined support for positioned elements in the IE 4 and W3C DOM environments, globals can be set for both element referencing types, as well as support for the style property. Example 4-1 shows a script sequence that should run as a page loads, to set flags arbitrarily named isW3C, isIE4, and isCSS; the script also redirects older browsers to another page. Example 4-1. A JavaScript browser support flag script// Global variables var isCSS, isW3C, isIE4; // initialize upon load to let all browsers establish content objects function initFlags( ) { if (document.images) { isCSS = (document.body && document.body.style) ? true : false; isW3C = (isCSS && document.getElementById) ? true : false; isIE4 = (isCSS && document.all) ? true : false; } if (!isW3C && !isIE4) { top.location.href = "noDHTML.htm"; } } // set event handler to initialize flags window.onload = initFlags; Global flag initialization occurs inside a function that is invoked after the page loads. Some browser versions do not treat the document node or body element as valid objects until the <body> tag loads. Since this element provides a good signal for a browser's DHTML support, the flags will more accurately reflect the browser's capabilities if the test is performed after the page loads. Note that you should exercise care in assigning a function to window.onload in external script libraries. If multiple libraries assign a function to this event handler property, only the last one to load "wins." Similarly, if the <body> tag includes an onload event handler, it can override event handler property assignments. You cannot pile on multiple assignments through event handler property assignments. Because the document.body property is supported in any browser that supports scripted positioning, that property is used as a gateway to verify support for the style object. In this example, the isCSS flag is part of the decision trees for both the isW3C and isIE4 flags. Later scripts can rely on just these two flags to indicate style support, as well. The final segment of the initFlags( ) function in Example 4-1 provides an escape route for other browsers. They are led to another page that won't show positioned elements. This kind of flagging works well for introductory pages that direct visitors to parts of a site tailored to browser features, such as one path each for DHTML and non-DHTML browsers. Rather than immediately redirecting a visitor to an alternate page, scripts behind the clickable links on the page can use the flags quietly to lead users to subsequent pages best suited for their browsers' powers. 4.4.2. Explicit BranchingFor the occasional need to control the property of a positionable element, an explicit branch does the job without a lot of fuss. Because standard CSS-style properties are identical for W3C and IE DOMs, all that's needed is a way to equalize element references. Example 4-2 shows a script fragment whose job it is to move an element (named face) to a particular coordinate point relative to the positioning context of the document. This fragment assumes that global flags similar to those shown in Example 4-1 have been set elsewhere in the document. Example 4-2. Simple branchingfunction placeFace( ) { var elem = (isW3C) ? document.getElementById("face") : ((isIE4) ? document.all("face") : null); if (elem) { elem.style.left = "25px"; elem.style.top = "15px"; } } Repeated use of the global flags in many such functions in a document is marginally more efficient than using the expanded condition tests for the existence of document properties. The less object evaluation that takes place in a script, the better—even at the expense of a few more variables. 4.4.3. Custom APIsIf you find yourself doing a lot of scripting of positionable elements in your applications, it is probably worth the effort to create a custom API that you can link into any application you create. A custom API can take care of the "grunt" work for common position-scripting tasks, such as moving, hiding, showing, and resizing elements, as well as setting background colors or patterns. When you define a custom API library, the methods you write become the interface between your application's scripts and various positioning tasks. Many programmers prefer to make things happen via function calls, rather than assigning values to properties. Example 4-3 gives you a sample of what such an API library might look like. For the sake of readers who used this API from the first edition (and may therefore still need to support Navigator 4), the API in Example 4-3 includes branches for Navigator 4's unique syntax, as well as the W3C and IE formats. Although internal processing of this new version is different from the first one, this API is compatible with the previous version. The API defines the following functions:
Additional helper functions for determining the height and width of the browser window's content region assist with many positioning tasks relative to the window (rather than the document). Example 4-3 provides a custom API for positionable elements. Example 4-3. A custom API for positionable elements// DHTMLapi.js custom API for cross-platform // object positioning by Danny Goodman (http://www.dannyg.com). // Release 2.0. Supports NN4, IE, and W3C DOMs. // Global variables var isCSS, isW3C, isIE4, isNN4, isIE6CSS; // initialize upon load to let all browsers establish content objects function initDHTMLAPI( ) { if (document.images) { isCSS = (document.body && document.body.style) ? true : false; isW3C = (isCSS && document.getElementById) ? true : false; isIE4 = (isCSS && document.all) ? true : false; isNN4 = (document.layers) ? true : false; isIE6CSS = (document.compatMode && document.compatMode.indexOf("CSS1") >= 0) ? true : false; } } // set event handler to initialize API window.onload = initDHTMLAPI; // Seek nested NN4 layer from string name function seekLayer(doc, name) { var theObj; for (var i = 0; i < doc.layers.length; i++) { if (doc.layers[i].name == name) { theObj = doc.layers[i]; break; } // dive into nested layers if necessary if (doc.layers[i].document.layers.length > 0) { theObj = seekLayer(document.layers[i].document, name); } } return theObj; } // Convert object name string or object reference // into a valid element object reference function getRawObject(obj) { var theObj; if (typeof obj == "string") { if (isW3C) { theObj = document.getElementById(obj); } else if (isIE4) { theObj = document.all(obj); } else if (isNN4) { theObj = seekLayer(document, obj); } } else { // pass through object reference theObj = obj; } return theObj; } // Convert object name string or object reference // into a valid style (or NN4 layer) reference function getObject(obj) { var theObj = getRawObject(obj); if (theObj && isCSS) { theObj = theObj.style; } return theObj; } // Position an object at a specific pixel coordinate function shiftTo(obj, x, y) { var theObj = getObject(obj); if (theObj) { if (isCSS) { // equalize incorrect numeric value type var units = (typeof theObj.left == "string") ? "px" : 0; theObj.left = x + units; theObj.top = y + units; } else if (isNN4) { theObj.moveTo(x,y); } } } // Move an object by x and/or y pixels function shiftBy(obj, deltaX, deltaY) { var theObj = getObject(obj); if (theObj) { if (isCSS) { // equalize incorrect numeric value type var units = (typeof theObj.left == "string") ? "px" : 0; theObj.left = getObjectLeft(obj) + deltaX + units; theObj.top = getObjectTop(obj) + deltaY + units; } else if (isNN4) { theObj.moveBy(deltaX, deltaY); } } } // Set the z-order of an object function setZIndex(obj, zOrder) { var theObj = getObject(obj); if (theObj) { theObj.zIndex = zOrder; } } // Set the background color of an object function setBGColor(obj, color) { var theObj = getObject(obj); if (theObj) { if (isNN4) { theObj.bgColor = color; } else if (isCSS) { theObj.backgroundColor = color; } } } // Set the visibility of an object to visible function show(obj) { var theObj = getObject(obj); if (theObj) { theObj.visibility = "visible"; } } // Set the visibility of an object to hidden function hide(obj) { var theObj = getObject(obj); if (theObj) { theObj.visibility = "hidden"; } } // Retrieve the x coordinate of a positionable object function getObjectLeft(obj) { var elem = getRawObject(obj); var result = 0; if (document.defaultView) { var style = document.defaultView; var cssDecl = style.getComputedStyle(elem, ""); result = cssDecl.getPropertyValue("left"); } else if (elem.currentStyle) { result = elem.currentStyle.left; } else if (elem.style) { result = elem.style.left; } else if (isNN4) { result = elem.left; } return parseInt(result); } // Retrieve the y coordinate of a positionable object function getObjectTop(obj) { var elem = getRawObject(obj); var result = 0; if (document.defaultView) { var style = document.defaultView; var cssDecl = style.getComputedStyle(elem, ""); result = cssDecl.getPropertyValue("top"); } else if (elem.currentStyle) { result = elem.currentStyle.top; } else if (elem.style) { result = elem.style.top; } else if (isNN4) { result = elem.top; } return parseInt(result); } // Retrieve the rendered width of an element function getObjectWidth(obj) { var elem = getRawObject(obj); var result = 0; if (elem.offsetWidth) { if (elem.scrollWidth && (elem.offsetWidth != elem.scrollWidth)) { result = elem.scrollWidth; } else { result = elem.offsetWidth; } } else if (elem.clip && elem.clip.width) { result = elem.clip.width; } else if (elem.style && elem.style.pixelWidth) { result = elem.style.pixelWidth; } return parseInt(result); } // Retrieve the rendered height of an element function getObjectHeight(obj) { var elem = getRawObject(obj); var result = 0; if (elem.offsetHeight) { result = elem.offsetHeight; } else if (elem.clip && elem.clip.height) { result = elem.clip.height; } else if (elem.style && elem.style.pixelHeight) { result = elem.style.pixelHeight; } return parseInt(result); } // Return the available content width space in browser window function getInsideWindowWidth( ) { if (window.innerWidth) { return window.innerWidth; } else if (isIE6CSS) { // measure the html element's clientWidth return document.body.parentElement.clientWidth; } else if (document.body && document.body.clientWidth) { return document.body.clientWidth; } return 0; } // Return the available content height space in browser window function getInsideWindowHeight( ) { if (window.innerHeight) { return window.innerHeight; } else if (isIE6CSS) { // measure the html element's clientHeight return document.body.parentElement.clientHeight; } else if (document.body && document.body.clientHeight) { return document.body.clientHeight; } return 0; } Notice that every function call in the API invokes the getObject( ) function. If the parameter passed to a function is already an object, the object reference is passed through to the function's other statements. But it also accepts a string value for an element ID (or Navigator 4 layer name). You can use the custom API in Example 4-3 as-is or as a foundation for your own extensions that fit the kinds of positioning tasks your applications require. Your version will probably grow over time, as you further enhance the positioning techniques used in your applications. When you write a custom API, save the code in a file with any filename that uses the .js extension. Then, you can link the library into an HTML document with the following tag pair in the head portion of the document: <script language="JavaScript" type="text/javascript" src="myAPI.js"></script> Once you do this, all the functions and global variables in the custom API library become immediately available to all script statements in the HTML document. Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|