reptile7's JavaScript blog
Friday, July 26, 2013
 
The Cards Are Alright
Blog Entry #296

As promised, in today's post we will revamp the checkCardNumber( ) and checkExpireDate( ) functions in the Scottish Gifts order.htm source; specifically, we will use regular expressions to carry out the functions' vetting operations.

checkCardNumber( )

References:
The Issuer identification number (IIN) section of Wikipedia's "Payment card number" entry features a helpful table that lists the beginning digit sequences and number of digits for a variety of credit card types.
• At his Regular-Expressions.info site, Jan Goyvaerts has posted a "Finding or Verifying Credit Card Numbers" tutorial that provides regexp patterns for validating a variety of credit card types.

As detailed in the previous post, the checkCardNumber( ) function is divided into two parts vis-à-vis the Credit Card Info table:
(1) a for loop checks if the Credit Card # field has an all-digit value; and
(2) an if...else if cascade - the Credit Card Sanity Check - checks (a) if the Card Type selection and the Credit Card # value are out of sync and (b) if the Credit Card # value contains at least 12 digits.
As discussed below, we can integrate and refine these parts via the use of regular expressions.

There are four specific credit card types whose numbers we want to validate: Visa, MasterCard (M/C), Discover, and American Express (AMEX).

N.B. The validations of the Visa, MasterCard and American Express, and Discover subsections are based on Wikipedia's mid-2013 IIN information. As of February 2017:
(v) Visa card numbers can have 13, 16, or 19 digits;
(mc) MasterCard card numbers can also begin with 2221-2720; and
(d) Discover card numbers can have 16 or 19 digits.

Visa

We noted last time that Visa card numbers always begin with a 4. The aforecited Wikipedia IIN table specifies that Visa card numbers comprise 13 or 16 digits; however, in the text following the table, Wikipedia states, All 13-digit [Visa] account numbers have since been migrated to 16-digit account numbers, which is consistent with what this page and this document (sorry, this resource is no longer available) reports, so let's run with it and assume that 13-digit Visa card numbers are a thing of the past. A ^4\d{15}$ regexp pattern matches a 16-digit string beginning with a 4; here's how we might use the pattern in the checkCardNumber( ) function:

if (document.order.card_type[0].checked) { // If the Visa: radio button is checked
    if (/^4\d{15}$/.test(document.order.card_no.value)) { window.alert("Thanks, Visa user."); return false; }
    else { window.alert("Please enter a valid VISA number."); return true; } }


The test( ) method of the RegExp object is detailed here.

MasterCard and American Express

From the IIN table:
MasterCard card numbers begin with 51-55 and contain 16 digits.
American Express card numbers begin with 34 or 37 and contain 15 digits.

MasterCard card numbers are matched by a ^5[1-5]\d{14}$ regexp pattern; American Express card numbers are matched by a ^3[47]\d{13}$ regexp pattern.

if (document.order.card_type[1].checked) { // If the M/C: radio button is checked
    if (/^5[1-5]\d{14}$/.test(document.order.card_no.value)) { window.alert("Thanks, MasterCard user."); return false; }
    else { window.alert("Please enter a valid MasterCard number."); return true; } }
if (document.order.card_type[3].checked) { // If the AMEX: radio button is checked
    if (/^3[47]\d{13}$/.test(document.order.card_no.value)) { window.alert("Thanks, American Express user."); return false; }
    else { window.alert("Please enter a valid American Express number."); return true; } }


Discover

According to the IIN table, Discover card numbers begin with 6011, 622126-622925, 644-649, or 65, and contain 16 digits; it is not so easy to craft a regexp pattern that encompasses all of these criteria but it can be done:

var regexpDiscover = /^6(?:011\d{2}|5\d{4}|4[4-9]\d{3}|22(?:12[6-9]|1[3-9]\d|[2-8]\d{2}|9[01]\d|92[0-5]))\d{10}$/;
if (document.order.card_type[2].checked) { // If the Discover: radio button is checked
    if (regexpDiscover.test(document.order.card_no.value)) { window.alert("Thanks, Discover user."); return false; }
    else { window.alert("Please enter a valid Discover number."); return true; } }


• To speed things up, boolean alternatives in the regexpDiscover pattern are grouped with (?:x) non-capturing parentheses.

• The application of regular expressions to numeric ranges is covered here at the Regular-Expressions.info site. In order to match it with a regexp pattern, the 126-925 range must be broken up into five subranges:
(1) 126-129, which is matched by 12[6-9];
(2) 130-199, which is matched by 1[3-9]\d;
(3) 200-899, which is matched by [2-8]\d{2};
(4) 900-919, which is matched by 9[01]\d; and
(5) 920-925, which is matched by 92[0-5].

This is a case for which you might want to use String object methods to handle the various starting-digits requirements; more code is required but it's way more readable:

if (/^6\d{15}$/.test(document.order.card_no.value) &&
(document.order.card_no.value.substring(1, 4) == "011" ||
"22126" <= document.order.card_no.value.substring(1, 6) && document.order.card_no.value.substring(1, 6) <= "22925" ||
"44" <= document.order.card_no.value.substring(1, 3) && document.order.card_no.value.substring(1, 3) <= "49" ||
document.order.card_no.value.charAt(1) == "5")) { ... }


Somewhat complicating the picture, the text below the IIN table notes:
(1) Effective October 16, 2009, Diners Club [card numbers] beginning with 30, 36, 38, or 39 have been processed by Discover Card; these card numbers should contain 14 digits.
(2) China UnionPay cards are now treated as Discover cards and accepted on the Discover network. China UnionPay card numbers begin with 62 or 88 and contain 16-19 digits.
It is left to the reader to incorporate this information into the preceding code.

Removing spaces and dashes

The aforecited "Finding or Verifying Credit Card Numbers" tutorial puts forward a [ -]+ regexp pattern for flagging spaces and dashes in a user's credit card number entry.

var spaces_and_dashes = /[ -]+/g;
document.order.card_no.value = document.order.card_no.value.replace(spaces_and_dashes, "");


The replace( ) method of the String object is detailed here.

If we want to make use of this code, then we should increase the Credit Card # field's maxlength value to 19 (or maybe throw out the maxlength attribute altogether) - a 4444 4444 4444 4444 Visa card number entry would comprise 19 characters, for example - and remove the (no spaces allowed) text that lies below the field.

checkExpireDate( )

The original checkExpireDate( ) function OKs the submission of the order form if the user has entered something, anything - Hello, World!, 3.14159, whatever - into the Expiration Date field of the Credit Card Info table, and we don't want that, needless to say. In rectifying the situation:
(1) We need to let the user know what we expect with respect to the Expiration Date entry format. Entries deviating from that format must be intercepted.
(2) We also need to flag cards whose expiration dates are in the past - this is, after all, the whole point of an expiration date check.

Credit card expiration dates generally have a mm/yy format, e.g., 07/13 for July 2013, so our first order of business is to replace the MasterCard-Visa logo visamast.gif image below the Expiration Date field with a (mm/yy, e.g., 07/13) string.

We can match the 01-12 mm range with a (?:0[1-9]|1[0-2]) regexp pattern; a (?:1[3-9]|[2-9]\d) pattern will match yy years running from 2013 to 2099. Consequently, the following if statement catches pre-January 2013 expiration dates as well as Expiration Date values that do not conform to the mm/yy format:

if (!/^(?:0[1-9]|1[0-2])\/(?:1[3-9]|[2-9]\d)$/.test(document.order.expire_date.value))
    window.alert("Either your card is too old or your expiration date is not valid.");


Note that the mm/yy slash delimiter is a regexp metacharacter and must be literalized with a backslash.

Credit card expiration dates go into effect at the end of the mm month, e.g., a 07/13 card is good through 31 July 2013, so, this being July 2013, we need to
(1) approve cards with 07/13-and-later expiration dates and
(2) weed out cards with 01/13-06/13 expiration dates.
Towards this end, we will convert the user's mm/yy entry into an expDate Date object and then compare the expDate with a now Date object representing the present time:

var now = new Date( );
...expDate Date object creation code...
if (now < expDate) window.alert("You're good to go.");
else window.alert("Your card has expired.");


Because the JavaScript date is measured in milliseconds since midnight 01 January, 1970 UTC, a simple < comparison is all that is necessary to determine whether the expDate is later than now.

To create our expDate, we first extract the mm and yy parts of the user's mm/yy entry.

var mm = document.order.expire_date.value.substring(0, 2);
var yy = document.order.expire_date.value.substring(3, 5);


Several syntaxes for creating Date objects are available, and a

new Date(year, month, day [hour, minute, second, millisecond]);

constructor is the best choice for the expDate; however, we'll need to adjust the mm and yy extracts a bit before we can plug them into the constructor:

if (mm == "12") { // If mm is for December, go to January of the following year
    mm = 0;
    yy = Number("20" + yy) + 1; }
else {
    mm = Number(mm);
    yy = Number("20" + yy); }
var expDate = new Date(yy, mm, 1);


A credit card effectively expires at the very beginning of the mm+1 month. Index-wise, the mm extract maps onto the desired expDate month (e.g., 07 maps onto August) for all months except December, for which mm must be set to 0 (January). For non-December months, the mm string is numberified via the Number( ) function; it's not strictly necessary to do this (string-to-number type conversion is automatically carried out by the JavaScript engine) but I thought, "Why not?"

For the expDate year, we want to prepend 20 to yy and also push 20yy to the next year if mm is 12. The 20yy year must be numberified prior to incrementing it but can be left alone for non-December mms.

Future mischief

According to this site, Expiration dates can be in the range of two to 10 years, but are typically three to five. The code below will reject credit card expiration dates that are more than 10 years into the future:

var futureCutoff = new Date(now.getFullYear( ) + 10, now.getMonth( ) + 1, 1);
if (futureCutoff < expDate) window.alert("Your expiration date is too far into the future.");


Demo

In the div below:
(1) Choose a Card Type.
(2) Enter a value (valid or invalid) into the Credit Card # field and then click outside the field.
(3) Enter a value (valid or invalid) into the Expiration Date field and then click outside the field.
A button is provided for your convenience.

Credit Card Info:
Card Type:

Spaces/dashes between number blocks are OK.

(mm/yy, e.g., 07/13)

We're actually not done with the order.html page yet. Loading the order.html page calls a refresh_ship_details( ) function whereas unloading the page calls an add_ship_details( ) function, and we'll go through these functions in the following entry; moreover, it's time for us to set the shopping cart's cookie machinery in motion, so we'll get to that too.

Tuesday, July 16, 2013
 
The Credit to My Credit Card
Blog Entry #295

We return now to the "So, You Want A Shopping Cart, Huh?" order.html page and the submission of its order form. Unlike Joe's order form, the order form at Gordon's original order.htm page provides a credit card facility, namely, the page's Choose Order Method table offers an On-line / Credit Card: option and is followed by a Credit Card Info table with fields for inputting a credit card number and expiration date.

Gordon's Choose Order Method and Credit Card Info tables

As you would expect, the order.htm page's form submission code is quite a bit different than that for the order.html page, and I thought it would be interesting to look at the former in today's post.

The orderSubmit( ) function

As at the order.html page, clicking the button at the bottom of the order.htm page calls an order.htm orderSubmit( ) function

function orderSubmit(type) {
    if (type == "order") { ... }
    else {
        if (!(checkGiftLength( ))) {
            document.order.form_action.value = type;
            document.order.submit( );
            return true; }
        else return false; } }


and passes it an order string argument. The if (type == "order") { ... } gate is accompanied by an else clause that, if type and order were not equal, would call a checkGiftLength( ) function whose (convertible-to-)false return would cause the form to submit. However, there is no checkGiftLength( ) function in the order.htm source.

Here's what we've got if type and order are equal (and they are):

if (type == "order") {
    if ( ! ((document.order.order_type[0].checked && checkCardNumber( )) || checkRequired( ) || checkExpireDate( ))) {
        document.order.form_action.value = type;
        document.order.submit( );
        return true; }
    else return false; }


Three subconditions must evaluate to false in order to submit the order form:
(1) (document.order.order_type[0].checked && checkCardNumber( ))
(2) checkRequired( )
(3) checkExpireDate( )

I've got your number

The common name of the radio buttons in the order.htm Choose Order Method table is order_type. Strangely, the On-line / Credit Card: radio button has a checked attribute set to an empty string (the other two radio buttons don't have a checked attribute):

<font size="2">On-line / Credit Card: <input type="radio" checked="" name="order_type" value="credit"></font>

The checked="" setting is syntactically incorrect and I'm not quite sure what Gordon meant for it to do - on my computer, it checks the radio button with Communicator 4.61 but has no effect (the radio button remains unchecked) with IE 4.5 - if we want the radio button to be checked, then the correct syntax would be:

<input type="radio" checked name="order_type" value="credit"> <!-- for HTML -->
<input type="radio" checked="checked" name="order_type" value="credit"> <!-- for XHTML -->


In any case, if the order_type[0] (On-line / Credit Card: ) radio button is checked, then the orderSubmit( ) function calls the order.htm checkCardNumber( ) function, which validates the value of the Credit Card # field

<td align="right" bgcolor="#ccffff"><b>Credit Card #:</b></td>
<td bgcolor="#ccffff"><input type="text" size="16" maxlength="16" name="card_no" onblur="checkCardNumber( );"><font size="2"><br>
(no spaces allowed)</font></td>


in the Credit Card Info table. The checkCardNumber( ) function first runs through the Credit Card # characters and checks if they're all digits.

function checkCardNumber( ) {
    var i, c, outString = "", retVal = false;
    for (i = 0; i < document.order.card_no.value.length; i++) {
        c = document.order.card_no.value.charAt(i);
        if (c >= "0" && c <= "9") outString += c;
        else retVal = true; }
    document.order.card_no.value = outString;
    /* Credit Card Sanity Check ... */
    return retVal; }


For comparison operations involving string literals, Mozilla states, Strings are compared based on standard lexicographical ordering, using Unicode values. The Unicode code positions for the 0 to 9 digit characters respectively run from U+0030 to U+0039; any and all non-digit characters will fall outside this range and cause the (c >= "0" && c <= "9") if condition to return false.

If the Credit Card # string contains one or more non-digit characters, then true is assigned to a retVal variable, which at the end of the checkCardNumber( ) function is returned to the orderSubmit( ) function. Back at the orderSubmit( ) function, the (document.order.order_type[0].checked && checkCardNumber( )) subcondition returns true and therefore the overall ( ! ((document.order.order_type[0].checked && checkCardNumber( )) || checkRequired( ) || checkExpireDate( ))) if condition returns false, sending control to the else return false; clause. No explanation is given to the user for why form submission is not successful.

If all of the Credit Card # characters are digits, then those characters are used to build an outString string, which is subsequently assigned to the value of the Credit Card # field. Actually, if the Credit Card # string contained a mix of digit and non-digit characters, then checkCardNumber( ) would build and assign to document.order.card_no.value an outString string comprising the original string's digit characters, i.e., the non-digit characters would be effectively discarded by the c >= "0" && c <= "9" operation, e.g., a 432a543b654c765d entry would be converted to 432543654765. If the Credit Card # string contained only non-digit characters, then outString would remain an empty string, which would again be assigned to document.order.card_no.value, effectively clearing the original entry.

In my book, there is no reason to rebuild the Credit Card # value; if we encounter a non-digit character, then we should immediately pop up a relevant alert( ) message, return true;, and exit the checkCardNumber( ) function. In moving forward, let us assume that the user enters an all-digit value into the Credit Card # field.

You're not my type

As shown in the screen shot at the beginning of the post, the Credit Card Info table contains a Card Type radio button menu of four acceptable credit card options: Visa, MasterCard (M/C), Discover, and American Express (AMEX). After assigning outString to document.order.card_no.value, the checkCardNumber( ) function conducts a Credit Card Sanity Check that tests for card type-number mismatches via checking the user's Card Type choice against the first document.order.card_no.value character.

// Credit Card Sanity Check
if (document.order.card_type[0].checked && document.order.card_no.value.charAt(0) != "4") {
    window.alert("Please enter a valid VISA number");
    retVal = true; }
else if (document.order.card_type[1].checked && document.order.card_no.value.charAt(0) != "5") {
    window.alert("Please enter a valid MasterCard number");
    retVal = true; }
else if (document.order.card_type[2].checked && document.order.card_no.value.charAt(0) != "6") {
    window.alert("Please enter a valid Discover number");
    retVal = true; }
else if (document.order.card_type[3].checked && document.order.card_no.value.charAt(0) != "3") {
    window.alert("Please enter a valid AMEX number");
    retVal = true; }


The common name of the Card Type radio buttons is card_type; the first-in-source-order Visa: option has a checked="" attribute and may or may not be initially checked, depending on the browser (vide supra).

Visa card numbers always begin with a 4; if the Visa: radio button is checked and the first Credit Card # digit is not a 4, then the if clause displays a Please enter a valid VISA number alert( ) message and assigns true to retVal. The else if clauses analogously handle MasterCard, Discover, and American Express card numbers, which always begin with a 5, 6, and 3, respectively. However, Wikipedia's "Payment card number" entry notes that the first digit of a payment card number identifies a card issuer category and not the card issuer itself: an American Express card number may always begin with a 3, but a card number beginning with a 3 is not necessarily for an American Express card.

12 and over, please

The Credit Card Sanity Check concludes with an else if clause that checks the length of the Credit Card # string.

else if (document.order.card_no.value.length < 12) {     window.alert("The card number you entered is not valid");     retVal = true; }

If the Credit Card # string contains fewer than 12 digits, then a The card number you entered is not valid alert( ) message pops up - better than no message at all, but a Your card number must contain 12-16 digits* message would be more appropriate - and true is assigned to retVal.

*As specified in the I've got your number section above, the Credit Card # field has a maxlength="16" attribute that prevents the user from entering more than 16 characters into the field.

No freshness date

If the user's Card Type and Credit Card # inputs get through the Credit Card Sanity Check (if all five if conditions return false), then retVal = false is returned to the orderSubmit( ) function, causing the (document.order.order_type[0].checked && checkCardNumber( )) subcondition to return false. The orderSubmit( ) function moves on to the checkRequired( ) subcondition/function, which we discussed in the previous post; if the user's Customer Information / Details and Shipping Address table inputs are in order, then the checkRequired( ) function returns false to the orderSubmit( ) function - two down, one to go. The orderSubmit( ) function finally moves to the checkExpireDate( ) subcondition/function. The order.htm checkExpireDate( ) function

function checkExpireDate( ) {
    if (document.order.order_type[0].checked) {
        if (!document.order.expire_date.value.length) { // If the length is 0
            window.alert("Please enter a valid credit card expiration date");
            return true; } }
    return false; }


returns false - and thereby green-lights the submission of the order form - if the Expiration Date field in the Credit Card Info table

<td align="right" bgcolor="#ccffff"><b>Expiration Date:</b></td>
<td bgcolor="#ccffff"><input type="text" size="16" maxlength="20" name="expire_date" onblur="checkExpireDate( );"><br>
<img src="http://www.mearns.org.uk/scotshop/images/visamast.gif" alt="Cards" width="100" height="30"></td>


has not been left blank. (C'mon, Gordon, we can do better than that.) The checkExpireDate( ) function also returns false if the user has checked the Cheque: or Phone Call: radio button in the Choose Order Method table.

We'll upgrade the checkCardNumber( ) and checkExpireDate( ) functions in the following entry.

Friday, July 05, 2013
 
Order over the Wire
Blog Entry #294

So, a user goes to the "So, You Want A Shopping Cart, Huh?" shopping cart, adds one or more items to the cart, proceeds to the order.html page, and fills out the Customer Information / Details and Shipping Address tables. Ready to order, the user clicks the submit button at the bottom of the page.

<input type="button" value="Place Order" onclick="orderSubmit('order');">

(Technically, this button is a push button and not a submit button because its type attribute is set to button and not submit, but its purpose is to submit the order form, so I'm gonna call it a submit button.)

Clicking the button calls the order.html orderSubmit( ) function and passes it an order string argument.

function orderSubmit(type) {
    if (type == "order") {
        if ( ! (document.order.order_type.checked || checkRequired( ))) {
            window.alert("first true");
            document.order.form_action.value = type;
            document.order.submit( );
            return true; }
        else {
            window.alert("first false");
            return false; } } }


The orderSubmit( ) function gives the order argument a type identifier and then promptly checks if type and order are equal; the if (type == "order") { ... } gate can of course be removed. The rest of orderSubmit( ) comprises an if...else statement whose if clause submits the order form if the user's order.html table inputs are in order.

Go get on the phone

If the user checked the Phone Call: radio button in the Choose Order Method table, then the (document.order.order_type.checked || checkRequired( )) operation short-circuits to true, which is converted to false by the ! operator, and consequently control passes to the else clause, which pops up a first false alert( ) message and unnecessarily returns false.

Something's missing

If the user didn't check the Phone Call: radio button, then the order.html checkRequired( ) function is called.

function checkRequired( ) {
    if (!document.order.name_first.value.length ||
    !document.order.name_last.value.length ||
    !document.order.email.value.length ||
    !document.order.address1.value.length ||
    !document.order.city.value.length ||
    !document.order.state.value.length ||
    !document.order.zip.value.length ||
    !document.order.country.value.length ||
    !document.order.ship_name.value.length ||
    !document.order.ship_address1.value.length ||
    !document.order.ship_city.value.length ||
    !document.order.ship_state.value.length ||
    !document.order.ship_zip.value.length ||
    !document.order.ship_country.value.length) {
        window.alert("You have not completed all required fields:\n" +
        "Please complete the Name, Address, City,\n" +
        "County, Post Code, and Country in both the\n" +
        "Customer Info and Shipping Address sections");
        return true; }
    else { return false; } }


The checkRequired( ) function pops up the alert( ) box below
The checkRequired( ) alert( ) message
and returns true if any of the following fields in the Customer Information / Details and Shipping Address tables have been left blank:

Customer Information / Details
First Name, Last Name, Email Address, Address, City, County, Post Code, Country
Shipping Address
Recipient Name, Address, City, County, Post Code, Country

• The document.order.inputName.value.length expressions can all be shortened to document.order.inputName.value: like 0, the empty string converts to false in a boolean context.

• The alert( ) message fails to include the Email Address field of the Customer Information / Details table in its list of required fields.

Back at the orderSubmit( ) function, a true checkRequired( ) return causes the (document.order.order_type.checked || checkRequired( )) operation to return true, which is !-ed to false, sending control to the else clause, which pops up the first false alert( ) message and returns false, per the preceding section.

Submit it

If the user has entered something (not necessarily a valid something) into each of the required fields listed above, then the checkRequired( ) else clause returns false to the orderSubmit( ) function. As false || false gives false, the overall ! (document.order.order_type.checked || checkRequired( )) if condition now returns true; as a result, the if clause
(1) pops up a first true alert( ) message,
(2) assigns type/order to the value of the order form's form_action field (vide supra),
(3) submits the order form via a document.order.submit( ); command, and
(4) unnecessarily returns true.

The processing agent

<form action="mailto:youremail@address.here.com" method="post" name="order"> ... </form>

Joe sets the order form's action attribute to a mailto: URL, which would have been OK for IE 4.x or Netscape 4.x but is not OK for modern browsers: see The mailto: blues section of Blog Entry #172. For his part, Gordon much more sensibly sends the form data to a formmail.pl CGI script, which is provided in the shopcart.zip package.

<form action="http://www.mearns.org.uk/cgi-bin/formmail.pl" method="post" name="order"> ... </form>

If you're a fellow Mac user, you can open and read the formmail.pl file with the Dashcode application.

The order form's data set includes the name-value data for a set of five hidden inputs at the beginning of the form (elements[0]-elements[4]).

<input type="hidden" name="subject" value="Order Forms - NAME OF FORM HERE">
<input type="hidden" name="recipient" value="YOUR EMAIL ADDRESS HERE">
<input type="hidden" name="redirect" value="thanku.htm">
<input type="hidden" name="retailer" value="NAME OF YOUR BUSINESS HERE">
<input type="hidden" name="form_action" value="order">


The subject, recipient, and redirect name values appear in the formmail.pl code whereas the retailer and form_action names don't, and I suspect that the retailer and form_action inputs can be thrown out but I don't know for sure. Note that the form_action input's value is already order, so we can at least delete the document.order.form_action.value = type; statement in the orderSubmit( ) function.

As Joe notes in the Altering the order.html Page section of the tutorial, your role here is to replace the CAPS parts with appropriate values. The redirect input's value points to a "thank you for your order" thanku.htm page to which the user is routed after the form has been submitted; a sample thanku.htm page is provided by the shopcart.zip package.

orderSubmit( ) retool

Per the preceding discussion, the orderSubmit( ) function can be streamlined as follows:

function orderSubmit( ) {
    if (document.order.order_type.checked)
        window.alert("The 'Phone Call' radio button in the Choose Order Method section must be unchecked in order to submit the form.");
    else if (!checkRequired( )) {
        document.order.submit( );
        window.alert("Thank you!"); } }


If we're going to condition the submission of the order form on the checked status of the order_type radio button, then we should give the user a more meaningful explanation than first false for why the submission is unsuccessful when the radio button is checked, wouldn't you say? Moreover, the checkRequired( ) function pops up its own alert( ) message if the form is not complete, so there's no need to display the first false message in that case either.

An alternate checkRequired( )

It's not necessary to write out a reference expression for each required field in the checkRequired( ) function:

// From the Functional compaction subsection of the previous post:
var userInputs = document.getElementById("userTable").getElementsByTagName("input");
var shippingInputs = document.getElementById("shippingTable").getElementsByTagName("input");


function checkRequired( ) {
    var empty = 0;
    for (i = 0; i < 9; i++) {
        if (i == 4) continue;
        if (!userInputs[i].value) empty++; }
    for (i = 1; i < 8; i++) {
        if (i == 3) continue;
        if (!shippingInputs[i].value) empty++; }
    if (empty) { window.alert("Please complete all required fields."); return true; }
    else return false; }


The continue statement allows us to skip over the Address #2 field in both tables.

We'll look at the corresponding form submission code for Gordon's order.htm page in the following entry.


Powered by Blogger

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