reptile7's JavaScript blog
Saturday, September 24, 2011
 
I Love an Image Parade
Blog Entry #227

We continue today our discussion of HTML Goodies' "Web Developer Tutorial: Build Your Own Image Scrollbar". In the previous post we deconstructed the tutorial's image scrollbar code. How does that code work in practice?

The scrollbar example

Just before the tutorial code is a parenthetical note providing a link to a demo. Originally the demo link pointed to http://www.htmlgoodies.com/img/2010/03/ImageScrollbar_Script.html (h/t commenter stephan), which is still live as of this writing. Within a couple of days of the tutorial's posting three readers had left comments complaining about the demo page, for example:
Chris 54 said on March 9, 2010 at 8:58 am
The example page doesn't work properly - the next and previous buttons don't work, so you can't see any other images (if any) Tried with Chrome and IE7
In a reply the author attempted to rationalize the demo page behavior by claiming that the html, head, and body tags got dropped from my example. How can that be? And so what? The start-tags and end-tags of the HTML html, head, and body elements are all optional - the absence of these tags shouldn't make any difference whatsoever.

After about a week the demo page had been moved off-site to its current location, http://www.nexistech.com/Imagescrollbar.htm. Shortly thereafter the author made the following comment:
Curtis Dicken said on March 16, 2010 at 2:07 pm
I just wanted to let everyone know that we figured out why the example did not work properly. There is nothing wrong with the code. Instead it turns out that the HTMLGoodies.com server is configured to attach certain JavaScript libraries to every page it serves up which was causing conflicts and odd behavior with the example. We have moved the example to another server and the example now functions as it should. I apologize for any problems this may have caused. It's always something. ;-)
Actually, when I first visited the (new) demo page the demo didn't function as it should. None of the images would load, although I could mouse over the Next >> button and the << Previous button label color immediately changed from silver to black and the Next >> button label color changed from black to silver within a few seconds. Moreover, Safari (but no other browser) threw a set of twelve "Failed to load resource: the server responded with a status of 403 (Forbidden)" errors. Doesn't sound very promising, does it? Fortunately, inspection of the demo source proved instructive and eventually enabled me to get the demo sorted out - as it happens, neither missing HTML tags nor attached JavaScript libraries are to blame.

The demo images themselves are off-site with respect to both the new and old demo pages. Check out the "image1.png" URL:

imagePath[0]="http://cache4.asset-cache.net/xc/sb10069137p-001.jpg?v=1&c=IWSAsset&k=2&d=8A33AE939F2E01FF64B2B2573E90E1541C8B733DE2CC979414FD0B94817AE1EA491BA17D91A07709";

All ten URLs are like that. Chopping off the query string doesn't lead to an image but rather a "400 Bad Request" page; interestingly, whittling down the URL to asset-cache.net results in redirection to http://www.gettyimages.com/, the Web site of Getty Images, Inc.

The complete URLs are live and do lead to the images you are supposed to see. Following those URLs to look at the images will cache the images, and once the images are cached, the demo at either the new or old demo page works like a charm, or at least that's how things go on my iMac.

The author seems to have had an inkling that the demo had an image-caching problem because the new demo page's source contains a block of image-preloading code that is not present in the old demo page's source or in the tutorial's The Image Scroller Code section.

<body onload="javascript:preloadThumbnails( );">
<!-- No, it is not necessary to use a JavaScript URL to trigger the preloadThumbnails( ) function. -->
...
// This function preloads the thumbnail images
function preloadThumbnails( ) {
    imageObject = new Image( );
    for (i = 0; i < imagePath.length; ++i)
        imageObject.src = imagePath[i]; }


The preloadThumbnails( ) function is problematic in that it creates a single imageObject object and then writes the src property of that object over and over vis-à-vis creating ten image objects with differing src values. preloadThumbnails( ) would thus seem to cache the imagePath images consecutively but not concurrently, that is, only the last imagePath image (imagePath[9]) should be preloaded at the end of the for loop: experimentation at my EarthLink server space revealed that this is indeed what happens when using Firefox, Opera, or Camino. However, preloadThumbnails( ) does in fact cache all ten images when using Safari, Chrome, or IE 5.2.3.

This Netscape example provides a template for preloading a set of ordinalized images in a cross-browser manner:

imageObjects = new Array( );
for (i = 0; i < imagePath.length; i++) {
    imageObjects[i] = new Image( );
    imageObjects[i].src = imagePath[i]; }


Something else I found out:
I was originally of the impression that it was necessary to reference the image object representing a preloaded image in order to act on the image

document.getElementById(ImageToChange).src = imageObjects[currentIndex].src;

but this is not true; once cached, the image is directly available via its URL.

document.getElementById(ImageToChange).src = imagePath[currentIndex];

One more point before moving on:
In the last entry I briefly discussed IE's support for the DOM setAttribute( ) method, linking to an MSDN page that seemed to specify that support as Windows Internet Explorer 8 and later (I've now removed that link). The image scrollbar code uses setAttribute( ) to set
(1) style attributes for the Next >>/<< Previous buttons and
(2) src attributes for the thumbnail placeholders.
On my computer, IE 4.x-5.x balk at (1) but do execute (2); noteworthily, irt.org fingers IE 4 as the starting point for IE's setAttribute( ) support. So perhaps we can leave the changeImage( ) function alone vis-à-vis exchanging its setAttribute( ) command for an imageObject.src = URL; assignment statement (although I do the latter in my demo below, 'to be on the safe side').

The image scroller code, take 2

In the tutorial's Conclusion section the author says:
Now, before you start sending your emails telling me how I can consolidate my code, keep in mind I intentionally was more verbose than necessary for the beginners.
I guess I'm not the only one who has taken him to task for his long-winded code, huh? If truth be told, the "Build Your Own Image Scrollbar" code isn't nearly as verbose as the "Making a Wizard with JavaScript" code we slogged through a few entries ago, but that doesn't mean it can't be profitably tightened up.

The scrollImages( ) function's if (scrollDirection == "up") and else clauses are superficially symmetrical and I initially hoped to roll them into a single block of code. My (admittedly not exhaustive) efforts in this regard were unsuccessful - there were too many variables to juggle going back and forth - and I ultimately decided that I was better off 'going the other way': IMO the cleanest, most straightforward way to code the scrollbar is to split the scrollImages( ) function into individual functions that respectively handle forward and backward scrolling.

Here's the function that replaces the if (scrollDirection == "up") clause:

var timer1;
function scrollLeft( ) {
    var currentIndex;
    if (imageIndexLast != maxIndex) {
        document.getElementById("scrollPreviousCell").style.color = "black";
        imageIndexLast++;
        imageIndexFirst++;
        currentIndex = imageIndexLast;
        var delay = 0;
        for (var i = 4; i > 0; i--) {
            window.setTimeout("document.getElementById('scrollThumb" + i + "').src = imageArray[" + currentIndex + "].src", delay);
            delay += 250;
            currentIndex--; }
    timer1 = window.setTimeout(scrollLeft, 1000); }
    else {
        document.getElementById("scrollNextCell").style.color = "silver";
        window.clearTimeout(timer1); } }
...
document.getElementById("scrollNextCell").onmouseover = scrollLeft;
document.getElementById("scrollNextCell").onmouseout = function ( ) { window.clearTimeout(timer1); }


The use of separate functions for forward and backward scrolling eliminates the need for the scrollDirection variable, thereby simplifying calls to those functions.

My intuition told me that the scrollNext( ) function, the scrollPrevious( ) function, the scrollAgain( ) function, the scrollStop( ) function, and the continueScroll on/off switch were all excess baggage, and I was right: they are easily eliminated by making recourse to the setTimeout( ) and clearTimeout( ) methods of the window object.

Finally, I wrote loops to automate the loading of images into the thumbnail placeholders; these loops incorporate the src-setting command of the changeImage( ) function, which was also thrown out.

Demo

To try out my revamped image scrollbar code, mouse over and out from the Next >> and << Previous strings flanking the thumbnail images in the div below.

<< PreviousNext >>
 
The scrollbar images have been given ordinalized file names - image1.jpg, image2.jpg, etc. (they're all .jpgs, not .pngs) - and are arrayed and preloaded via: imageArray = new Array(10); for (j = 0; j < 10; j++) {     imageArray[j] = new Image( ); } imageArray[0].src = "image_path/image1.jpg"; imageArray[1].src = "image_path/image2.jpg"; ... /* It was not possible to automatedly set the imageArray src values as the demo images were given irregular URLs when I uploaded them to Blogger. */ You can download the scrollbar images directly from me via the links below (right-click or control-click on each link and then select the "Save Linked File As..." or equivalent command in the contextual menu that pops up). image1.jpg image2.jpg image3.jpg image4.jpg image5.jpg image6.jpg image7.jpg image8.jpg image9.jpg image10.jpg
"Build Your Own Image Scrollbar" has a companion "JavaScript Tutorial: Build Your Own Image Viewer with Scrollbar" that connects the scrollbar to the large first-row image and we'll check it over in the next entry.

reptile7

Monday, September 12, 2011
 
The Krewe of Image
Blog Entry #226

For the next several entries we will work through a series of animation-related tutorials in the HTML Goodies Beyond HTML : JavaScript sector. Today we'll get into "Web Developer Tutorial: Build Your Own Image Scrollbar", which is yet another Curtis Dicken production. Perhaps you are wondering, "Just what are image scrollbars?" I myself had never heard of them. The author describes an image scrollbar thusly:
It's a collection of thumbnail size images, usually in a table, with buttons on either side of the images that allow you to scroll through the collection. It's a great space saver when Web site real estate is hard to come by.
Associated with the "Build Your Own Image Scrollbar" tutorial is a strange demo that is nonfunctional with the browsers on my computer - several people in the tutorial comment thread also report having trouble with it - and that we may devote a separate post to. When working on the desktop and with the scrollbar images in hand, however, I find that the tutorial code works fine as far as it goes.

Pre-scroll

The tutorial works with a series of ten images with ordinalized file names: image1.png, image2.png, etc. The image file names are organized as an imagePath array:

var imagePath = new Array(10);
imagePath[0] = "image1.png";
imagePath[1] = "image2.png";
...
imagePath[9] = "image10.png";


The last and first imagePath index numbers are respectively assigned to maxIndex and minIndex variables that will later be used to bound the scrolling process.

var maxIndex = 9;
var minIndex = 0;


The image series is "scrolled" forward and backward through a row of four 100px-by-100px ("thumbnail") img placeholders that are initially loaded with the image1.png, image2.png, image3.png, and image4.png images and whose ids are scrollThumb1, scrollThumb2, scrollThumb3, and scrollThumb4, respectively.

<img id="scrollThumb1" height="100" src="image1.png" style="border-right:1px solid; border-top:1px solid; border-left:1px solid; border-bottom:1px solid;" width="100" />

(What's with the lengthy border specification? Yes, we should be using a border:1px solid; style declaration instead.)

An imageIndexFirst variable is used to index the image held by the scrollThumb1 placeholder and an imageIndexLast variable is used to index the image held by the scrollThumb4 placeholder; imageIndexFirst and imageIndexLast are initialized to 0 and 3, respectively.

var imageIndexFirst = 0;
var imageIndexLast = 3;


The author puts the img placeholders in separate cells in the second row of a

<table border="0" cellpadding="5" cellspacing="0" width="700px"> ... </table>

table. (In HTML, width attribute values do not include unit identifiers; the table's width attribute should be replaced by a width:700px; style declaration.) The table's first row contains a 400px-by-400px img placeholder loaded with the image1.png image; at no point in the code is the first-row image replaced by another image.

Forward scrolling is effected via a Next >> td element "button" to the right of the placeholders whereas backward scrolling is effected via a corresponding << Previous td element button to the left of the placeholders.

<td id="scrollPreviousCell" style="color:silver;" onmouseover="scrollPrevious( );" onmouseout="scrollStop( );"> << Previous</td>
...img placeholder cells...
<td id="scrollNextCell" style="color:black;" onmouseover="scrollNext( );" onmouseout="scrollStop( );"> Next >></td>


If the aforementioned demo worked as advertised, here's what you'd see initially:

[The pre-scroll tutorial demo table]

Scroll it

When the page loads we are ready to begin forward scrolling. The Next >> button label color is black, signaling that the Next >> button is active; the << Previous button label color is silver ('grayed out'), signaling that the << Previous button is inactive. (Nothing happens if you 'push' the << Previous button because imageIndexFirst and minIndex are equal - see the else clause discussion at the end of the post.) Forward scrolling is initiated by mousing over the Next >> button, which triggers a scrollNext( ) function:

var continueScroll = 0;
...
function scrollNext( ) {
    continueScroll = 1;
    scrollImages("up"); }


A continueScroll variable serves as an on/off switch for the image scrollbar; continueScroll is initialized to 0 (scrolling is off) and is toggled to 1 (scrolling is on) by the scrollNext( ) function's first statement. The scrollNext( ) function subsequently calls a scrollImages( ) function and passes thereto an up string, meaning that the thumbnail placeholders will be sequentially loaded with imagePath images in order of increasing imagePath index number, thereby giving a right-to-left scroll - we'll roll out a demo in due course.

When the scrollImages( ) function is called, the up string is given a scrollDirection identifier. The first scrollImages( ) statement declares but does not initialize a currentIndex variable; currentIndex will be a sort of roving index for the images that are loaded into the thumbnail placeholders.

function scrollImages(scrollDirection) {
    var currentIndex;


The remainder of the scrollImages( ) function comprises one big if...else statement whose if and else clauses respectively handle forward and backward scrolling. The if clause first tests if scrollDirection is equal to up: check. It next tests if imageIndexLast (3) is not equal to maxIndex (9): check.

if (scrollDirection == "up") {
    // Only do work if we are not to the last image
    if (imageIndexLast != maxIndex) {


The if clause then switches the << Previous button label color to black, even though we haven't done any scrolling yet, and unnecessarily resets the Next >> button label color to black:

document.getElementById("scrollPreviousCell").setAttribute("style", "color:black;");
document.getElementById("scrollNextCell").setAttribute("style", "color:black;");


The setAttribute( ) method of the DOM Element interface goes back to the DOM Level 1 Core; its support began on the Microsoft side with IE 4 and on the Netscape side with Netscape 6. However, I find that setAttribute( ) has a "doesn't throw an error but isn't implemented in practice" status with IE 5.2.3 for Mac OS X. Users without setAttribute( ) support can be brought into the loop by trading in the first setAttribute( ) command for a corresponding style.color assignment

document.getElementById("scrollPreviousCell").style.color = "black";

and then ditching the unneeded second setAttribute( ) command.

Subsequently the if clause increments both imageIndexLast and imageIndexFirst; these operations are followed by a conditional that would switch the Next >> button label color to silver if imageIndexLast and maxIndex were equal (the placement of this statement also seems odd to me - I would have formulated it as an else clause to complement the if (imageIndexLast != maxIndex) { ... } clause we are currently dissecting):

imageIndexLast = imageIndexLast + 1;
imageIndexFirst = imageIndexFirst + 1;
if (imageIndexLast == maxIndex) {
    document.getElementById("scrollNextCell").setAttribute("style", "color:silver;"); }


We assign the imageIndexLast value (4) to currentIndex, and then call a changeImage( ) function and pass thereto scrollThumb4 and imagePath[currentIndex] (image5.png).

currentIndex = imageIndexLast;
changeImage("scrollThumb4", imagePath[currentIndex]);


Upon calling the changeImage( ) function

function changeImage(ImageToChange, MyImagePath) {
    document.getElementById(ImageToChange).setAttribute("src", MyImagePath); }


scrollThumb4 and imagePath[currentIndex] are respectively given ImageToChange and MyImagePath identifiers; these arguments are plugged into a command that sets the ImageToChange's src property to MyImagePath, that is, the command changes the scrollThumb4 placeholder's image from image4.png to image5.png.

currentIndex is decremented to 3; after a 25-millisecond delay, changeImage( ) is re-called to change the scrollThumb3 placeholder's image from image3.png to image4.png.

currentIndex = imageIndexLast - 1;
window.setTimeout("changeImage('scrollThumb3', imagePath[" + currentIndex + "]);", 25);


currentIndex is decremented to 2; after a 50-millisecond delay, changeImage( ) is re-called to change the scrollThumb2 placeholder's image from image2.png to image3.png. currentIndex is decremented to 1; after a 75-millisecond delay, changeImage( ) is re-called to change the scrollThumb1 placeholder's image from image1.png to image2.png.

currentIndex = imageIndexLast - 2;
window.setTimeout("changeImage('scrollThumb2', imagePath[" + currentIndex + "]);", 50);
currentIndex = imageIndexLast - 3;
window.setTimeout("changeImage('scrollThumb1', imagePath[" + currentIndex + "]);", 75);


The cumulative visual effect of the preceding currentIndex/changeImage( ) commands is one of right-to-left image movement (cf. the table below):
• The image1.png image is now 'off-screen', having scrolled beyond the left edge of the bar.
• The image2.png, image3.png, and image4.png images have each scrolled leftward by one position.
• The image5.png image has now scrolled into view at the right side of the bar.

The changeImage( ) call timeouts would seem to be off by a factor of 10:
We also use setTimeout( ) when we call our changeImage( ) function. This causes a specific delay before the function is called. We stagger the delays by a quarter of a second (25 milliseconds) so that we can create a pseudo-animation effect.
That would be 250 milliseconds, bro.

The if clause concludes by calling a scrollAgain( ) function after a 1-second delay:

window.setTimeout("scrollAgain('" + scrollDirection + "');", 1000);

The scrollAgain( ) function checks if continueScroll is still equal to 1 (it is) and then re-calls the scrollImages( ) function; scrollDirection is passed to and from the scrollAgain( ) function so as to continue up/forward scrolling.

function scrollAgain(scrollDirection) {
    if (continueScroll == 1) {
        scrollImages(scrollDirection); } }


In our second run through the scrollImages( ) function, imageIndexLast is incremented to 5 and image6.png, image5.png, image4.png, and image3.png are respectively loaded into the thumbnail placeholders; scrollImages( ) is re-called, imageIndexLast is incremented to 6, and image7.png, image6.png, image5.png, and image4.png are respectively loaded into the thumbnail placeholders; and so on until imageIndexLast hits 9 (maxIndex), at which point we have run out of imagePath images.

imageIndexLastscrollThumb1 imagescrollThumb2 imagescrollThumb3 imagescrollThumb4 image
3image1.pngimage2.pngimage3.pngimage4.png
4image2.pngimage3.pngimage4.pngimage5.png
5image3.pngimage4.pngimage5.pngimage6.png
6image4.pngimage5.pngimage6.pngimage7.png
...
9image7.pngimage8.pngimage9.pngimage10.png

We can stop the scrolling action at any time by mousing out from the Next >> button: doing so calls a scrollStop( ) function

function scrollStop( ) {
    continueScroll = 0; }


that toggles continueScroll to 0, thereby shutting down the scrollImages( ) call in the scrollAgain( ) function.

The scrollImages( ) else clause runs it all in reverse for backward scrolling. Mousing over the << Previous button calls a scrollPrevious( ) function

function scrollPrevious( ) {
    continueScroll = 1;
    scrollImages("down"); }


that switches continueScroll to 1, calls the scrollImages( ) function, and feeds to scrollImages( ) a down argument, whose assignment to scrollDirection effectively directs control to the else clause. The else code is conditioned by an if (imageIndexFirst != minIndex) { ... } statement that is inoperative when the page loads (recall that imageIndexFirst starts out at 0) but is executed if we've done any forward scrolling (recall that forward scrolling increments imageIndexFirst as well as imageIndexLast).

We'll put the tutorial demo under the microscope and (depending on how long that takes) perhaps also retool the code a bit in the following entry.

reptile7

Thursday, September 01, 2011
 
Wizard Nouveau
Blog Entry #225

In this post we will revamp the code of the JavaScript Wizard Example section of HTML Goodies' "Making a Wizard with JavaScript" tutorial. À la the previous post's deconstruction, we'll first deal with the code's HTML and then take on its JavaScript.

Structure (+ some presentation)

I noted last time that the "Making a Wizard with JavaScript" wizard resembles one big table - excepting the Step 1 introduction text, everything you see in the wizard is table element content - and it initially seemed like a good idea to code the wizard header, body, and footer as thead, tbody, and tfoot elements, respectively. Upon blithely applying a #HeaderTable td { width: 20%; } style rule to an id="HeaderTable" thead element (more precisely, to the td children thereof), however, it immediately became apparent that the thead layout would end up directing the tbody and tfoot layouts, and I didn't like that. Yes, we can give the tbody/tfoot elements their own style rules so as to override the thead layout, but I thought, "Do I really want to do that, or would I rather work with three completely independent zones of code?" I decided to go the latter route.

And why are we using all those tables in the first place? I'm sure you are aware that the W3C frowns on the use of tables for layout purposes:
Tables should not be used purely as a means to lay out document content as this may present problems when rendering to non-visual media. Additionally, when used with graphics, these tables may force users to scroll horizontally to view a table designed on a system with a larger display. To minimize these problems, authors should use style sheets to control layout rather than tables.
Accordingly, I next threw out the Steps 2/3/4 tables plus their span containers and replaced them with analogous div elements. Here's my new Step 2, for example:

#Step2, #Step3, #Step4 { display: none; text-align: right; }
label, input { margin: 5px; }
...
<div id="Step2">
<label for="TextFirstName">First name:</label>
<input id="TextFirstName" name="FirstName" type="text" /><br />
<label for="TextMiddleName">Middle name:</label>
<input id="TextMiddleName" name="MiddleName" type="text" /><br />
<label for="TextLastName">Last name:</label>
<input id="TextLastName" name="LastName" type="text" />
</div>


As shown, the control labels are marked up as label elements; if desired, the controls and their labels can be slightly pushed apart via the CSS margin property in lieu of the tables' cellpadding="5" attributes.

What about the align="right" attributes for the labels in the Steps 2/3/4 tables? Like most block-level elements, the div element has an effective width of 100%, that is, its width spans the width of the viewport; as a result, simply replacing the align="right" attributes with a text-align:right; style declaration would shift the Steps 2/3/4 labels and controls as div content to the right side of the viewport - not good. One approach to this problem is to set specific widths for the Steps 2/3/4 divs. Alternatively and more easily, we can give each div a shrink-to-fit width when we render it by setting its CSS display property to inline-block - note that the CSS text-align property applies to inline blocks as well as block-level elements. (We are not concerned with the flowed as a single inline box aspect of the inline-block value as there is no content to the right of the divs.)

As for Step 5, I decided to hold onto its table - I felt it was 'semantically appropriate' to do so because the Step 5 table does actually organize a table of information - but did at least discard the table's span container.

I replaced the header table with an id="dhead" div of five spans, whose borders and background-colors were set via conventional style rules; I kept the Step1/Step2/Step3/Step4/Step5 ids for the wizard body divs and the final review table; I replaced the footer table with an id="dfoot" div. I vertically separated the header div, the body divs/table, and the footer div with the following style rule sets:

#Step1, #Step2, #Step3, #Step4, #Step5 { position: relative; top: 25px; }
#dfoot { position: relative; top: 40px; }


Behavior

We move now to the wizard's JavaScript. The handleWizardNext( ) and handleWizardPrevious( ) functions tediously spell out each operation for the four forward step transitions and the four reverse step transitions. Is it really necessary to do this? For that matter, do we need to have separate handleWizardNext( ) and handleWizardPrevious( ) functions? In both cases the answer is no.

So, let us begin building a common handleWizard( ) function that will drive both forward and reverse movement through the wizard. This function will use a boolean-like direction variable to determine the direction of movement and a step variable to track the user's progress through the wizard.

var step = 1; // The wizard begins at Step 1.
function handleWizard(direction) { ... }
...
<div id="dfoot">
<input id="ButtonPrevious" type="button" value="Previous" disabled="disabled" name="reverse" onclick="handleWizard(this.name);" />
<input id="ButtonNext" type="button" value="Next" name="forward" onclick="handleWizard(this.name);" /> ...


The handleWizardNext( ) and handleWizardPrevious( ) operations can be divided into 'old' operations - operations pertaining to the wizard step we are leaving - and 'new' operations - operations pertaining to the step we are going to. Not counting the navigation button name change business, which the step index is superseding, there are two old operations that are carried out for all eight step transitions: (1) zeroing out the old step and (2) silverizing the old step's header in the header table. The step containers have 'ordinalized' ids (Step1, Step2, ...) as do the step headers (HeaderTableStep1, HeaderTableStep2, ...), allowing us to generalize the old-for-all-transitions operations via the step index:

document.getElementById("Step" + step).style.display = "none";
document.getElementById("HeaderTableStep" + step).style.backgroundColor = "silver";


There are two complementary new operations that apply to all eight step transitions: (1) displaying the new step and (2) yellowizing the new step's header in the header table. These operations can again be generalized via the step index:

document.getElementById("Step" + step).style.display = "inline-block";
document.getElementById("HeaderTableStep" + step).style.backgroundColor = "yellow";


To go from the old operations to the new operations requires us to increment step when moving forward and to decrement step when moving backward; we can effect either step change with a single statement that ties the change to the value of the direction variable:

direction == "forward" ? step++ : step--;
/* The ?: conditional operator is documented here in the Mozilla JavaScript Reference. */


The commands that un/disable the footer buttons and the loadStep5Review( ) function call are what remains; these are all new operations that pertain to a specific step transition and can be specified via step-based conditionals:

// Transition-specific operations going forward:
if (step == 2 && direction == "forward")
    document.getElementById("ButtonPrevious").disabled = false;
if (step == 5) {
    document.getElementById("ButtonNext").disabled = true;
    document.getElementById("SubmitFinal").disabled = false;
    loadStep5Review( ); }
// Transition-specific operations going backward:
if (step == 1)
    document.getElementById("ButtonPrevious").disabled = true;
if (step == 4 && direction == "reverse") {
    document.getElementById("ButtonNext").disabled = false;
    document.getElementById("SubmitFinal").disabled = true; }


For the step == 2 and step == 4 conditionals, the direction == "forward" and direction == "reverse" subconditions are unnecessary in the sense that at Step 2 we will want an enabled button and at Step 4 we will want an enabled button and a disabled button whether we are going forward or backward. Still, my preference is to test the wizard direction vis-à-vis executing redundant statements.

More behavior

We lastly address the loadStep5Review( ) function, which can also be significantly streamlined. The first four loadStep5Review( ) statements write the user's Steps 2/3 First name, Middle name, Last name, and Email inputs to the Step 5 review table cells whose ids are ReviewFirstName, ReviewMiddleName, ReviewLastName, and ReviewEmail, respectively. These commands differ only in the <input>-name substrings of their getElementById( ) arguments and are thus easily automated:

var fieldName = ["FirstName", "MiddleName", "LastName", "Email"];
for (i = 0; i < fieldName.length; i++)
    document.getElementById("Review" + fieldName[i]).innerHTML = document.getElementById("Text" + fieldName[i]).value;


As for the subsequent conditionals that, per the user's Step 4 checkbox selections, load Yes or No into the Step 5 table cells whose ids are ReviewHtmlGoodies, ReviewJavaScript, and ReviewWdvl, it has probably occurred to you that those statements can be condensed via the ?: operator, e.g.:

document.getElementById("ReviewHtmlGoodies").innerHTML = document.getElementById("CheckboxHtmlGoodies").checked ? "Yes" : "No";

The checkbox conditionals can also be automated à la the FirstName/MiddleName/LastName/Email commands:

var fieldName2 = ["HtmlGoodies", "JavaScript", "Wdvl"];
for (i = 0; i < fieldName2.length; i++)
    document.getElementById("Review" + fieldName2[i]).innerHTML = document.getElementById("Checkbox" + fieldName2[i]).checked ? "Yes" : "No";


I was going to finally note that adding a "Password" element to the above fieldName array will display the Step 3 Password input in unasterisked form in the review table's id="ReviewPassword" cell, thereby allowing the user to check the accuracy (and not just the length) of that input, but this now strikes me as something we shouldn't be doing; after all, passwords are supposed to be as private as possible, and it should be the responsibility of the user, and not the Webmaster, to maintain the integrity of a password.

Demo #2

The wizard demo in the div below incorporates the code discussed in this entry.

Step 1: Getting StartedStep 2: NameStep 3: Account AccessStep 4: Select subscriptionsStep 5: Finalize & Submit
Welcome to our Subscription Wizard!

This wizard simulates subscribing for access to website content. Each step is highlighted in the header.
This step is intended to provide the user with everything they need to know to get started.

Next up in the Beyond HTML : JavaScript sector is "Web Developer Tutorial: Build Your Own Image Scrollbar", which we'll tuck into in the following entry.

reptile7


Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)