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">HtmlGoodies.com:</td> <td><input id="CheckboxHtmlGoodies" name="CheckboxHtmlGoodies" type="checkbox" value="Yes" /></td> </tr><tr> <td align="right">JavaScript.com:</td> <td><input id="CheckboxJavaScript" name="CheckboxJavaScript" type="checkbox" value="Yes" /></td> </tr><tr> <td align="right">wdvl.com</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.

reptile7

Thursday, August 11, 2011
 
The RegExp True Connection
Blog Entry #223

Today's post will wrap up our discussion of HTML Goodies' "Checking Your Work: Validating Input -- Getting Started" and "Checking Your Work: Validating Input -- The Cool Stuff" tutorials and their validations of field values for a guestbook form.

Computer OK

In the previous entry we examined the "Cool Stuff" tutorial's validation of the guestbook form's First Name and Last Name fields via a noNumbersExpression regexp pattern that flags name inputs containing any digit or symbol characters. Not quite complementarily, the "Cool Stuff" tutorial also provides a numbersOnlyExpression regexp pattern that can flag inputs that do not consist solely of digits and is used to validate the form's How many years have you been using the Internet? field.

var numbersOnlyExpression = /^[0-9]+$/;
// Validate years is a whole number
if (!numbersOnlyExpression.test(document.getElementById("TextInternetYears").value)) {
    validationMessage += " - Years using Internet must be a whole number\n";
    valid = false; }


As we are only interested in a true/false comparison between the numbersOnlyExpression pattern and the years input, the above code employs a regexpObject.test( )-based conditional vis-à-vis the tutorial's stringObject.match( )-based conditional.

The ^[0-9]+$ pattern matches a string that, from start to finish, comprises one or more digit characters, and can be shorthanded to ^\d+$.
• Read about the ^ and $ anchors here.
• The + operator is detailed here.
• The use of square brackets to delimit regexp character classes is fleshed out here.

If the user types hi into the How many years ... field, then that input will be intercepted by the numbersOnlyExpression conditional. Per its // Validate years is a whole number comment, the conditional will also flag a floating-point number input such as 5.5. On the minus side, the conditional will accept a number like 500, which is not exactly a meaningful input given that the Internet didn't exist 500 years ago.

So we need a new-and-improved numbersOnlyExpression pattern that puts a reasonable cap on the How many years ... value. A bit arbitrarily - granting that there is probably some disagreement as to when "the Internet" effectively began - I am going to set that cap to 25 years because 1986 was the year that the Internet Engineering Task Force came into being. Accordingly, here's the validation pattern I would use:

var digits0to25Expression = /^(1?\d|2[0-5])$/;

The 1?\d subpattern handles the 0-19 range of inputs; the ? operator optionalizes the tens-place 1 so that the \d matches the ones-place digit for both the 0-9 and 10-19 ranges. In turn, the 2[0-5] subpattern handles the 20-25 range of inputs. The subpatterns are separated by a vertical bar, which serves as a boolean OR operator. The subpattern composite must be wrapped in parentheses because the ^ and $ anchors as operators have a higher precedence than does the | operator; sans parentheses ^1?\d|2[0-5]$ would match strings such as 19apples and oranges20.

(The original numbersOnlyExpression allows for numbers with leading zeroes whereas my digits0to25Expression does not. But the leading zero thing now strikes me as a non-issue. I mean, why would users prepend zeroes to their inputs unless specifically directed to do so? And it's not as though leading zeroes invalidate the digits that follow them - at worst they make a number look a bit weird.)

Hangin' on the telephone

Let's move on to the harder stuff. The "Cool Stuff" tutorial vets the guestbook form's Telephone field via a telephoneExpression regexp pattern:

var telephoneExpression = /^((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}$/;

The telephoneExpression pattern accommodates the following telephone number formats:
(123) 456-7890
(123)456-7890
123-456-7890
456-7890

The \d{3}-\d{4} subpattern preceding the $ end-of-string anchor matches three digits followed by a hyphen followed by four digits, and is for the local part of the phone number.

The ((\(\d{3}\) ?)|(\d{3}-))? subpattern is for the area code part of the phone number; per the concluding ? operator, this part of the match is optional. The area code subpattern can be divided into two parts:

(\d{3}-) matches three digits followed by a hyphen.

(\(\d{3}\) ?) matches a ( left parenthesis followed by three digits followed by a ) right parenthesis followed by an optional space. The left parenthesis and the right parenthesis are both regexp metacharacters and must be literalized via a preceding \ backslash operator. A space in a regexp pattern actually matches a literal space character and is not just there to separate other regexp tokens.

The outer parentheses are unnecessary in both cases although they do improve the subpattern's readability a bit.

Instead of using one field to hold the user's phone number, it would be better to offer a -- three-field arrangement that moves focus from field to field as the phone number is filled in; we previously coded just such an arrangement in the Demo section of Blog Entry #173. If we stick with a single Telephone field, then we should indicate on the form the specific phone number format(s) that we want (the user shouldn't have to guess in this regard), for example:

Telephone (123-456-7890):

One format is good enough for me, and the 123-456-7890 format, which would allow us to simplify the telephoneExpression pattern to ^\d{3}-\d{3}-\d{4}$, is as good as any of them.

Email my

The "Cool Stuff" tutorial lastly provides an emailExpression regexp pattern for vetting the guestbook form's Email field.

var emailExpression = /^[\w\-\.\+]+\@[a-zA-Z0-9\.\-]+\.[a-zA-z0-9]{2,4}$/;

Crafting a regular expression for validating an email address is necessarily an exercise in compromise; as a matter of course a practicable email regexp pattern will fail to match some valid email addresses and will match some invalid email addresses, and the emailExpression pattern is no exception. With that caveat out of the way, here's what we've got in the emailExpression pattern:

• In JavaScript, the \w regexp token is a shorthand for the [a-zA-Z0-9_] character class. [\w\-\.\+]+ matches one or more letters case-insensitively, digits, underscores, hyphens, periods, and/or plus signs, and is for the local part of an email address. As part of a character class, the period and the plus sign do not need to be literalized with a backslash - see the Metacharacters Inside Character Classes section of the aforelinked Regular-Expressions.info "Character Classes" page - and this is also true for the class's non-range-spanning hyphen; consequently, the local-part subpattern can be written as [\w-.+]+.

\@ matches the @ symbol that separates the address's left-hand local part and its right-hand domains. Even outside of a character class, @ is not a regexp metacharacter and does not need to be literalized with a backslash.

[a-zA-Z0-9\.\-]+ matches one or more letters case-insensitively, digits, periods, and/or hyphens (the backslash operators are again unnecessary), and takes care of everything between the @ separator and the period that precedes the address's final/top-level domain; for example, this subpattern would match the uq.edu part of feedback@uq.edu.au.

\. matches the period that precedes the address's final/top-level domain (the backslash is needed here).

[a-zA-z0-9]{2,4} matches two-to-four letters case-insensitively and/or digits, and is for the address's final/top-level domain.

Commenter kburger raises two important issues regarding the [a-zA-z0-9]{2,4} subpattern:
If I'm reading it correctly it expects a TLD of between 2 and 4 characters. I used to use something similar to that until they allowed a couple (maybe more?) of new TLD's: .travel .museum And then it also looks like your regex would accept TLD's containing numbers. I don't think that's allowed, is it?
As of this writing:
(1) .museum and .travel are the only longer-than-four-letters generic top-level domains; accommodating them is a simple matter of changing the {2,4} operator to {2,6}.
(2) Not counting the ASCII versions of internationalized country code top-level domains, it is true that there are no top-level domains that contain digits. According to the Restrictions on domain (DNS) names section of RFC 3696 ("Application Techniques for Checking and Transformation of Names"), however, a top-level domain name can include but not consist solely of digits (and thus an a@b.99 input, which would pass validation, is indeed "not allowed").

Imperfect as it may be, the emailExpression pattern does match the overwhelming majority of valid email addresses out there. The emailExpression pattern is very similar to the /^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i pattern that Jan Goyvaerts of Regular-Expressions.info uses to validate email addresses; the latter pattern requires an alphabetic final/top-level domain and augments the local-part [\w.-] character class with a percent sign vis-à-vis a plus sign, but otherwise the two patterns are essentially identical. I'm not sure I've ever seen a + or a % in an email address but maybe you have, and we should probably put both of them in the emailExpression local part to be on the safe side.

The "A Feedback Form" page of WebReference.com's "JavaScript Regular Expressions" tutorial offers a reg1 regexp pattern that can be used to fend off several types of ordinarily invalid email addresses (those containing two or more @ characters, those with consecutive periods, etc.):

var emailExpression = /^[\w+%.-]+@[a-z\d.-]+\.[a-z\d]{2,6}$/i;
var reg1 = /(@.*@)|(\.\.)|(@\.)|(\.@)|(^\.)/; // not valid
// Validate email formatted properly
var emailValue = document.getElementById("TextEmail").value;
if (!emailExpression.test(emailValue) || reg1.test(emailValue)) {
    validationMessage += " - Your email address appears invalid\n";
    valid = false; }


In the name of completeness

The Restrictions on email addresses section of RFC 3696 states:
In addition to restrictions on syntax, there is a length limit on email addresses. That limit is a maximum of 64 characters (octets) in the "local part" (before the "@") and a maximum of 255 characters (octets) in the domain part (after the "@") for a total length of 320 characters. Systems that handle email should be prepared to process addresses which are that long, even though they are rarely encountered.
It is left to you to incorporate local-part/domain/overall length testing into the preceding code should you feel the need to do so.

More, more, more

The "Getting Started"/"Cool Stuff" guestbook form is rendered in the div below and is ready for your inputs. Clicking the button will trigger a revamped validateMyForm( ) function that uses the tests we have developed in the last three entries to vet values for the form's fields excepting the Country selection list, which is wired to a new !document.getElementById("SelectCountry").selectedIndex test that checks if the user has not selected a country. 'Blank' inputs for the text fields and the Country selection list will turn on "*Please provide ..." error messages next to those fields on the form. In all cases false is returned to the form's onsubmit event handler; your data set won't be sent to me or anyone else.

First Name: *Please provide your first name.
Last Name: *Please provide your last name.
Telephone (123-456-7890): *Please provide your phone number.
Email: *Please provide your email address.
Country: *Please select a country.
How many years have you been using the Internet? *Please provide an integer in the range 0-25, inclusive.
Add to mailing list? Yes No
Favorite websites? HTMLGoodies.com WDVL.com Internet.com JavaScript.com None


In the following entry we'll take on the next Beyond HTML : JavaScript sector tutorial, "Making a Wizard with JavaScript".

reptile7


Powered by Blogger

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