reptile7's JavaScript blog
Tuesday, June 24, 2008
 
Incompatible Options II
Blog Entry #117

Welcome back, Weekend Silicon Warriors, to our ongoing analysis of HTML Goodies' "A Case for a Case" tutorial. Having sorted out the tutorial's validation script in the previous entry by
(a) adding a default clause to the outer switch statement, and
(b) appending break statements to the case clauses of the outer switch statement,
we are ready to roll out a script demo, which appears in the div below. The Submit Your Top/Base Choices 'submit button' is actually a

<input type="button" value="Submit Your Top/Base Choices" onclick="checkTopAndBase(this.form);" />

generic push button; your Top/Base choices will not be sent to me or anyone else when you click it. 'Correct' Top/Base combinations will be greeted by a "Thank you for your purchase!" message; 'incorrect' Top/Base combinations will be greeted by the script's original "Sorry, wrong Top and Base combination." error message.

Welcome to the "Case for a Case" demo.

Dear customers:
(1) You must choose both a Top module and a Base module.
(2) Top #1 can only be paired with Base #1, #2, #5, or #6.
(3) Top #2 can only be paired with Base #1, #2, #5, or #6.
(4) Top #3 can only be paired with Base #2, #3, #4, #6, or #7.
If you don't follow these rules, then we'll call up Cousin Vinnie over at HTML Goodies and have him take care of you. ;-)
Thank you for your cooperation.




In putting together this demo, I found (contra an earlier version of the previous post) that when the outer switch statement's default clause is placed after its third case clause, then fallthrough will occur for the Top #3/Base #2, Top #3/Base #3, Top #3/Base #4, Top #3/Base #6, and Top #3/Base #7 pairings, and generate "Please choose a Top module." false-negatives, if the third case clause does not conclude with a break statement.

Validation via regular expressions

When I think of form validation, my first instinct is to reach for Mr. Regular Expression. In this section, we'll use the following simple regular expressions to carry out the "Case for a Case" validation and compact the script code even further:

var Top1or2 = /1|2|5|6/;
var Top3 = /2|3|4|6|7/;

The Top1or2 pattern matches one of the theForm.base.selectedIndex indexes for the Base #1, Base #2, Base #5, or Base #6 options that go with Top #1 or Top #2; similarly, the Top3 pattern matches one of the theForm.base.selectedIndex indexes for the Base #2, Base #3, Base #4, Base #6, or Base #7 options that go with Top #3 - pretty straightforward, eh?

Regular expression (regexp) pattern notes
(1) The above regexp patterns won't be changing, so for better performance, I've created them using the regexp literal syntax (as opposed to using new RegExp( ) constructor statements) per Mozilla's recommendation.
(2) In a regexp pattern, a vertical bar (|) functions as a logical OR operator.
(3) Regarding the base selection list, the only user input we'll be checking are the integer returns of the selectedIndex property, so there's no need to begin the Top1or2 and Top3 patterns with ^ start-of-string anchors or end the patterns with $ end-of-string anchors.

Before we utilize these patterns, however, let's first deal with the didn't-choose-a-Top/Base possibilities, which can be handled by the if statement below:

if (theForm.top.selectedIndex == 0 || theForm.base.selectedIndex == 0) {
window.alert("Please make selections from both the Top menu and the Base menu.");
return false; }

All 21 Top/Base combinations can then be checked via a subsequent else block:

else {

if ((theForm.top.selectedIndex != 3 && Top1or2.test(theForm.base.selectedIndex)) ||
(theForm.top.selectedIndex == 3 && Top3.test(theForm.base.selectedIndex))) {
window.alert("Thank you for your purchase!"); return true; }

else {
window.alert("Your chosen Base cannot be paired with your chosen Top.");
theForm.base.focus( );
return false; } }

And as Vince would say, That's the entire validation! The else block's nested if statement packs a lot of action into three lines of code; in plain English, here's what it says:

(1) (a) if ((theForm.top.selectedIndex != 3
IF the index of the user's choice from the top selection list is not equal to 3, i.e., if the user selects Top #1 or Top #2...
(b) && Top1or2.test(theForm.base.selectedIndex)) ||
...AND the index of the user's choice from the base selection list matches one of the digits in the Top1or2 regexp pattern, i.e., if the user selects Base #1, Base #2, Base #5, or Base #6, OR...
(2) (a) (theForm.top.selectedIndex == 3
...the index of the user's choice from the top selection list is equal to 3, i.e., if the user selects Top #3...
(b) && Top3.test(theForm.base.selectedIndex)))
...AND the index of the user's choice from the base selection list matches one of the digits in the Top3 regexp pattern, i.e., if the user selects Base #2, Base #3, Base #4, Base #6, or Base #7, then...
(3) (a) { window.alert("Thank you for your purchase!");
...pop up a "Thank you for your purchase!" message on an alert( ) box...
(b) return true; }
...and return a Boolean true to the onsubmit checkTopAndBase( ) function call, i.e., go ahead and submit the form to its processing agent.

If you are unfamiliar with the test( ) method of the core JavaScript RegExp object, Mozilla briefly discusses it here.

I prefer the regular expressions approach to validating the "Case for a Case" scenario, although there's definitely something to be said for the intuitively readable switch statement approach, which lays out the allowed Top/Base combinations in a nice, orderly fashion.

To anyone who wants to 'go beyond the basics' of Web coding, I highly recommend a study of regular expressions, which I have found pays major dividends in grappling with the minutiae of the coding world.

Your assignment

You produce a for-pay Web newsletter that analyzes stock market trends. When subscribers sign up for your newsletter, you ask for their birthdays so that you can send them birthday greetings.
(a) Code a suitable form that contains, among other controls, a January-December birthMonth selection list and a 1-31 birthDate selection list.
(b) Write a switch statement-based validation function that will prevent your subscribers from inputting 'erroneous' birthdays, e.g., February 30, April 31, etc.



Moving up the Beyond HTML : JavaScript page brings us to the obviously outdated "JavaScript Y2k Fix" tutorial, which we will not cover in a separate entry. For generating four-digit years, I trust y'all know that the getYear( ) method of the JavaScript Date object has been deprecated and that you're supposed to use the getFullYear( ) method instead. However, "JavaScript Y2k Fix" offers a getYear( )-based script that creates post-1999 four-digit years for users whose browsers don't support the getFullYear( ) method, and if that's your situation, then your system is even more ancient than mine is, and (like me) you really do need to upgrade.

Next tutorial, please...that would be "Minimize/Maximize a Browser Window", whose Netscape-specific script contains functions that (sort of) mimic the Minimize and Maximize Buttons of the Windows window interface and which we'll look over in the following entry. I'll see what I can do to get the "Minimize/Maximize a Browser Window" script to work with MSIE - if I'm successful, I'll tell you about it, and if I'm not successful, then I'll tell you about that, too.

reptile7

Saturday, June 14, 2008
 
The Case of the Incompatible Options
Blog Entry #116

The subject of today's post is Vince Barnes' "A Case for A Case" tutorial in HTML Goodies' Beyond HTML : JavaScript sector. At its outset, "A Case for A Case" hints that it contains a general discussion of form validation when it says, Simplify that Mountain of JavaScript Validation Code, but it actually addresses a specific validation issue, one that we heretofore have not covered: for a form comprising two (or more) controls whose values are dependent on each other and in some cases are incompatible with each other, how do you ensure that the user inputs a legitimate combination of control values?

In illustration, Vince lays out the following abstract scenario:
You are a businessperson selling a product consisting of a "Top" module and a "Base" module. You have for sale three Top types and seven Base types; however, for whatever reason, Top #1 and Top #2 can only be paired with Base #1, Base #2, Base #5, or Base #6, whereas Top #3 can only be paired with Base #2, Base #3, Base #4, Base #6, or Base #7. How do you ensure that a customer chooses a correct Top and Base combination? At a simpler level, you would also like to prevent a customer from only ordering a Top and not ordering a Base, and vice versa.
(Not exactly the most flexible of business models, is it?)

Accordingly, your programmer employee codes a form composed of
(a-b) two selection lists that respectively hold the various Top and Base options and are named top and base, and
(c) a submit button.
Attempted form submission triggers a validation function that checks the customer's Top and Base choices. You could alternatively use separate sets of radio buttons to present the Top and Base options, but the selectedIndex property makes it easier to determine user input for a selection list than for a set of radio buttons, whose checked member must be determined via a loop.

There's no HTML at all in the tutorial, so let's start filling in some blanks. The form element container will look something like:

<form action="myFormProcessingAgentURI" method="post"
onsubmit="return checkTopAndBase(this);"> ... </form>

We'll call the validation function checkTopAndBase( ) and pass thereto a reference to the form (this), to which Vince gives the identifier theForm:

function checkTopAndBase(theForm) { ...validation code... }

For incorrect Top/Base combinations, checkTopAndBase( ) will return false. The submit event is cancelable; the syntax for its cancelation is helpfully provided by HTML Goodies' "Checkboxes: Only Two" tutorial.

We'll code the top selection list as:

<select name="top">
<option>Choose a Top module:</option>
<option>Top #1</option>
<option>Top #2</option>
<option>Top #3</option>
</select>

The base selection list can be coded analogously.
Menu coding notes:
(1) In XHTML, the name attribute of the select element is still legit even as the name attribute of the form element has been deprecated.
(2) As I'm sure you know, the </option> tags are optional in HTML but are needed for XHTML-compliance.

As for the plenty of text on our form to explain all these [option] requirements to our users, well, I trust you can write that. Finally, here's our submit button:

<input type="submit" value="Submit Your Top/Base Choices" />

We're now ready to take on the checkTopAndBase( ) validation code. Vince first notes that a series of if statements (3 × 7 = 21 in all, not counting didn't-choose-a-Top/Base possibilities) could be used to test every possible Top/Base combination. To tighten up and clarify the code, he reaches for a different type of conditional statement, namely, the switch statement. Wikipedia provides a good definition/summary of the switch statement: Its purpose is to allow the value of a variable or expression to control the flow of program execution. The switch statement is not specific to JavaScript but appears in a variety of programming languages, as both Vince and Wikipedia point out.

Courtesy of Mozilla, the JavaScript switch syntax can be generalized as:

switch (expression) {
     case label_1: statements_1 [break;]
     case label_2: statements_2 [break;]
     ...
     default: statements_def [break;] }

Briefly, the value of the expression is compared to the values of the labels (label_1, label_2, ...) for a series of case clauses. If a match is found, then the statements associated with the matching case - including, if present, a terminating break statement - are executed; if no match is found, then the statements of the default clause, if present, are executed.

Vince uses nested switch statements to carry out his validation; his switch code is reproduced in the div below:

function checkTopAndBase(theForm) {
switch(theForm.top.selectedIndex)
 { case 1:
      switch(theForm.base.selectedIndex)
       {    case 1:  break;
            case 3:  break;
            case 4:  break;
            case 6:  break;
            default:
             { alert("Sorry, wrong top and base combination.");
               theForm.top.focus( );
               return (false);
             }
       }
  case 2:
      switch(theForm.base.selectedIndex)
       {    case 1:  break;
            case 3:  break;
            case 4:  break;
            case 6:  break;                 
            default:
             { alert("Sorry, wrong top and base combination.");
               theForm.top.focus( );
               return (false);
             }
       }
  case 3:
      switch(theForm.base.selectedIndex)
       {    case 2:  break;
            case 3:  break;
            case 4:  break;
            case 6:  break;                              
            case 7:  break;
            default:
             { alert("Sorry, wrong top and base combination.");
               theForm.top.focus( );
               return (false);
             }
       }
 }
}

Script display note: Checking the "Case for A Case" source shows that the script's lines are indented with &nbsp; entity references, which represent "non-breaking" spaces, per HTML Goodies' "So You Want Indents and Lists, Huh?" tutorial; with much less effort, however, the script can alternatively be formatted with 'normal' spaces (via the space bar) if it is wrapped in a pre element.

Another point: Vince's text and pseudocode pairs Tops #1 and #2 with Bases #1, #2, #5, and #6, but his script pairs Tops #1 and #2 with Bases #1, #3, #4, and #6; my deconstruction below will be based on his script.

Deconstruction

The outer switch statement compares the index of the user's chosen Top module, theForm.top.selectedIndex, to the 1, 2, and/or 3 labels of three cases with which the inner switch statements are associated (but not respectively associated, as we'll see in a moment).

The inner switch statements all have default clauses but the outer switch statement does not have a default clause. If the user does not select a Top and clicks the Submit Your Top/Base Choices button, then theForm.top.selectedIndex (0) will not match any of the outer switch case labels and none of the inner switch statements will be triggered; in this case, the primitive value undefined will be returned to the onsubmit function call but, at least on my computer, is not converted to false (as would occur in a 'logical' context) and the browser will submit the form, i.e., the validation gives a 'false-positive'. Adding the following default clause to the outer switch statement will prevent this from happening:

switch(theForm.top.selectedIndex) {
default: window.alert("Please choose a Top module.");
theForm.top.focus( );
return false;
case 1: // etc.

• A default clause is typically placed at the end of a switch statement "by convention", but you can also place it before (as shown above) or between the case clauses if you prefer.
• Contra the default syntax in Vince's script, it is not necessary to delimit the default commands with braces. Moreover, the returned false values do not need to be surrounded by parentheses.

The case clauses of the inner switch statements all have terminating break statements but the case clauses of the outer switch statement do not have terminating break statements, giving rise to "fallthrough" (to use Wikipedia's term) if the user combines Top #1 or Top #2 with Bases #1, #3, #4, or #6. For example, here's what happens when Top #1 is paired with Base #1:

switch(theForm.top.selectedIndex) {
case 1: switch(theForm.base.selectedIndex) {
case 1: break; ... }

For the Top #1 and Base #1 selections, theForm.top.selectedIndex and theForm.base.selectedIndex are both 1. theForm.top.selectedIndex matches the 1 label of the first case clause of the outer switch statement, triggering the first inner switch statement. In turn, theForm.base.selectedIndex matches the 1 label of the first case clause of the first inner switch statement. The subsequent break statement terminates only the first inner switch statement; it does not terminate the outer switch statement.

Because the first case clause of the outer switch statement does not conclude with a break statement, fallthrough occurs: the browser moves to the outer switch statement's second case clause and executes its associated inner switch statement, notwithstanding the non-match between the case label (2) and theForm.top.selectedIndex:

case 2: switch(theForm.base.selectedIndex) {
case 1: break; ... }

theForm.base.selectedIndex matches the 1 label of the first case clause of the second inner switch statement; the break statement then terminates the second inner switch statement. The second case clause of the outer switch statement doesn't conclude with a break statement either, so fallthrough occurs again and the browser moves to the outer switch statement's third case clause and its associated inner switch statement:

case 3: switch(theForm.base.selectedIndex) {
case 2: break; case 3: break; case 4: break; case 6: break; case 7: break;
default: alert("Sorry, wrong top and base combination."); theForm.top.focus( ); return false; }

theForm.base.selectedIndex doesn't match any of the case labels in the third inner switch statement, and the default clause fires: an alert( ) box with a Sorry, wrong top and base combination. message pops up, focus is sent to the top selection list, and form submission is canceled. Ouch! The also legitimate Top #2/Base #1 pairing gives an identical result (via one fallthrough and not two).

Fallthrough also occurs with the Top #1/Base #3, Top #1/Base #4, Top #1/Base #6, Top #2/Base #3, Top #2/Base #4, and Top #2/Base #6 pairings, but these cases do not give 'false-negatives' because theForm.base.selectedIndex will match one of the case labels in the third inner switch statement. Fallthrough does not occur with the Top #1/Base #2, Top #1/Base #5, Top #1/Base #7, Top #2/Base #2, Top #2/Base #5, and Top #2/Base #7 pairings, because the return false statement in the default clause of the first or second inner switch statement stops the propagation of the submit event in these cases.

Anyway, this fallthrough business is easily choked off by appending break statements to the case clauses of the outer switch statement, e.g.:

switch(theForm.top.selectedIndex) {
case 1: switch(theForm.base.selectedIndex) {
case 1: break; case 3: break; case 4: break; case 6: break;
default: alert("Sorry, wrong top and base combination."); theForm.top.focus( ); return false; }
break;
case 2: // etc.

Finally, what if the user selects Top #1, #2, or #3, but doesn't make a Base selection? If theForm.base.selectedIndex is 0, then it won't match any of the inner switch case labels and form submission will be canceled by one of the inner switch default clauses; admittedly, the Sorry, wrong top and base combination. default error message leaves room for improvement with respect to these cases.

The tutorial doesn't offer a demo, so I'll give you one in the following entry; also in our next episode: we'll discuss a regular expressions approach to coding the "Case for A Case" scenario.

reptile7

Wednesday, June 04, 2008
 
Second Intermission
Blog Entry #115

Man, I'm not quite sure what's going on over at HTML Goodies these days. I don't believe Joe Burns has a connection with the site anymore (besides an historical connection, of course). Joe's bio in the About HTML Goodies sector is gone and he last wrote a Goodies to Go newsletter in August 2002 (Newsletter #195 - read it here); for that matter, the Goodies to Go newsletter series fizzled out entirely in June 2006. Moreover, most (all?) of the recent 'new' articles at HTML Goodies have been recycled from WebReference.com newsletters. At least the discussion forum at the bottom of the page is still active.

In any case, we've now burned through HTML Goodies' JavaScript Primers (more specifically, the original primers written by Andree Growney and Joe) and Script Tips per the game plan of my very first blog entry. What's next to go under the microscope?
For the short term:
(1) We will check over some (not all) of the tutorials in HTML Goodies' Beyond HTML : JavaScript sector, with an emphasis on those tutorials that present new concepts and/or allow for cool demos.
Further into the future:
(2) We owe it to ourselves to go through some of the interesting example scripts in Part 2 of Netscape's "Dynamic HTML in Netscape Communicator" resource.
(3) Lee Underwood of WebReference.com recommends The JavaScript Source, ScriptSearch, and 'JavaScripts.com' as sources of JavaScript scripts to analyze.
(4) Most ambitiously, perhaps it's time I start learning a new programming language; in particular, I'm thinking about tucking into C.

So with no further ado, the next post will kick off our tour of the Beyond HTML : JavaScript sector. We'll begin at the bottom of the page with a look at Vince Barnes' "A Case for A Case" article, which will introduce us to the JavaScript switch statement. (Vince wrote the very last Goodies to Go newsletter - I'm not sure he's still at HTML Goodies either.)

reptile7


Powered by Blogger

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