reptile7's JavaScript blog
Monday, August 22, 2011
Programming in the City of Emeralds
Blog Entry #224

Today we will take up HTML Goodies' "Making a Wizard with JavaScript" tutorial, which like the "Checking Your Work: Validating Input" tutorials we've been discussing in the last few entries is authored by Curtis Dicken. In an application context, a wizard is a structured, multistage interface for accomplishing a task of some sort. A common example of a software wizard is the bill payment service offered by many banking Web sites: a bill payment wizard features a series of pages on which the user selects a payee, enters payment information (a monetary amount, a send-by date, a brief description), reviews the payment information, submits the payment, and finally (if all goes well) is notified that the payment has been successfully scheduled - you know the drill.

"Making a Wizard with JavaScript" codes a wizard for subscribing to some Web sites: more specifically, the tutorial wizard presents
(1) a form with (a) a set of text fields for entering a name, email address, and password and (b) a checkbox menu of Web sites to subscribe to, followed by
(2) a table that reviews the form data set;
in practice, the data set is not sent off to a processing agent so we won't actually be subscribing to anything. No demo is offered for the wizard, prompting Prasanna Prabhu in the tutorial comment thread to say:
If you could post a sample screen shot of what your code looked like along with a video (on YouTube) that would be a great value-add.
Quite. I can confirm that seeing the rendered wizard makes a huge difference in being able to make sense of the wizard code and the author's deconstruction thereof. But of course, screen shots and a YouTube video are not really acceptable substitutes for a live demo in a div, are they?

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.

You may safely click the submit button at the end of the wizard: you will be brought back to the wizard introduction because the form's method has been switched from post to get and its action is set to an empty string (i.e., the action value resolves to the current page's URL and therefore the current page will reload). The code appearing in the tutorial's JavaScript Wizard Example section works fine, and other than the aforementioned method change I employed it verbatim (well, almost verbatim, formatting tweaks aside) for the above demo. We'll begin our analysis with a brief tour of the tutorial code and then look at how we might improve it. Overview/HTML The tutorial wizard has a table-like organization, comprising a mostly fixed header, a mostly fixed footer, and a body that changes completely upon the click of a button. (After I had run through the wizard a couple of times, I thought, "This is nothing more, and nothing less, than a high-end slide show.") The wizard body is divided into five steps: Step 1 provides an introduction for the user; Step 2, Step 3, and Step 4 are for collecting the information outlined earlier; Step 5 tabulates the data collected by the preceding steps. These steps are described in general terms in the tutorial's Parts of a Wizard section and a bit more specifically in the tutorial's Breaking Down the Wizard section; the latter section also discusses the row of step headers at the top of the display and the row of buttons at the bottom of the display. A single, id="SubscriptionWizard" form holds the Steps 2/3/4 fields and the footer buttons. The Steps 2/3/4 fields and their labels (not marked up as such) are laid out via table elements, which in turn are wrapped in span elements that we'll render and disappear via the CSS display property. Here's the Step 4 HTML, for example: <span id="Step4" style="display:none"> <table border="0" cellpadding="5" cellspacing="0"> <tr> <td align="left" colspan="2"><strong>Select your subscription:</strong></td> </tr><tr> <td align="right"></td> <td><input id="CheckboxHtmlGoodies" name="CheckboxHtmlGoodies" type="checkbox" value="Yes" /></td> </tr><tr> <td align="right"></td> <td><input id="CheckboxJavaScript" name="CheckboxJavaScript" type="checkbox" value="Yes" /></td> </tr><tr> <td align="right"></td> <td><input id="CheckboxWdvl" name="CheckboxWdvl" type="checkbox" value="Yes" /></td> </tr></table></span> The border attribute has been deprecated for the img and object elements but is still valid for the table element. The align attribute has been deprecated for most of the elements that could legitimately take it but is still valid for internal table elements such as the td element. The Step 5 review table basically consists of a left-hand column of headers and a right-hand column of empty cells that will be filled with the user's Steps 2/3/4 field values; this table is also wrapped in a display:none; span. <span id="Step5" style="display:none"> <table border="0" cellpadding="5" cellspacing="0"> <tr> <td colspan="2"><strong>Final Review:</strong></td> </tr><tr> <td align="right">First name:</td> <td id="ReviewFirstName"></td> </tr><tr> <td align="right">Middle name:</td> <td id="ReviewMiddleName"></td> </tr><tr> <td align="right">Last name:</td> <td id="ReviewLastName"></td> </tr><tr> ... The Step 1 introduction text is placed in a span that effectively has a block display by virtue of being surrounded by br elements. The wizard header and footer are again laid out via table elements. Like the non-introduction span displays, the highlight and non-highlight background colors of the header table's individual step headers are initially set via inline style attributes. <table border="1" cellpadding="5" cellspacing="0" id="HeaderTable"> <tr> <td id="HeaderTableStep1" style="background-color:yellow;">Step 1: Getting Started</td> <td id="HeaderTableStep2" style="background-color:silver;">Step 2: Name</td> ... We noted in Blog Entry #219 that the span element has an (%inline;)* content model and should not contain block-level elements such as the table element; we will exchange the step spans for divs and throw out most of the layout tables in due course. The statements behind the curtain We're ready to see what makes the wizard tick. The JavaScript part of the wizard code comprises (1) a handleWizardNext( ) function that drives forward movement through the wizard, (2) a handleWizardPrevious( ) function that drives reverse movement through the wizard, and (3) a loadStep5Review( ) function that loads the user's Steps 2/3/4 field values into the empty cells of the Step 5 review table. The handleWizardNext( ) and handleWizardPrevious( ) functions are respectively triggered by clicking the and buttons in the footer table, which I didn't write out for you earlier so let me give it to you now: <table border="0" cellpadding="5" cellspacing="0"> <tr> <td><input id="ButtonPrevious" type="button" value="Previous" disabled="disabled" name="" onclick="handleWizardPrevious( );" /></td> <td><input id="ButtonNext" type="button" value="Next" name="Step2" onclick="handleWizardNext( );" /></td> <td><input id="SubmitFinal" type="submit" value="Finish" disabled="disabled" /></td> </tr></table> So, we click the button to go from Step 1: Getting Started to Step 2: Name. Somewhat oddly, the author chose the name values of the and buttons to index/monitor the user's progress through the wizard. Accordingly, the handleWizardNext( ) function first checks if the button's name is Step2, the step to which we want to go. function handleWizardNext( ) {     if (document.getElementById("ButtonNext").name == "Step2") { For our initial run through the handleWizardNext( ) function, the if condition returns true. The if clause kicks off by setting the names of the and buttons to Step3 and Step1, respectively. document.getElementById("ButtonNext").name = "Step3"; document.getElementById("ButtonPrevious").name = "Step1"; Once we're at Step 2, we'll want a button click to take us to Step 3 and a button click to take us to Step 1 - that's the line of reasoning behind these button name choices. However, we still have several more things to do before we get to Step 2. The button, initially disabled="disabled" for the introduction step, is next undisabled via the following command: document.getElementById("ButtonPrevious").disabled = "";   The disabled attribute of the DOM HTMLInputElement interface takes a boolean value and we would normally set it to false to undisable something. That said, I find that any of JavaScript's convertible-to-false-in-a-boolean-context values - the empty string, 0, null, or undefined - can be used to activate a disabled control when using Firefox, Safari, or Opera. That takes care of the footer. Moving to the wizard body, the if clause subsequently replaces the Step 1 span with the Step 2 span: document.getElementById("Step1").style.display = "none"; document.getElementById("Step2").style.display = ""; You might expect the latter command to throw an error as the empty string isn't one of the display property's enumerated values, and yet it does display the Step 2 span without incident; with Firefox, Safari, or Opera, null can be used in place of the empty string. My best guess* as to what's happening here: the empty string or null assignment clears the Step 2 span's display:none; style, after which the initial (default) display value, inline, kicks in. Anyway, we really should specifically set the Step 2 style.display to inline or block - both values give an identical rendering. (*What effect does document.bgColor = false; or window.resizeTo("cat", "dog"); have? HTML and CSS address the use of illegal values but JavaScript's specifications are oddly silent on this subject.) Moving to the wizard header, the if clause finally transfers the Step 1: Getting Started header's yellow highlight to the Step 2: Name header: document.getElementById("HeaderTableStep2").style.backgroundColor = "yellow"; document.getElementById("HeaderTableStep1").style.backgroundColor = "silver"; The if clause is followed by three else if clauses that respectively effect the corresponding transitions to Step 3, Step 4, and Step 5. At the end of the last else if clause the loadStep5Review( ) function is called. The loadStep5Review( ) function first scoops up the values entered into Step 2's First name, Middle name, and Last name fields and Step 3's Email field and writes those values to the appropriate cells of the Step 5 review table. function loadStep5Review( ) {     document.getElementById("ReviewFirstName").innerHTML = document.getElementById("TextFirstName").value;     ... The loadStep5Review( ) function next looks at each Step 4 checkbox to see if it is checked or not, in which case Yes or No is respectively written to the relevant cell of the review table. if (document.getElementById("CheckboxHtmlGoodies").checked == 1) {     document.getElementById("ReviewHtmlGoodies").innerHTML = "Yes"; } else {     document.getElementById("ReviewHtmlGoodies").innerHTML = "No"; } ... The == 1 part of the if condition is unnecessary; like disabled, checked is a boolean attribute. Lastly, the loadStep5Review( ) function creates a string of asterisks having the same length as the value entered into Step 3's Password field and then loads the asterisk string into its designated review table cell. var iCharacterCount = document.getElementById("TextPassword").value.length; var passwordMasked = ""; for (var iCounter = 1; iCounter <= iCharacterCount; iCounter++) {     passwordMasked = passwordMasked + "*"; } document.getElementById("ReviewPassword").innerHTML = passwordMasked; As noted earlier, the handleWizardPrevious( ) function runs the wizard in reverse, thereby allowing the modification of user inputs. The handleWizardPrevious( ) function sports the same structure and operations as the handleWizardNext( ) function so there's no need for us to discuss it.
We'll give the wizard code a thorough overhaul in the next post.


Comments: Post a Comment

<< Home

Powered by Blogger

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