20th February 2004
Updated 24th January 2005
In this short article, I am going to discuss one method of creating DHTML that is accessible to a wider audience. Accessibility has many facets. In the case of DHTML there are two broad categories of users to consider. Those users that have scripting enabled browsers and those users that do not. To present a page that is accessible to the widest possible audience, it is essential to try and serve all the content for the page to that audience. This can be beneficial to both the user and the business that the page represents, as this article will demonstrate. I hope the article will elucidate what I consider an important thought process when considering the implementation of dynamic behaviour on the client side web page.
Take a product catalogue, where a collection of images illustrates each product, with a separate page for each product. These images are quite large, but each one displays a different and equally important feature of the product. The obvious solution is to just display all the images at the same time. But this will take up considerable screen real estate, and will impart on the chosen presentation of the catalogue (Figure 1).
This is where DHTML can come to the rescue. A simple slideshow utility can be implemented, that either scrolls automatically to the next image or is controlled by the user (clicking next image - see Figure 2).
However, if the slideshow is hardcoded into the page, any user that does not have the appropriate script enabled (or available) in their web browser will only see the first image. For users of alternative and assistive mediums, where images are not displayed at all, a well written description can convey considerable meaning. However, likewise, an inaccessible script may provide them with only the description of the first image, thus only conveying part of the product's features. This failure in accessibility is both detrimental to the user, and the business supplying the product and catalogue.
Serve the page to reach the widest possible audience before introducing any dynamic features to those clients that can receive them. This thought process is a simple one, but an important one. With modern conformance to standards DHTML has become a very powerful tool that can create impressive usability. But in the global audience, DHTML enabled clients are a subset of the total web clients. Therefore a page should never be served on the assumption that dynamic features are available.1
In part this is the easy bit! Thanks to the power of Web Standards and the general conformance of the major browser vendors to these standards,
DHTML can be created once the page has arrived at the client. The page that is served will arrive on the client with static (non-behavioural) content. If the client browser is script enabled,
the page calls the external script file. This file then goes about creating the necessary presentation for dynamic features and associating corresponding
behaviour. In short, dynamic behaviour is not hardcoded into the structure of the document served (e.g. <a href="#" onclick="someFunction()" onkeypress="this.onclick">).
So let us look at some of the coding to acheive this. Here is the static XHTML code that is served with regards the product images:
<head>
[..snip..]
<script type="text/javascript" src="/s/img_control.js"></script>
</head>
<body>
[..snip..]
<div id="pimgs">
<img class="pimg" id="im1" src="/p/prod/_036/002.jpg" height="180" width="240" alt="Description" />
<img class="pimg" id="im2" src="/p/prod/_036/003.jpg" height="180" width="240" alt="Description" />
<img class="pimg" id="im3" src="/p/prod/_036/001.jpg" height="180" width="240" alt="Description" />
</div>
[..snip..]
</body>
As it stands this code will reach the widest possible audience. All the images are visible on image enabled browsers. On all other user agents an informative description of the image is provided via the alt tag. So users of speech browsers, braille browsers, text browsers or just image disabled browsers can still get an informative conceptualisation of the product. If DHTML had been hardcoded, a certain portion of that global audience would lose out on the complete description2.
With the static content served, an external script file, img_control.js, (here I am using Javascript) is retrieved by script-enabled browsers. The purpose of this script is three fold:
An onload behaviour must also be defined for the document to initiate the process.
To access the document structure, the W3C DOM can be used 3. The initialisation script is as follows:
1 // global variable stores the total number of images in the product container
2 var gTotal;
3 // global variable stores the index of the current image displayed on the page
4 var gIndex;
5 // define a constant for the value of the tab index attribute of the navigation controls
6 var INT_TAB = 20;
7
8 function initImageControl()
9 {
10 // exit if the DOM is not supported
11 // [or use alternative DOM]
12 if (document.getElementById("pimgs"))
13 {
14 var elemC = document.getElementById("pimgs");
15 // collect all the images in the product container into an array
16 var arrImgs = elemC.getElementsByTagName("img");
17 gTotal = arrImgs.length;
18 if (gTotal > 1)
19 {
20 // redefine the presentation of all but the first image
21 for (var i = 1; i < gTotal; i++)
22 {
23 var objProp = arrImgs[i].style;
24 objProp.display = "none";
25 objProp.visibility = "visible";
26 objProp.height = "auto";
27 objProp.width = "auto";
28 }
29
30 // initialise the global variable that stores index of current image
31 // the index of the first image is 0
32 gIndex = 0;
33
34 // create a navigation bar, append it to the product container ...
35 var elemNav = document.createElement("div");
36 elemNav.id = "navlinks";
37 elemC.appendChild(elemNav);
38
39 // ... and add the next and previous links
40 addLink("next");
41 addLink("previous");
42 }
43 }
44 }
45
46 function addLink(str)
47 {
48 /**
49 * creates a control element (as an anchor), sets its attributes,
50 * assigns behaviours and appends it to the navigation element.
51 */
52 var elem = document.createElement("a");
53 var text = document.createTextNode(str);
54 elem.appendChild(text);
55 elem.setAttribute("href", "#");
56 elem.setAttribute("title", "View the " + str + " photo");
57 elem.setAttribute("tabindex", INT_TAB);
58 // add behaviour to the link
59 elem.onclick = switchImage;
60 elem.onkeypress = elem.onclick;
61 elem.id = str.substring(0,4) + "link";
62 document.getElementById("navlinks").appendChild(elem);
63 // on intialisation the first image is displayed, so it is
64 // inappropriate to display the previous control link
65 if ("previous" == str) {elem.style.display = "none"};
66 }
The above code should be pretty much self-documenting and familiar to anyone who has used the Document Object Model (in any scripting language). It creates the next and previous link (hiding the later) and redefines the
presentation of all but the first image. The choice of styles in line 26 to 29 may appear a little unusual but will be explained below. (Note: setIdProperty is a user-defined utility function
that sets the style for an element, identified by the value of its id property.
The following code defines the behaviour for the next and previous links. The function
switchImage is assigned to the onclick and onkeypress
events of each navigation control.
1 function switchImage(evt)
2 {
3 /**
4 * simply redefines the presentation of the current image and
5 * the image to be displayed. these are determined by the value
6 * of the global variable gIndex.
7 */
8 evt = (evt) ? evt : (window.event) ? event : null;
9 if (evt)
10 {
11 var charCode = (evt.charCode) ? evt.charCode :
12 ((evt.keyCode) ? evt.keyCode : null);
13 if (evt.type == "click" || charCode == 13)
14 {
15 // the routine will only run if the event trigger was a left
16 // mouse click or the return key pressed.
17 var elem = (evt.target) ? evt.target :
18 ((evt.srcElement) ? evt.srcElement : null);
19 if (elem)
20 {
21 // switches the currently displayed image by hiding the image indexed by gIndex,
22 // incrementing/decrementing gIndex and making the newly indexed image visible
23 var arrImgs = document.getElementById("pimgs").getElementsByTagName("img");
24 var objProp = ("nextlink" == elem.id)
25 ? arrImgs[gIndex++].style
26 : arrImgs[gIndex--].style;
27 objProp.display = "none";
28 arrImgs[gIndex].style.display = "inline";
29 refreshLinks();
30 return false;
31 }
32 }
33 }
34 }
35
36 function refreshLinks()
37 {
38 /**
39 * defines the display property of the next and previous links based on
40 * the index of the current image (in the global variable gIndex).
41 */
42 document.getElementById("nextlink").style.display =
43 (gIndex == (gTotal - 1)) ? "none" : "block";
44 document.getElementById("prevlink").style.display =
45 (gIndex == 0) ? "none" : "block";
46 }
This is a fairly simple routine, which simply redefines the image currently displayed inline, and
the display status of the next and previous links. Since the behaviour has been assigned
to both the onclick and onkeypress events a test must first be
run to see what action triggered the event. If it was a key press event, the method should
only proceed if the key pressed was return.
So by declaring the document behaviour at the end of the script file window.onload=initImages the page will be redefined to show just the first image, and a navigation bar with next link
will be appended to the product container. However, there is still a rather disorientating presentational issue to resolve.
The initialisation script, initImageControl hides all but the first image. Since the images
must not take up screen real estate when they are not displayed, they should be declared as display:none. However, the script
does not run until the page and all images have loaded, so the net result of this single declaration will be to load the
images on the screen, and then remove them once they have loaded. This can be bewildering to the user, who catches a glimpse of each photo
before it disappears. Therefore, the images need to be hidden while they are loading. Since this presentational rule must not
interfere with the standard display rules, it must also be defined within the scripting language. The simplest way to do this is to declare
the rule at the top of the script file, and pass it straight to the browser, thus simulating an inline CSS declaration.
If, however, the display property of the images is assigned the value none as inline style declarations, the images will not actually
be downloaded to the cache, at least in the test browsers4. Therefore,
instead they can be declared as invisible and their height and width both set to zero. The following code demonstrates this at the top of the
external script file, img_control.js.
1 var strCSS = '<style type="text/css">' +
2 '/*<![CDATA[*/ ' +
3 '#im2,#im3,#im4,#im5 {visibility:hidden;height:0;width:0;}' +
4 '/*]]>*/' +
5 '</style>';
6 document.write(strCSS);
Now, the presentational redefinitions during initialisation (lines 26 to 29) become clear. With the images downloaded and stored in cache, their visibility and dimensions are reset and their display set to none.
This article has attempted to demonstrate a thought process towards the development of a more accessible DHTML
implementation. If the document is served to the widest possible audience before the
DHTML is built, accessibility can be achieved without the need for multiple versions. It also ensures that the
client only downloads the files they need thus conserving their bandwidth. In this particular demonstration the process is beneficial
to the site owner, since they are presenting their product complete to all users, whether it be as images or comprehensive alt
descriptions. The code has utilised Web Standards by synthesising the W3C DOM with CSS
XHTML and Javascript (ECMA Script).
As a final note, while the discussion has demonstrated a method to safely serve dynamic content to legacy browsers, it is inherently progressive, and as
such XHTML 1.1 cannot be ignored. XHTML 1.1 should be served with the mime-type application/xhtml+xml. Try this in
a browser that recognises it (eg Mozilla or Firefox) and the inline style declarations created by the document.write method will be
ignored. Instead the style element should be created using the DOM:
1 var strCSS = '/*<![CDATA[*/ ' +
3 '#im2,#im3,#im4,#im5 {visibility:hidden;height:0;width:0;}' +
4 '/*]]>*/';
5 if (document.getElementById && document.createElementNS) {
6 var elem=document.createElementNS("http://www.w3.org/1999/xhtml","style");
7 elem.setAttribute("type", "text/css");
8 var text = document.createTextNode(strCSS);
9 elem.appendChild(text);
10 document.getElementsByTagName("head")[0].appendChild(elem);
11 } else {
12 strCSS = '<style type="text/css">' + strCSS + '</style>';
13 document.write(strCSS);
14 }
To post a comment on this article please visit the corresponding web log entry.
20th February 2004, Copyright © 2004, Tom Wright, Severn Solutions
The following corrections were made -
An example of the Manual Image Slideshow is available at http://css.experiments.severnsolutions.co.uk/dhtml/slideshow/.