reptile7's JavaScript blog
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

Comments: Post a Comment

<< Home

Powered by Blogger

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