PHP

Case study: A generic JavaScript validation function

The example in this section shows more features of JavaScript as a validation tool. An example of errors produced by applying the techniques described in this section to customer validation is shown in Figure 7-2.

Figure 7-2. A dialog box showing errors produced by the JavaScript validation function
figs/wda_0702.gif

A sophisticated and general-purpose data entry function for post-validation and batch error reporting is shown in Example 7-2. Only part of the script is shown; the remainder of the script includes the same PHP code to retrieve data and the HTML to display the customer <form> as in Example 6-7 in Chapter 6.

Example 7-2. A general-purpose JavaScript <form> validation function
<!DOCTYPE HTML PUBLIC
              "-//W3C//DTD HTML 4.0 Transitional//EN"
              "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Customer Entry Form</title>
<script type="text/javascript">
<!-- Hide the script from old browsers
// A utility function that returns true if a string
// contains only whitespace characters.
function isblank(s)
{
  for(var i = 0; i < s.length; i++)
  {
     var c = s.charAt(i);
     if ((c != ' ') &&
         (c != '\n') &&
         (c != '\t'))
        return false;
  }
  return true;
}
// This is the function that performs <form> validation.
// It will be invoked from the onSubmit(  ) event handler.
// The handler should return whatever value this function
// returns.
function verify(f)
{
  var msg;
  var empty_fields = "";
  var errors = "";
  // Loop through the elements of the form, looking for all
  // text and textarea elements that don't have an
  //  "optional" property defined.  Then, check for fields
  // that are empty and make a list of them.
  // Also, if any of these elements have a "min" or a "max"
  // property defined, then verify that they are numbers
  // and that they are in the right range.
  // Put together error messages for fields that are wrong.
  for(var i = 0; i < f.length; i++)
  {
     var e = f.elements[i];
     if (((e.type == "text") ||
         (e.type == "textarea")) &&
         !e.optional)
     {
        // first check if the field is empty
        if ((e.value == null) ||
            (e.value == "") ||
            isblank(e.value))
        {
           empty_fields += "\n        " +
                           e.description;
           continue;
        }
        // Now check for fields that are supposed
        // to be numeric.
        if (e.numeric ||
           (e.min != null) ||
           (e.max != null))
        {
           var v = parseFloat(e.value);
           if (isNaN(v) ||
              ((e.min != null) && (v < e.min)) ||
              ((e.max != null) && (v > e.max)))
           {
              errors += "\n- The field " +
                        e.description +
                        " must be a number";
              if (e.min != null)
                 errors += " that is greater than " +
                           e.min;
              if (e.max != null &&
                  e.min != null)
                 errors += " and less than " +
                           e.max;
              else if (e.max != null)
                 errors += " that is less than " +
                           e.max;
              errors += ".\n";
           }
        }
        // Now check for fields that are supposed
        // to be emails.
        // Not exactly as described in RFC 2822, but
        // a rough attempt
        // of the form "local-bit@domain-bit"
        if (e.email && !isblank(e.value))
        {
           var seenAt = false;
           var append = "";
           for(var j = 0; j < e.value.length; j++)
           {
              var c = e.value.charAt(j);
              if ((c == ' ') ||
                  (c == '\n') ||
                  (c == '\t'))
                 append +=
     "\n           - not contain white-space";
              if ((c == '@') && (seenAt == true))
                 append +=
     "\n           - contain only one @";
              if ((c == '@'))
                 seenAt = true;
           }
           if (seenAt == false)
              append +=
     "\n           - contain exactly one @";
           if (append)
              errors += "- The field " +
                        e.description +
                        " must: " + append;
        }
        // Now check for fields that are supposed
        // to be DOBs.
        if (e.dob && !isblank(e.value))
        {
           var slashCount = 0;
           var append = "";
           var addedError1 = false;
           var addedError2 = false;
           for(var j = 0; j < e.value.length; j++)
           {
              var c = e.value.charAt(j);
              if ((c == '/'))
                 slashCount++;
              if (c != '/' &&
                 (c < '0' || c > '9') &&
                 addedError1 == false)
              {
                 addedError1 = true;
                 append +=
     "\n           - must contain only numbers " +
     "and forward-slashes";
              }
           }
           if (j != 10 || slashCount != 2)
              append +=
     "\n           - must have the format DD/MM/YYYY";
           if (slashCount != 2)
              append +=
     "\n           - must contain two slashes";
           if (append)
              errors +=  "- The field " +
                         e.description +
                         " must: " + append;
        }
        // Now check for fields that are supposed
        // not to have spaces
        if (e.nospaces)
        {
           var seenAt = false;
           var append = "";
           for(var j = 0; j < e.value.length; j++)
           {
              var c = e.value.charAt(j);
              if ((c == ' ') ||
                  (c == '\n') ||
                  (c == '\t'))
                 errors += "- The field " + e.description +
                           " must not contains white-space";
           }
        }
     } // if (type is text or textarea) and !optional
  } // for each character in field
  // Now, if there were any errors, then display the
  // messages, and return true to prevent the form from
  // being submitted.  Otherwise return false
  if (!empty_fields && !errors)
     return true;
  msg  = "____________________________________________________  _  _\n\n"
  msg += "The form was not submitted because of the " +
         "following error(s).\n";
  msg += "Please correct these error(s) and re-submit.\n";
  msg += "____________________________________________________  _  _\n\n"
  if (empty_fields)
  {
     msg += "- The following required field(s) are empty:"
           + empty_fields + "\n";
     if (errors)
        msg += "\n";
  }
  msg += errors;
  alert(msg);
  return false;
}
// end hiding -->
</script>
</head>
<body>
<h2>Customer Details</h2>
<hr>
<form onSubmit="this.firstName.nospaces = true;
  this.firstName.description = 'First Name';
  this.surname.description = 'Surname';
  this.address1.description = 'Address Line 1';
  this.city.description = 'City';
  this.email.description = 'Email';
  this.email.email = true;
  this.dob.dob = true;
  this.dob.description = 'Date of Birth (DD/MM/YYYY)';
  return verify(this);"
  method="post" action="example.6-8.php">

In the example, the <form> tag contains a long script for the onSubmit event that is called when the user clicks the Submit button. The code creates and sets properties for each data entry widget. As all widgets are mandatory, a description property is created and set (e.g., this.email.description = 'Email'). This description is later displayed in an error dialog box if data isn't entered. For widgets that are optional-there are none in this example, but the full customer <form> in Chapter 10 has them-an optional = true property can be set.

For widgets that require specific validation, a property that describes the data type is set. For example, the email widget has a property of this.email.email = true to ensure that validation appropriate to an email field is performed. After setting all properties for all fields, the verify( ) function is called with the <form> (this refers to the <form>) object as a parameter; the <form> object includes all widgets and their properties.

For compactness, we don't describe in detail how the verify( ) function works. However, it has the following features:

  • The function progressively creates a message to display to the user-much like $errorString in the PHP validation-as errors are detected. After collecting all errors, an error dialog box is shown listing all errors the user needs to correct before the <form> will submit. An example of the error dialog box is shown in Figure 7-2.

  • All widgets that are inputs of type text or textarea and aren't optional are checked to ensure they contain data.

  • Numeric fields are checked to ensure they are actually numeric and, if the value must fall in a range, the value is checked to ensure it's within the range.

  • Emails are checked in a simplistic way. The email must contain exactly one @ symbol and must not contain whitespace.

  • Dates are checked to ensure they are in the DD/MM/YYYY format used in most countries.

  • Fields that should not contain whitespace are checked to ensure they don't contain spaces, tabs, or carriage returns.

The verify( ) function isn't comprehensive and certainly doesn't do all the validation proposed for the winestore customer <form>. However, in most cases, the customer <form> can't be submitted without a good chance of it passing the server-side validation checks.

JavaScript code can be reused across multiple HTML pages without adding the code to each page. For example, the code surrounded by the <script> and </script> tags in Example 7-2 can be saved in the file valid.js and then included into several HTML pages using the src attribute of the <script> element:

<script type="text/javascript" src="valid.js">
</script>

This approach has the advantage of reducing network traffic if the user has a web browser cache, because a copy of the script can be reused in multiple HTML pages.

Case study: A password <form> validation function

Example 7-3 gives a final example of JavaScript validation. In this example, the validation is interactive; that is, the fields are validated as data is entered. Instead of the onSubmit event, an onChange event is trapped for the two password widgets, formPassword1 and formPassword2; the function thesame( ) is called whenever the user changes the data in a widget and then leaves it. The reporting is field-by-field, and a sample dialog box output by the script is shown in Figure 7-3.

Figure 7-3. A dialog box produced by the script in Example 7-3
figs/wda_0703.gif

The function thesame( ) checks if the current widget contains data. If it does, the data in the widget is compared to the data in the other password widget. If the data in the widgets is different, an error message is shown to the user. It's necessary to test whether both widgets actually contain data in interactive validation; without this check, the function annoyingly displays an error before the user has the opportunity to enter data into both widgets.

Example 7-3. Using JavaScript for interactive validation of password fields
<!DOCTYPE HTML PUBLIC
              "-//W3C//DTD HTML 4.0 Transitional//EN"
              "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Password Validation</title>
<script type="text/javascript">
<!-- Hide the script
function thesame(value1, value2, description)
{
  if (((value1 != null) ||
       (value1 != "")) &&
       value2 != "" &&
       value1 != value2)
  {
       alert("The " + description + " must be identical.");
       return (false);
  }
  return (true);
}
// end hiding -->
</script>
</head>
<body>
  <h2>Username Form</h2>
  <form
 method="post" action="test.php">
  <br>Username:
  <input type="text" name="userName" size=10>
  <br>Password:
  <input type="password" name="formPassword1" onChange="
    thesame(formPassword1.value, formPassword2.value,
            'passwords');"
    size=10>
  <br>Re-enter password:
  <input type="password" name="formPassword2" onChange="
    thesame(formPassword2.value, formPassword1.value,
            'passwords');"
    size=10>
  <br><input type="submit" value="SUBMIT">
  </form>
</body>
</html>

There are several other events that are commonly trapped and handled in validation:

onBlur

When a user removes focus from a <form>, <frame>, or window

onClick

Left mouse button click on a <form> element

onFocus

When a user brings focus to a <form>, <frame>, or window

onUnload

When the user exits a page