reptile7's JavaScript blog
Tuesday, August 29, 2006
 
More on Regular Expressions
Blog Entry #49

In the previous entry, we developed several regular expressions for vetting a user's response (or nonresponse) to the "Enter First Name" field of the Primer #29 Script. We turn now to the script's "Enter Zip Code (99999-9999)" field:

<form name="dataentry">
Enter Zip Code (99999-9999):<br>
<input type="text" name="zip" size="10"><p>
<input type="button" value="Submit" onclick="validZip(zip.value);"></form>

You may recall that the original validZip( ) function tested:
(a) if the user had entered 5 or 10 characters into the zip field; and
(b) if the user's first five inputted characters were numeric digits.
If we wanted to, we could again address these issues separately with two different regular expressions; however, it's more efficient to use a single regular expression that will match either a 99999 ZIP code or a 99999-9999 ZIP+4 code:

function validZip(zp) {
var zc = /^\d{5}$|^\d{5}-\d{4}$/;
if (zp.search(zc) == -1) {
window.alert("Please enter a proper ZIP code.");
document.dataentry.zip.focus( ); } }

Let's look at the regular expression zc, which is really two regexp patterns rolled into one.
• \d matches any numeric digit and is equivalent to [0-9] (a hyphen can be used to span a number range as well as a letter range).
• {5} is a quantifier that matches \d exactly 5 times; ^\d{5}$ thus matches a string comprising 5 digits and 5 digits only. One might expect the opening brace { and the closing brace } to be metacharacters; somewhat curiously, they are not. The ^ and $ metacharacters were covered in the previous post.
• The vertical bar (|) is a metacharacter that serves as a logical OR statement (recall JavaScript's || logical OR operator, which briefly cropped up in Blog Entry #37); zc will match either the subpattern preceding the | or the subpattern following the |.
• ^\d{5}-\d{4}$ matches a string comprising 5 digits followed by a hyphen followed by another 4 digits.

In sum, the ^\d{5}$ subpattern will match a 99999 ZIP code and the ^\d{5}-\d{4}$ subpattern will match a 99999-9999 ZIP+4 code, and the | metacharacter between the subpatterns allows zc to match either ZIP code format.

We then search( ) zp, the value (user input) of the "Enter Zip Code (99999-9999)" field, for an occurrence of a zc subpattern, and compare the return with -1 in the condition of the subsequent if statement:

if (zp.search(zc) == -1)

If in any way the user's input does not conform with either of zc's subpatterns, then the if condition returns true; a "Please enter a proper ZIP code" alert( ) message pops up and focus is returned to the zip field.

It should be clear from the above that the following code can be used to separately address the 'first five characters must be numeric' issue:

var firstfive = /^\d{5}/;
if (zp.search(firstfive) == -1) {
window.alert("Your first five characters must be numeric.");
document.dataentry.zip.focus( ); }

Can you write a regexp pattern that tests if a user's zip input consists of 5 or 10 not-necessarily-digit characters?

The test( ) method

It's high time that we broke out of our search( ) method rut, wouldn't you say? An equally user-friendly method for comparing a regexp pattern with a target string is the test( ) method of the RegExp object:

regexp_name.test("some_string");
// returns true if regexp_name matches "some_string" and false if it doesn't

Here's code we can use to test( ) zc vs. zp:

if (!zc.test(zp)) {
window.alert("Please enter a proper ZIP code.");
document.dataentry.zip.focus( ); }

We discussed the ! logical NOT operator in Blog Entry #47. If neither of zc's subpatterns matches zp, then zc.test(zp) returns false and thus the !zc.test(zp) condition of the if statement returns true; as before, a "Please enter a proper ZIP code" alert( ) message pops up and focus is returned to the zip field.

As long as we're talking about regular expressions, let's look at the two HTML Goodies articles in which they briefly crop up.

Social Security number validation

The following code for validating a Social Security number (SSN) appears in HTML Goodies' "Validating Special Numbers" article:

<script language="JavaScript1.2"><!--
function regular(string) {
if (string.search(/^[0-9][0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]$/) != -1)
return true;
else
return false; }
//-->
</script>
The user enters an SSN into the following field:
<input type="text" size="12" maxlength="12"
onchange="if (!regular(this.value)) alert('Not Valid');">

(The <script> tag contains an historical note of sorts; the first version of JavaScript with support for regular expressions was in fact JavaScript 1.2.)

The approach here is more convoluted than it needs to be. The call for the validating regular( ) function is set in the condition of an if statement assigned to an onChange event handler, which 'fires' after the user enters an SSN and then blurs the input field. Note that the regular( ) function call is preceded by the ! operator; consequently, if regular( ) returns false to the if statement, whose condition would then return true, then a 'Not Valid' alert( ) message pops up. (No commands execute if regular( ) returns true.)

When regular( ) is triggered, the user's input (this.value) is assigned to the identifier string (string is not a JavaScript reserved word, in case you were wondering); an if statement then uses the search( ) method to compare string with the following regexp pattern:

/^[0-9][0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]$/

Equivalently and more simply, we can rewrite the pattern as:

var ssn = /^\d{3}-\d{2}-\d{4}$/;

Unless it is spanning a letter or number range inside of square brackets, a hyphen is not a regexp metacharacter and does not need to be escaped with a backslash.

If the user's input conforms to the 999-99-9999 SSN format, then string.search(ssn) will return 0, which is not equal (!=) to -1; the if condition returns true and regular( ) itself returns true. If the user's input deviates from the 999-99-9999 format, then string.search(ssn) returns -1, the if condition returns false, regular( ) returns false via its else statement, and the 'Not Valid' alert( ) pops up.

Here's what I would have done:

<script type="text/javascript">
function regular(str) {
var ssn = /^\d{3}-\d{2}-\d{4}$/;
if (str.search(ssn) != -1) window.alert("Thank you.");
else window.alert("Your SSN is not valid."); }
</script>
<input onchange="regular(this.value);">

Email address validation

This topic deserves its own entry and we'll take it up in the next post.

reptile7

Saturday, August 19, 2006
 
A Tourist's Guide to Regular Expressions
Blog Entry #48

In today's post, we venture into the wild and woolly world of regular expressions - a relatively advanced topic that would seem to separate a real programmer from an amateur such as myself - but there's nothing stopping us from giving it the go, is there now? A full-fledged presentation on regular expressions is definitely beyond the scope of this blog; instead, to keep things at a fairly basic level, we will apply some simple regular expressions to user inputs into the text fields of the Primer #29 Script, which we discussed in detail in Blog Entry #46, and I'll explain as best I can what I'm doing as we go along.

References
Various regular expressions resources can be found on the Web; here are my 'picks':
(1) If you're new to regular expressions, then a good starting point is JavaScript Kit's "Introductory Guide to Regular Expressions" tutorial.
(2) A comprehensive and more general (not limited to JavaScript) treatment of regular expressions appears at http://www.regular-expressions.info/.
Also deserving of mention:
(3) WebReference.com (which, like HTML Goodies, is part of the JupiterWeb 'empire') offers an 11-part "Pattern Matching and Regular Expressions" tutorial.
(4) Netscape's overview of regular expressions is here; its discussion of the core RegExp object is here. And to be up-to-date about all of this, you may want to check out Appendix B of Netscape's JavaScript 1.5 Core Reference, whose list of deprecated features pertains mostly to the RegExp object.

At HTML Goodies itself, the regular expressions topic briefly crops up in two articles - "Validating Special Numbers" and the "JavaScript Basics Part 3" primer - and that's about it, I'm sorry to say. There is no information on the RegExp object, its properties, or its methods on any of HTML Goodies' JavaScript References pages.

General remarks/syntax

So, what is a regular expression and what is it good for? To use a simplified analogy, use of a regular expression is a bit like doing a word search puzzle. We're going to search a string (as opposed to a grid of seemingly random letters) for one or more occurrences of a character pattern, which, if found, will appear horizontally left-to-right (not vertically, diagonally, nor backwards) in the string. The character pattern, termed a regular expression (or regexp for short), can be wildly complex or as simple as a single letter; yes, as in a word search, the pattern can also be an ordinary word.

There are two syntaxes for creating a regular expression:

(1) A 'literal' syntax that delimits the pattern with forward slashes:

var regexp_name = /pattern/flags;

Optionally following the pattern are one or more modifiers termed flags; in JavaScript, there are three regular expression flags - i, g, and m - most important are the i flag, which renders our pattern search case-insensitive, and the g flag, which allows us to globally match all occurrences (not just the first occurrence) of the pattern in the string.

(2) We can also use a new RegExp( ) constructor statement:

var regexp_name = new RegExp("pattern", "flags");

Note that both RegExp( ) arguments appear in quotes. (I was going to say at this point, "After all, regular expressions are themselves strings"; however, typeof regexp_name returns object.)

With respect to these two syntaxes, JavaScript Kit's "Programmer's Guide to Regular Expressions" tutorial says, "In almost all cases you can use either way to define a regular expression, and they will be handled in exactly the same way no matter how you declare them." I for my part will use the literal regular expression syntax in this post.

And how do we put these things into practice? As noted on the "String and Regular Expression Methods" page of the first JavaScript Kit tutorial linked above, there are four String object methods and two RegExp object methods for comparing a regular expression with a target string. Of these six methods, I will in our discussion of the Primer #29 Script use most often the search( ) method of the String object, which works much the same way as does the indexOf( ) method of the String object (discussed in Blog Entry #46) except that search( ) takes a regular expression argument (indexOf( ) takes a string argument):

string_object.search(regexp_pattern);
/* "If successful, search( ) returns the index of the regular expression inside the string. Otherwise, it returns -1," quoting Netscape. */

Let's turn now to the input fields of the Primer #29 Script...

Your first name, please

<form name="dataentry">
Enter First Name:<br>
<input type="text" name="fn" onblur="validfn(this.value);">

We're ready to code a new-and-improved, regular-expression-based validfn( ) function. What do we put in it?

Don't leave it blank, part 3

If all you want to do is to ensure that a user doesn't leave the "Enter First Name" field blank - and as far as regular expressions go, this is setting the bar rather low, as we'll see in a bit - then this can be easily done with the following function:

function validfn(fnm) {
var notblank = /[\s\S]/;
if (fnm.search(notblank) == -1) {
window.alert("First name is required."); document.dataentry.fn.focus( ); } }

Let's look at the regular expression that is assigned to the identifier notblank.
(You may want to follow along at JavaScript Kit's "Categories of Pattern Matching Characters" page.)
• \s matches any whitespace character (a space, a tab, a line break, etc.).
• \S matches any non-whitespace character.
Without the backslashes, s and S match themselves.
• The square brackets apply a logical OR to the matching process; [\s\S] matches either a single whitespace character or a single non-whitespace character. Without the square brackets, \s\S would match a whitespace character followed by a non-whitespace character.

In sum, the /[\s\S]/ pattern matches any single character, because all characters are either whitespace or non-whitespace, as the "Dot" page of http://www.regular-expressions.info/ points out.

We then search( ) fnm, the value (user input) of the "Enter First Name" field, for an occurrence of notblank (any character), and compare the return with -1 in the condition of the subsequent if statement:

if (fnm.search(notblank) == -1)

If the user has left the field blank, i.e., if no characters are present, then the if condition returns true; a "First name is required" alert( ) message pops up and focus is returned to the fn field.

A regexp pattern for 'normal' names

I recognize that there is as much variation to first names as there is to language itself. First names can be multi-part ("Mary Ann"), be hyphenated ("Jean-Paul"), contain accents, umlauts, apostrophes, etc.; some of these situations are easily accommodated by a regular expression, others less so. We can begin by using a regular expression to keep nonletter characters out of the user's fn input:

function validfn(fnm) {
var normalname = /^[a-z]+$/i;
if (fnm.search(normalname) == -1) {
window.alert("Please enter a proper name."); document.dataentry.fn.focus( ); } }

Let's look at the normalname pattern.
• Note the i flag after the second forward slash; the subsequent search( ) will be case-insensitive.
• The normalname pattern begins with a caret (^), which does not match itself but is a metacharacter that matches, to be precise, the dividing line between the first character of a string and the 'void' to the left of the string, according to the "Anchors" page of http://www.regular-expressions.info/. In other words, what follows the ^ must appear at the very beginning of a string. (If normalname also had an m (multiline) flag, then the ^ would also match the dividing line between a newline (\n) character and the following line.)
• [a-z] matches a single character in the 26-letter a-to-z character set; use of a hyphen between a and z allows us to specify a-to-z as a range of characters so we don't have to type out all 26 letters; in contrast, [az-] would match a single a, a single z, or a single -. Without the i flag, [a-z] would match a single lowercase a-to-z character.
• The plus sign (+) does not match itself but is another metacharacter that serves as a quantifier; + matches the preceding character or entity one or more times. [a-z]+ thus matches a string of ≥1 a-to-z characters.
• Finally, the $ does not match itself but is another metacharacter that matches the dividing line between the end of a string and the 'void' to the right of the string; in other words, what precedes the $ must appear at the very end of a string. (If normalname had an m flag, then the $ would also match the dividing line between a \n and the preceding line.)

As before, we then search( ) fnm for an occurrence of normalname and compare the return with -1 in the condition of the subsequent if statement. If the user has entered any nonletter characters (e.g., numbers, nonalphanumeric characters, whitespace), then the if condition returns true; a "Please enter a proper name" alert( ) message pops up and focus is returned to the fn field.

Note that without the ^ and $ metacharacters, a user input of #Joe% would match the normalname pattern.

Perhaps you are a stickler for capitalization; the following code will let sloppy users know, in no uncertain terms, that inputs beginning with lowercase letters are simply out of order:

var firstcap = /^[A-Z]/;
if (fnm.search(firstcap) == -1) {
window.alert("The first letter of your name should be capitalized."); document.dataentry.fn.focus( ); }

Admittedly, the patterns above won't stop the user from entering Lkjhgf into the fn field; however, we can make sure that the user's input contains at least one vowel with the following code:

var vowel = /[aeiouy]/i;
if (fnm.search(vowel) == -1) {
window.alert("There are no vowels in your name! Please enter a proper name.");
document.dataentry.fn.focus( ); }

A regexp pattern for not-so-normal names

The following pattern can handle a name with a space, a hyphen, or an apostrophe; it can also accommodate first-initial-middle-name-last-name people ("J. Paul Getty") who might want to enter just a first initial and a period:

var notsonormal = /^[a-z]*[\s\.'-]?[a-z]*$/i;

• The asterisk (*) does not match itself but is a metacharacter that serves as a quantifier; * matches the preceding character or entity zero or more times - this will allow the notsonormal pattern to match 'Aisha as well as Ze'ev.
• In a regular expression, a period ("dot") is ordinarily a metacharacter that matches any single character except a newline character; a preceding backslash "escapes" a period back to its literal identity, i.e., an actual period.
• The ? does not match itself but is a metacharacter that serves as a quantifier; ? matches the preceding character or entity zero or one time(s).

(Update: according to the "Use The Dot Sparingly" section of http://www.regular-expressions.info/dot.html, a period inside of square brackets is not a metacharacter and does not in this case need to be escaped with a backslash. I find on my computer that notsonormal will match A. but not A# with or without the backslash.)

The notsonormal pattern will not match Abd-Al-Rahman; can you write a pattern that does?

And what if a Günther, a Søren, or a Thérèse visits your site? We certainly don't want to leave anyone out if at all possible...names like these can be matched by putting the Unicode code points for their special characters in your regexp pattern, e.g.:

var specialchar = /^[a-z]+[\u00fc\u00f8\u00e9][a-z]+[\u00e8]?[a-z]*$/i;

• \u00fc encodes ü
• \u00f8 encodes ø
• \u00e9 encodes é
• \u00e8 encodes è

I think that's enough for this entry - we'll look at a regular expression pattern for a zip code in the next post.

^reptile7$

Wednesday, August 09, 2006
 
Data Validation II
Blog Entry #47

We continue today our introductory treatment of form data validation. In the last entry, we carried out some simple validations of user inputs into text fields. Well, what about other types of form controls, huh?

Don't leave it blank, part 2

We turn now to radio buttons and checkboxes. How do you ensure that a user chooses a radio button or at least one checkbox from a set thereof? You could take the easy way out by preselecting for the user a particular radio button or checkbox via a checked minimized attribute in its <input> tag, e.g.:

<input type="radio" name="radio_button_name" value="button_value" checked>

But perhaps you would rather not do that - what then? You may recall that in Blog Entry #17 we used a for loop to determine a user's radio button or checkbox input, and we can use a for loop again to run through a set of radio buttons or checkboxes to make sure it hasn't been left blank. In both cases, use of a loop is not strictly necessary but substantially lowers the number of lines of code that are required. Consider the demo question below - first, click the Submit button without making a choice, and then choose one of the radio buttons and reclick the Submit button:

Which element below is a member of the halogen family?

Arsenic
Gallium
Iodine
Selenium
Xenon


Here's the code:
<head><script type="text/javascript">
function radio_test( ) {
var unchecked=0;
for (i=0; i<document.fradio.hal.length; i++) {
if (!document.fradio.hal[i].checked) unchecked++;
if (unchecked==5) window.alert("Please make a selection."); }
if (unchecked==4) {
if (document.fradio.hal[2].checked) window.alert("Iodine it is!");
else window.alert("Nope, try again."); } }
</script></head><body>
<form name="fradio">
Which element below is a member of the halogen family?<p>
<input type="radio" name="hal"> Arsenic<br>
<input type="radio" name="hal"> Gallium<br>
<input type="radio" name="hal"> Iodine<br>
<input type="radio" name="hal"> Selenium<br>
<input type="radio" name="hal"> Xenon<p>
<input type="button" value="Submit" onclick="radio_test( );"> <input type="Reset">
</form></body>

Let's briefly look at the radio_test( ) function that is triggered by the Submit button.

The key to the radio_test( ) function is the variable unchecked, which counts the number of unchecked radio buttons. After unchecked is initialized with a value of 0, a for loop runs through the five radio buttons to see which ones are unchecked:

for (i=0; i<document.fradio.hal.length; i++) {
if (!document.fradio.hal[i].checked) unchecked++;
if (unchecked==5) window.alert("Please make a selection."); }

The first if statement above introduces us to the ! logical/Boolean NOT operator, which "[r]eturns false if its single operand can be converted to true; otherwise, returns true," quoting Netscape. For an unchecked radio button, the document.fradio.hal[i].checked expression evaluates to false; the !document.fradio.hal[i].checked condition then returns true, and unchecked is incremented.

If all five radio buttons are unchecked, then the value of unchecked peaks out at 5 in the loop's last iteration; the condition of the second if statement now returns true, and a "Please make a selection" alert( ) message pops up. If the user does check one of the radio buttons (unchecked=4), then an external-to-the-loop if...else conditional generates appropriate alert( ) messages for correct and incorrect answers to the original question.

Only two checkboxes, please

Lurking in the HTML Goodies archives is an interesting and useful "Checkboxes: Only Two" tutorial that, per its title, offers code for permitting a user to choose only two boxes in a set of checkboxes. The script therein, although satisfyingly functional, can itself be significantly condensed via a for loop, as we'll show below. Consider the demo question below - try making three choices and see what happens:

Choose the two elements below that are alkali metals:

Aluminum
Iron
Lithium
Magnesium
Rubidium


Here's my code (adapted from Joe's original code):
<head><script type="text/javascript">
function checkbox_test( ) {
var elemcount=0;
for (i=0; i<document.fcheckbox.alkmet.length; i++) {
if (document.fcheckbox.alkmet[i].checked) elemcount++;
if (elemcount==3)
{window.alert("Only two checkboxes, please."); return false; break;} } }
function answer( ) {
if (document.fcheckbox.alkmet[2].checked && document.fcheckbox.alkmet[4].checked)
window.alert("Excellent!");
else window.alert("Nope, try again."); }
</script></head><body>
<form name="fcheckbox">
Choose the two elements below that are alkali metals:<p>
<input type="checkbox" name="alkmet" onclick="return checkbox_test( );"> Aluminum<br>
<input type="checkbox" name="alkmet" onclick="return checkbox_test( );"> Iron<br>
<input type="checkbox" name="alkmet" onclick="return checkbox_test( );"> Lithium<br>
<input type="checkbox" name="alkmet" onclick="return checkbox_test( );"> Magnesium<br>
<input type="checkbox" name="alkmet" onclick="return checkbox_test( );"> Rubidium<p>
<input type="button" value="Submit" onclick="answer( );"> <input type="Reset">
</form></body>

Clicking any of the checkboxes above triggers the checkbox_test( ) function in the document head script. The key to the checkbox_test( ) function is the variable elemcount, which counts the number of checked checkboxes. After elemcount is initialized with a value of 0, a for loop runs through the five checkboxes to see which ones are checked:

for (i=0; i<document.fcheckbox.alkmet.length; i++) {
if (document.fcheckbox.alkmet[i].checked) elemcount++;
if (elemcount==3)
{window.alert("Only two checkboxes, please."); return false; break;} }

For each checked checkbox, the condition of the first if statement returns true and elemcount is incremented. If three checkboxes are checked, then the condition of the second if statement returns true; an "Only two checkboxes, please" alert( ) message pops up, the value false is returned to the calling checkbox, and the loop is terminated with a break statement. We briefly discussed the return statement and its use in a function body in Blog Entry #23.

In its "Description" of the onClick event handler, Netscape notes, "For checkboxes, links, radio buttons, reset buttons, and submit buttons, onClick can return false to cancel the action normally associated with a click event." So, suppose the user checks the Aluminum, Iron, and Lithium checkboxes, and that false is returned to the Lithium checkbox; consequently, the onclick="return checkbox_test( );" code in the third <input> tag becomes in effect onclick="return false", which cancels the third click event and promptly unchecks the checkbox.

Joe concludes, "You can set this to as many checkboxes as you'd like and as many choices from those boxes as you'd like...Just make sure to keep the return command in the checkboxes themselves." I can confirm that if the return keyword is removed from the onclick="return checkbox_test( );" attribute, then the uncheck-the-checkbox effect does not occur.

Selection lists

We can easily retool the radio_test( ) function above for a selection list to ensure that a user selects an option thereof. Consider demo question #3 below - first, click the Submit button without making a choice, and then choose one of the options and reclick the Submit button:

If you could visit another planet, which one would it be?




Here's the code:
<head><script type="text/javascript">
function select_test( ) {
var unselected=0;
for (i=0; i<document.fselect.planet.options.length; i++) {
if (!document.fselect.planet.options[i].selected) unselected++;
if (unselected==8) window.alert("Please make a selection."); } }
</script></head><body>
<form name="fselect">
If you could visit another planet, which one would it be?<p>
<select name="planet" size="5">
<option>Mercury</option>
<option>Venus</option>
<option>Mars</option>
<option>Jupiter</option>
<option>Saturn</option>
<option>Uranus</option>
<option>Neptune</option>
<option>Pluto</option>
</select><p>
<input type="button" value="Submit" onclick="select_test( );"> <input type="Reset">
</form></body>

As noted in Blog Entry #17, the selected property of the option object is analogous to the checked property of the radio and checkbox objects; the options[i] array is itself a property of the select object.

Note the size="5" in the <select> tag; without it, the selection list would only show the Mercury option, which then by default becomes the user's 'chosen selection.'

A much easier approach is to use the "If you could visit another planet..." question as the 0th and only displaying option of the selection list:

<select name="planet">
<option>If you could visit another planet, which one would it be?</option>
<option>Mercury</option> etc.

In this case, the select_test( ) function can be simplified to:

function select_test( ) {
if (document.fselect.planet.selectedIndex==0) window.alert("Please make a selection."); }

The selectedIndex property of the select object was also discussed in Blog Entry #17.

Finally, my attempts to retool the checkbox_test( ) function above for a select-multiple selection list were largely but not entirely successful. Again, suppose you want to limit the user to choosing two options from a list of this type. It's fairly simple to get an "Only two selections, please" alert( ) message to pop up if the user tries to select three options; getting that third option to 'deselect' is a much tougher nut to crack, however. Chief problems in this regard:

(1) There are no event handlers, not a one, that are usable with the option element. Sure enough, on my computer the following codings:

<option onEvent="executable_code">Option text</option> and
<span onEvent="executable_code"><option>Option text</option></span>

both fail to execute commands in response to click, mouseover, or other events.

(2) At least the onChange event handler can be used to execute code in response to a change in the 'selection state' of a selection list; however, unlike an onClick action, an onChange action is not cancelable by a return false statement.

After some experimentation, it occurred to me that I could organize the indices of the user's selected options as an array and then use an array element reference to set the selected property of a selected option to false. With respect to the "If you could visit another planet..." script above, then, I renamed the form element and recoded the select element as follows:

<form name="fselect2">
If you could visit two other planets, which ones would they be?<p>
<select name="planet2" size="5" multiple onchange="select_test2( );">

The onChange attribute in the <select> tag triggers the following function:
function select_test2( ) {
var planetcount=0;
var optionIndex = new Array( );
var j=0;
for (i=0; i<document.fselect2.planet2.options.length; i++) {
if (document.fselect2.planet2.options[i].selected) {
optionIndex[j] = i;
j++;
planetcount++; }
if (planetcount==3) {
window.alert("No more than two planets, please.");
document.fselect2.planet2.options[optionIndex[2]].selected=false;
break; } } }

The optionIndex elements are indexed with the variable j, which increments normally (0,1,2...) for each selected option; a separate planetcount variable keeps track of the number of planets chosen by the user. If three options are selected (planetcount=3), then the statement:

document.fselect2.planet2.options[optionIndex[2]].selected=false;

unselects the third-in-source-order of those options*; for example, if the user selects, in order, the 0th (Mercury), 4th (Saturn), and 2nd (Mars) planet options, then the Saturn option (not the Mars option) will be unselected. The effect of the select_test2( ) function thus does not exactly parallel the effect of the checkbox_test( ) function, but it's close.
(*Alternatively, a document.fselect2.planet2.selectedIndex = -1; statement on this line of the script will unselect all three options.)

Try it all out below; for choosing consecutive or nonconsecutive options of a select-multiple selection list, consult if necessary the helpful instructions in the "Lists" section of this page hosted by the EPA (yes, the Environmental Protection Agency).

If you could visit two other planets, which ones would they be?




A refraction of the Primer #29 Script through the prism of regular expressions deserves its own entry, and we'll do that next - we may also bring password field validation into the mix.

reptile7


Powered by Blogger

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