Checking form data

Client-side validation

Where are we?

In a Web application, data validation takes place on the browser and on the server. You learned how to do some validation with JavaScript in a ClientCore lesson.

This lesson reminds you how to do validation in JavaScript. It also tells you how to do better error reporting.

This lesson’s goals

By the end of this lesson, you should:

  • Know how to get form data into JavaScript variables.
  • Be able to check that data for errors.
  • Report errors to the user.

A JavaScript and jQuery review

Let’s review some JavaScript and jQuery. Let’s start with a page like this:

Empty form

Figure 1. Empty form

Notice that the input focus is on the first field; you can tell because the cursor is there.

The user completes the field and clicks the Go button. Here is the result.

Complete form

Figure 2. Complete form

The output area only appears after the button is clicked. It appears with an animated effect.

You can try the page.

Here is the code.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Review</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
      /* Output area starts out hidden. */
      #output_area {
        display: none;
      }
      /* Used to highlight the user's name. */
      .highlight {
        font-weight: bold;
        color: red;
      }

    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        //Put the input focus in the first field.
        $("#first_name").focus();
        //Set up the button click.
        $("#go").click(function(){
          //Get the input.
          var first_name = $("#first_name").val();
          var last_name = $("#last_name").val();
          //Compute the full name.
          var full_name = first_name + ' ' + last_name;
          //Show output.
          $("#full_name")
            .text(full_name)
            .addClass('highlight');
          $("#output_area").show('medium');
        });
      });
    </script>
  </head>
  <body>
    <h1>Review</h1>
    <p>
      First name:
      <input type="text" id="first_name" size="20">
    </p>
    <p>
      Last name:
      <input type="text" id="last_name" size="20">
    </p>
    <p>
      <button type="button" id="go">Go</button>
    </p>
    <div id="output_area">
      <p>Your full name is <span id="full_name"></span>.</p>
    </div>
  </body>
</html>

Figure 3. Code for the page

Line 23 loads the jQuery library, from one of Google’s fast servers.

Line 25 starts the JavaScript. The line is:

$(document).ready(function() {

The code in the ready() function runs when the page has loaded.

Line 27:

$("#first_name").focus();

sets the input focus on to the HTML element with the id of first_name. That element is:

<input type="text" id="first_name" size="20">

That’s how the input focus appears in the first name field when the page first appears.

The field has an id attribute so that jQuery can reference the fields. But it doesn’t have a name attribute. name is used when sending data to a server. We’re not doing that here, so no name attribute is needed.

There is no <form> tag around the <input> elements. It’s only needed when data is being sent to a server, which we aren’t doing.

This line (number 29):

$("#go").click(function(){

attaches some code to the button’s click event. The code runs when the user clicks the button.

The tag for the button says type="button". The forms we’ve seen recently have used type="submit". A submit button is only used when sending (submitting) data to a server. We’re not doing that, so type="button" makes more sense.

What do we want to happen when the user clicks the button? Here’s some pseudocode.

Get the first and last name the user typed.
Put them together to make the full name.
Show the full name.

Figure 4. click pseudocode

It’s a good idea to write pseudocode for every function you create. This helps:

  • Be clear about the goal of the function, that is, what it does.
  • Plan the code, without getting bogged down in the syntax details.

Here’s the JavaScript:

//Get the input.
var first_name = $("#first_name").val();
var last_name = $("#last_name").val();
//Compute the full name.
var full_name = first_name + ' ' + last_name;
//Show output.
$("#full_name")
  .text(full_name)
  .addClass('highlight');
$("#output_area").show('medium');

Figure 5. click code

Let’s see how this achieves the steps given in the pseudocode (Figure 4).

The first step in the pseudocode is:

Get the first and last name the user typed.

I added a comment in line 30, explaining what the code is doing.

Line 31 gets the value the user typed into the first_name field, and puts it into the JavaScript variable first_name:

var first_name = $("#first_name").val();

The names of the field and the variable don’t have to be the same, but it makes the code easier to understand. Line 32 gets the last name.

That’s the first step in the pseudocode: get the user input.

The second step in the pseudocode (Figure 4) is:

Put them together to make the full name.

Line 34 takes the first name, appends a space to it, and then appends the last name. The result gets put into the variable full_name:

var full_name = first_name + ' ' + last_name;

The third step in the pseudocode (Figure 4) is:

Show the full name.

Line 36 finds an element with an id of full_name:

$("#full_name")

That element is the <span>:

<p>Your full name is <span id="full_name"></span>.</p>
Next, set the text of that element to the contents of the variable full_name:

$("#full_name")
   .text(full_name)

Then add the class highlight to the element:

$("#full_name")
   .text(full_name)
   .addClass('highlight');

The class is defined in the CSS as:

.highlight {
   font-weight: bold;
   color: red;
}

Lines 36 to 38 use method chaining (“method” is a type of function). The statement could have been typed all on one line, like this:

$("#full_name").text(full_name).addClass('highlight');

Spreading across three lines makes it easier to read.

Line 39 is:

$("#output_area").show('medium');

It makes the element with the id of output_area appear. It uses an animated effect at medium speed.

Let’s look at the structure of the output area again, just to be sure what is going on.

<div id="output_area">
  <p>Your full name is <span id="full_name"></span>.</p>
</div>

Part of Figure 3 (again). Code for the page

output_area is a container for, well, the output area. Inside it are places for the individual output pieces. There is only one in this example: full_name. But there could be more.

This is a common pattern.

Create a container for the output.
Have individual output elements inside the container.
Fill in the individual output elements.
Show the entire output container at once.

Figure 6. Pattern for output with HTML and JavaScript

You can try the page.

Exercise: Your favorite animals

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user fills in the form and clicks the button:

Form with data

Figure 2. Form with data

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Checking numbers

Let’s see how you can use JavaScript to check the data that users type into form fields. Start with this one:

Age check form

Figure 6. Age check form

The user types something in the field, and clicks the button. Some JavaScript checks the value, and shows a message, like this:

Age error

Figure 7. Age error

You can try it.

Here is the code:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Check age 1</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#age").focus();
        $("#go").click(function(){
          var age = $("#age").val();
          if ( age == "" ) {
            alert("Sorry, please enter your age.");
          }
          else if ( isNaN(age) ) {
            alert("Sorry, please enter a number.");
          }
          else if ( age < 0 || age > 120 ) {
            alert("Sorry, please enter a valid age.");
          }
          else if ( age < 21 ) {
            alert("Sorry, you are too young.");
          }
          else {
            alert("Thanks! You can proceed.");
          }
          $("#age").focus();
        });
      });
    </script>
  </head>
  <body>
    <h1>Check age 1</h1>
    <p>
      Age:
      <input type="text" id="age" size="3">
    </p>
    <p>
      <button type="button" id="go">Go</button>
    </p>
  </body>
</html>

Figure 8. Code for age error check

Line 18 put the value the user typed into the variable age.

var age = $("#age").val();

Here is the first check:

if ( age == "" ) {

This is true if the variable is an empty string. Remember:

  • Use == and not =.
  • "" is an empty string. The quotes are together with no space between them. This is not the same as " ", which is a string with a space in it.

Line 22 is:

else if ( isNaN(age) ) {

The isNaN() function returns true if the parameter is Not a Number.

This (line 25):

else if ( age < 0 || age > 120 ) {

means “if age is less than 0 or greater than 120.” || means “or,” && means “and,” and ! means “not.”

Here’s another example.

width > 10 && width < 50

width is more than 10 and less than 50. Note that leaving out the second width will not work:

width > 10 && < 50

Some more.

x < 10 || y == 10

x is less than 10, or y is equal to 10. You can have as many different variables as you want in an if test.

(x + y) < 100 && z != 11

x plus y is less than 100, and z does not equal 11. You can do computations like this in if statements.

Exercise: Check song time

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user clicks the button, JavaScript code checks the values in the fields. Each field must have a valid number. Minutes must be from 0 to 120. Seconds must be from 0 to 59.

Here’s an error message:

Error

Figure 2. Error

If the data is OK, show this:

Data OK

Figure 3. Data OK

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Checking strings

Time to see how you can check strings. Here’s a log in form.

Empty log in form

Figure 9. Empty log in form

Here’s an error message showing.

Log in error

Figure 10. Log in error

Here’s the code:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Check log in 1</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#user_name").focus();
        $("#login").click(function(){
          var user_name = $("#user_name").val();
          var password = $("#password").val();
          if ( user_name == "" || password == "" ) {
            alert("Sorry, you must enter both your user name and password.");
          }
          else if ( user_name.length < 6 ) {
            alert("Sorry, the user name is too short.");
          }
          else if ( password.length < 6 ) {
            alert("Sorry, the password is too short.");
          }
          else if ( user_name.toLowerCase() == "renata" ) {
            alert("Sorry, Renata! You need to get permission.");
          }
          else {
            alert("Thanks! You can proceed.");
          }
          $("#user_name").focus();
        });
      });
    </script>
  </head>
  <body>
    <h1>Check log in 1</h1>
    <p>
      User name:
      <input type="text" id="user_name" size="20">
    </p>
    <p>
      Password:
      <input type="password" id="password" size="20">
    </p>
    <p>
      <button type="button" id="login">Login</button>
    </p>
  </body>
</html>

Figure 11. Log in code

Line 48 has something new:

<input type="password" id="password" size="20">

A field with a type of password is just like a regular text field, except that it hides what the user types.

Password field in action

Figure 12. Password field in action

I gave the field an id of password. It could have been anything, but this id makes sense.

Lines 18 and 19:

var user_name = $("#user_name").val();
var password = $("#password").val();

get the values the user typed into the form.

This (line 20):

if ( user_name "" || password "" ) {

checks whether user_name or password are empty.

Here’s line 23:

else if ( user_name.length < 6 ) {

user_name.length returns the number of characters in a string.

Here’s line 29:

else if ( user_name.toLowerCase() == "renata" ) {

user_name.toLowerCase() returns a string user_name, but with all of the characters converted to lower case. So the if statement will succeed if the user enters renata, Renata, RENATA, renatA, etc.

This does not change the value of user_name. If we wanted to do that, we could write:

user_name = user_name.toLowerCase();

Exercise: New dog name

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user clicks the button, JavaScript code checks the value in the field. The field cannot be empty. It must contain a value that is longer than one character, and shorter than 21 characters.

Here’s an error message:

Error

Figure 2. Error

If the data is OK, show this:

Data OK

Figure 3. Data OK

Hint: this combines the error checking in this section with the output method we saw earlier – showing an output container.

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Reporting errors

So far, we have used simple alert() statements to report errors. But we can do better.

Let’s change the age page. The new page starts off like this:

Empty form

Figure 13. Empty form

When there is an error, a message appears in the content of the page, right below the field the message is about. Like this:

Showing an error

Figure 14. Showing an error

Try the page. You’ll see that the error message appears with an animated effect.

Here is the code.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Check age 2</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
      #error_message {
        font-weight: bold;
        color: red;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#age").focus();
        $("#error_message").hide();
        $("#go").click(function(){
          var age = $("#age").val();
          if ( age == "" ) {
            $("#error_message").text("Sorry, please enter your age.");
            $("#error_message").show('medium');
          }
          else if ( isNaN(age) ) {
            $("#error_message").text("Sorry, please enter a number.");
            $("#error_message").show('medium');
          }
          else if ( age < 0 || age > 120 ) {
            $("#error_message").text("Sorry, please enter a valid age.");
            $("#error_message").show('medium');
          }
          else if ( age < 21 ) {
            $("#error_message").text("Sorry, you are too young.");
            $("#error_message").show('medium');
          }
          else {
            $("#error_message").hide();
            $("#ok_message").text("Thanks! You can proceed.");
          }
          $("#age").focus();
        });
      });
    </script>
  </head>
  <body>
    <h1>Check age 2</h1>
    <p>
      Age:
      <input type="text" id="age" size="3"><br>
      <span id="error_message"/>
    </p>
    <p>
      <button type="button" id="go">Go</button>
    </p>
    <p id="ok_message"/>
  </body>
</html>

Figure 15. Code for improved error reporting

Let’s look at the HTML first. There’s a place for an error message after the input field (see line 53):

<input type="text" id="age" size="3"><br>
<span id="error_message"/>

The span will show an error message when there is one.

Note that you can use <span id="error_message"/> as a shortcut for <span id="error_message"></span>, when the tag is empty.

The error message is hidden when the page loads:

$(document).ready(function() {
   ...
   $("#error_message").hide();

error_message is highlighted by some CSS (lines 12 to 15).

#error_message {
   font-weight: bold;
   color: red;
}

If there is an error, some text is put into error_message, and it is shown. Here are lines 25 and 26:

$("#error_message").text("Sorry, please enter your age.");
$("#error_message").show('medium');

All of the errors are handled the same way. For example:

$("#error_message").text("Sorry, please enter a number.");
$("#error_message").show('medium');

What if there is no error? Here’s what happens (line 41):

$("#error_message").hide();
$("#ok_message").text("Thanks! You can proceed.");

This hides whatever error message was showing (if any). Then an “OK” message shows in a different place.

Exercise: New dog name, again

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user clicks the button, JavaScript code checks the value in the field. The field cannot be empty. It must contain a value that is longer than one character, and shorter than 21 characters.

Here’s an error message:

Error

Figure 2. Error

If the data is OK, show this:

Data OK

Figure 3. Data OK

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Using a function

Have another look at the error checking code.

var age = $("#age").val();
if ( age == "" ) {
  $("#error_message").text("Sorry, please enter your age.");
  $("#error_message").show('medium');
}
else if ( isNaN(age) ) {
  $("#error_message").text("Sorry, please enter a number.");
  $("#error_message").show('medium');
}
else if ( age < 0 || age > 120 ) {
  $("#error_message").text("Sorry, please enter a valid age.");
  $("#error_message").show('medium');
}
else if ( age < 21 ) {
  $("#error_message").text("Sorry, you are too young.");
  $("#error_message").show('medium');
}
else {

Part of Figure 15 (again). Code for improved error reporting

There’s a lot of repeated code:

$("#error_message").text( A message );
$("#error_message").show('medium');

It would be better to create a function. Here’s the new code:

var age = $("#age").val();
if ( age == "" ) {
  show_error("Sorry, please enter your age.");
}
else if ( isNaN(age) ) {
  show_error("Sorry, please enter a number.");
}
else if ( age < 0 || age > 120 ) {
  show_error("Sorry, please enter a valid age.");
}
else if ( age < 21 ) {
  show_error("Sorry, you are too young.");
}
else {

...

//Show an error message.
function show_error(message) {
  $("#error_message").text(message);
  $("#error_message").show('medium');
}

Figure 16. Error function

This moves the repeated code into a function. Whatever message you send to the function will show in error_message. For example:

show_error("Sorry, you have fleas.");

show_error("Sorry, your " + toy_name + " is broken.");

The main advantage of this is that we can change the error processing all at once. For example, suppose we want to change the animation speed of all the error messages. So we want:

$("#error_message").show('medium');

to be:

$("#error_message").show('slow');

In Figure 15, we have several lines to change. It would be easy to miss one.

But with the function, we just change medium to slow on one line:

function show_error(message) {
   $("#error_message").text(message);
   $("#error_message").show('slow');
}

Functions give us another productivity win when changing a page.

Exercise: New dog name, once more with functions

Create a page that looks like this when first displayed:

Empty form

Figure 1. Empty form

Notice that the input focus is in the first field.

When the user clicks the button, JavaScript code checks the value in the field. The field cannot be empty. It must contain a value that is longer than one character, and shorter than 21 characters.

Here’s an error message:

Error

Figure 2. Error

Use a function to show error messages. Call the same function for every error message.

If the data is OK, show this:

Data OK

Figure 3. Data OK

You can try my solution to see how it works. But don’t look at the source code until you try it yourself first!

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

  • Less code to change.
  • Less chance of making a mistake.

W00f!

Summary

In this lesson, you learned:

  • How to get form data into JavaScript variables.
  • How to check that data for errors.
  • How to report errors to the user.

What now?

This lesson looked at client-side validation. Let’s start learning about server-side validation.

Basic server-side validation

Where are we?

We just looked at client-side validation with JavaScript. Time to start working on the server side.

This lesson’s goals

By the end of this lesson, you should:

  • Understand why you need server-side and client-side validation.
  • Know how to check whether a value sent to the server is a valid number.

The need for server-side validation

You need to validate form data on the server (with PHP) as well as on the client (with JavaScript). Why? Three reasons.

Reason 1: There are some checks you can only do on the server.

Suppose you have an online store selling dog toys. A user enters the number of toys s/he wants:

Ordering toy

Figure 1. Ordering toy

Maybe you’ve sold out of the Giant Chew Rope. You want to tell the user:

Out of stock

Figure 2. Out of stock

The code for this will be something like:

if number_ordered > current_inventory then

(This is pseudocode, of course.)

You get number_ordered from the form the user filled out. But where does current_inventory come from?

As you’ll learn later, you look that up in a database. This is typical:

DB lookup

Figure 3. DB lookup

The if statement above is usually done by PHP code on the server, not the browser. The server has access to the database. The browser does not.

Reason 2: Security.

This is the second reason why you do validation on the server as well as the browser.

Remember that every page a user sees in his or her browser is downloaded to his or her computer. That includes the JavaScript that has the validation code.

A clever hacker might be able to create a new version of your page, without the JavaScript checking. S/he could then fool your server into accepting invalid data.

Reason 3: Coding mistakes.

You might make a mistake coding the JavaScript. For example, you might write:

if ( age = "" ) { //WRONG!
   Tell the user the field is empty
}
else {
   Accept the data
}

The first line is wrong. It should be:

if ( age == "" ) {

An easy thing to miss, but it could mean that you get bad data in your databases. This can mess up sales, event registration, or whatever business your Web site supports.

So, even though you know how to do validation by writing JavaScript that runs on a browser, you also need to know how to do it on a server. Because:

  • There are some checks you can only do on the server.
  • Security.
  • Coding mistakes.

Simple validation

Let’s start out easy (as usual). Let’s make this:

Empty order form

Figure 4. Empty order form

The user types a number and clicks the button:

Order form with data

Figure 5. Order form with data

S/he sees this:

Order output

Figure 6. Order output

But what if the user enters an invalid number?

Bad input

Figure 7. Bad input

This is the result:

Result from bad input

Figure 8. Result from bad input

You can try it.

Here’s the page:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Order Processing</title>
  </head>
  <body>
    <h1>Order Processing</h1>
    <?php
    //Get the input.
    $giant_chew_ropes = $_POST['giant_chew_ropes'];
    //Validate input.
    if ( ! is_numeric($giant_chew_ropes) ) {
      print '<p>Invalid number.</p>';
      print '<p>Please click the Back button on your browser and try again.</p>';
    }
    else {
      //Input OK, show the order.
      //Compute total.
      $giant_chew_ropes_total = $giant_chew_ropes * 12.95;
      //Output total.
      ?>
      <p>Thank you for your order.</p>
      <table cellpadding="5" cellspacing="0" border="1">
        <tr>
          <th>Product</th>
          <th>Number<br>ordered</th>
          <th>Unit price</th>
          <th>Total price</th>
        </tr>
        <tr>
          <td>Giant chew ropes</td>
          <td><?php print $giant_chew_ropes; ?></td>
          <td>$12.95</td>
          <td>$<?php print number_format($giant_chew_ropes_total, 2); ?></td>
        </tr>
      </table>
    <?php
    }
    ?>
  </body>
</html>

Figure 9. Simple validation

Line 11 gets the value the user typed into the form field:

$giant_chew_ropes = $_POST['giant_chew_ropes'];

The if statement in line 13 checks it:

if ( ! is_numeric($giant_chew_ropes) ) {
   print '<p>Invalid number.</p>';
   print '<p>Please click the Back button on your browser and try again.</p>';
}
else {
   Do this if number is OK.
}

The function is_numeric() is true if the argument it’s given is a valid number. Otherwise, it returns false. Recall that ! means “not.” So, if $giant_chew_ropes is not numeric, then output the HTML for an error message.

Remember the JavaScript isNaN() function? It returns true when a value is not a number. So isNaN() is like a backwards is_numeric(). is_numeric() is true when a value is a number, while isNaN() is true when a value is not a number.

There are some other things to notice about Figure 9. Look at the else on line 17. There’s a lot of code in the else, all the way up to line 39. That’s OK. You can put as much code as you like in braces.

Now look at line 22. This switches back from PHP to HTML mode. But it’s still inside the else! So the <table> is only shown if the user’s input passes the validation test.

The opening brace ({) in line 17 needs a closing brace (}) to go along with it. You can see that in line 39. But notice that the brace is a PHP brace. So you need to switch back to PHP mode to close the brace in line 17.

Switching modes like this is very common in PHP land. But it can be tricky for beginners. Remember that when you need to close a brace, make sure you’re in PHP mode.

Exercise: Dog weight

Create a page with a form:

Input

Figure 1. Input

Send the form data to a PHP page. If the user enters an invalid number, show this:

Error

Figure 2. Error

If there’s a valid number, show this:

Output

Figure 3. Output

You can see my solution. You can also download the files. Of course, do the exercise yourself before you look at my solution.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

You learned:

  • Why you need server-side and client-side validation.
  • How to check whether a value sent to the server is a valid number.

What now?

Let’s see how you can do better validation.

Improving validation

Where are we?

You learned to send a form value to a PHP page, and do one check on it. Let’s see how to do a bunch of checks.

More thorough checks

Here’s the order form again:

Order form

Figure 1. Order form

On the previous page we checked whether the user typed a non-numeric value into the field. But there are other errors the user could make, like:

  • Leave the field empty.
  • Type a negative number.
  • Order more than we have in the warehouse (if this is an “error” – it might not be).

Let’s improve the validation, so that it checks for these things. You can try the improved version.

Here’s the validation code:

//Get the input.
$giant_chew_ropes = $_POST['giant_chew_ropes'];
//Validate input.
$error_message = '';
if ( $giant_chew_ropes == '' ) {
  $error_message = 'Please enter the number you want to order.';
}
else if ( ! is_numeric($giant_chew_ropes) ) {
  $error_message = 'Please enter a valid number.';
}
else if ( $giant_chew_ropes <= 0 ) {
  $error_message = 'Please enter a positive number.';
}
else if ( $giant_chew_ropes > 10 ) {
  $error_message = 'Sorry, we don\'t have that many in stock.';
}
//Any error made?
if ( $error_message != '' ) {
  print "<p>$error_message</p>";
  print '<p>Please click the Back button on your browser and try again.</p>';
}
else {
  //Input OK, show the order.
...

Figure 2. Improved validation code

The first thing to notice is the variable $error_message. It’s created on line 13, and set to an empty string.

$error_message = '';

Then there’s a bunch of error checking if statements. Any one of them could find an error, and give $error_message a value, like this:

if ( there is something wrong ) {
   $error_message = ' An error message ';
}

There can be as many error checks as are needed: 3, 8, 18, whatever.

What happens after all of the error checks have been run? If there is any error at all, then $error_message will have some text in it. If there have been no errors, then $error_message will have the same empty string it started with.

This is checked in line 27:

if ( $error_message != '' ) {
   print "<p>$error_message</p>";
   print '<p>Please click the Back button on your browser and try again.</p>';
}

If $error_message does not equal an empty string, it means that one of the if statements found an error, and put something in $error_message.

Line 28 has double quotes (”) in it:

print "<p>$error_message</p>";

Remember, this means that PHP will insert the value of the variable $error_message before printing anything.

Notice how flexible this is. We can add and remove as many error checks as we like. As long as each one uses $error_message, it will all work.

Here’s line 23:

else if ( $giant_chew_ropes > 10 ) {

This is the inventory level check. It assumes that we have 10 items to sell. In a real program, the 10 would probably be pulled from a database.

Actually, in a real business, we would probably allow back orders.

Exercise: Dog weight again

Create a page with a form:

Input

Figure 1. Input

Send the form data to a PHP page. If the user enters an invalid number, show an error message:

Error

Figure 2. Error

Check for:

  • An empty field
  • A non-numeric value.
  • A value of zero or less.
  • A value greater than 500.

If there’s a valid number, show this:

Output

Figure 3. Output

You can see my solution. You can also download the files. Of course, do the exercise yourself before you look at my solution.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

In this lesson, you learned how to do a sequence of checks on a value passed to a PHP page.

What now?

You’ve learned a lot about validation. But we still need to make it better. For example, one problem is that, when there is an error, you get a message like this:

Error message

Figure 3. Error message

This isn’t very good. Let’s make it better, more like something you see in a real application.

But before we do that, let’s take a detour, and talk about functions in PHP. It will make our work easier.

PHP functions

Where are we?

You know how to run a bunch o’ validation in PHP. We can improve the code by using functions. Let’s see how.

This lesson’s goals

By the end of this lesson, you should:

  • Know what a function is.
  • Know why functions are Good Things.
  • Be able to create a function library.

What is a function?

A function is a piece of code that has three things:

  • A name, e.g., validate_order, or compute_area.
  • Some data going in (optional).
  • Some data coming out (optional).

Functions do three Good Things for us.

  • They reduce duplicate code, making Web sites easier to maintain.
  • They let us create reusable libraries we can drop into any page.
  • They clump PHP statements together, making a program easier to understand.

Let’s see an example. Here’s a page that computes the area of some rectangles.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Rectangle Areas</title>
  </head>
  <body>
    <h1>Rectangle Areas</h1>
    <?php
    $r1_width = 7;
    $r1_length = 3;
    $r1_area = rectangle_area($r1_width, $r1_length);
    print "<p>A rectangle with sides of $r1_width and $r1_length has an area of $r1_area.</p>";
    
    $r2_width = 5;
    $r2_length = 6;
    $r2_area = rectangle_area($r2_width, $r2_length);
    print "<p>A rectangle with sides of $r2_width and $r2_length has an area of $r2_area.</p>";
    
    function rectangle_area($width, $length) {
      $area = $width * $length;
      return $area;
    }
    ?>
  </body>
</html>

Figure 1. Area code

The functions starts in line 20. Its name is rectangle_area. It has two input parameters: $width, and $length. The return statement stops the function, and sends back some data.

A function call sends some data to the function, and (maybe) does something with the result that comes back from the function’s return statement. For example:

$r1_area = rectangle_area($r1_width, $r1_length);

The values in $r1_width and $r1_length go into the function. The return statement sends back a result, which gets stored in $r1_area.

The arguments in the call and the function match by position, not name.

Argument matching

Figure 2. Argument matching

That’s why the function can be called with different variables each time.

You can try the program.

Here’s part of Figure 1 again:

<?php
$r1_width = 7;
$r1_length = 3;
$r1_area = rectangle_area($r1_width, $r1_length);
print "<p>A rectangle with sides of $r1_width and $r1_length has an area of $r1_area.</p>";

$r2_width = 5;
$r2_length = 6;
$r2_area = rectangle_area($r2_width, $r2_length);
print "<p>A rectangle with sides of $r2_width and $r2_length has an area of $r2_area.</p>";

function rectangle_area($width, $length) {
  $area = $width * $length;
  return $area;
}
?>

Figure 1 (again). Area code

The function is after the rest of the code on the page. But it can be in other places as well. For example, it could be above the other code, like this:

<?php
function rectangle_area($width, $length) {
  $area = $width * $length;
  return $area;
}

$r1_width = 7;
$r1_length = 3;
$r1_area = rectangle_area($r1_width, $r1_length);
print "<p>A rectangle with sides of $r1_width and $r1_length has an area of $r1_area.</p>";

$r2_width = 5;
$r2_length = 6;
$r2_area = rectangle_area($r2_width, $r2_length);
print "<p>A rectangle with sides of $r2_width and $r2_length has an area of $r2_area.</p>";
?>

Figure 3. Moving the function up

Or it could be in the middle:

<?php
$r1_width = 7;
$r1_length = 3;
$r1_area = rectangle_area($r1_width, $r1_length);
print "<p>A rectangle with sides of $r1_width and $r1_length has an area of $r1_area.</p>";

function rectangle_area($width, $length) {
  $area = $width * $length;
  return $area;
}

$r2_width = 5;
$r2_length = 6;
$r2_area = rectangle_area($r2_width, $r2_length);
print "<p>A rectangle with sides of $r2_width and $r2_length has an area of $r2_area.</p>";
?>

Figure 4. Moving the function to the middle

The function can even be in another file. As we’ll see.

Creating a function library

The function rectangle_area works, but is so simple that it is hardly worth using. Let’s change things by:

  • Creating a more complex function.
  • Moving the function into a library file.

Here is the page:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Normal probability density function</title>
  </head>
  <body>
    <?php
    require 'normal-pdf.inc';
    print normal_pdf(.5, 0, 1);
    ?>
  </body>
</html>

Figure 5. Another example

Line 10 calls a function called normal_pdf and sends it three arguments. It prints the return value. Note that you can do anything with the return value: print it, put it in a variable, use it in an if statement, and so on.

The function isn’t in Figure 5. It’s in a separate file, normal-pdf.inc. Here’s the contents of that file.

<?php
function normal_pdf($x, $mu, $sigma) {
  $denominator = sqrt(2 * pi()) * $sigma;
  $numerator = exp(-($x - $mu) * ($x - $mu) / ( 2 * $sigma));
  $density = $numerator / $denominator;
  return $density;
}
?>

Figure 6. The function

Being in a separate file, it can be inserted into any number of PHP pages. And it’s a lot more complex than rectangle_area.

The function returns a probability value from a normal distribution. It was adapted from code on this page.

When variables are created inside a function, they are called “local variables.” They are not available outside the function. They are destroyed when the function exits. So if you tried to print the variable $denominator outside the function, you would get an error. It only exists inside the function.

This is a Good Thing. It means you can write a program like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Normal probability density function</title>
  </head>
  <body>
    <?php
    require 'normal-pdf.inc';
    $numerator = 1;
    $denominator = 2;
    $p = normal_pdf($numerator/$denominator, 0, 1);
    ?>
    <p>Numerator: <?php print $numerator; ?></p>
    <p>Denominator: <?php print $denominator; ?></p>
    <p>p: <?php print $p; ?></p>
  </body>
</html>

Figure 7. Local variables in action

If you try the program, you will see that it works. Neither $numerator nor $denominator – names that appear in both the function and the code that calls it – get clobbered.

Why is there no confusion between $numerator in line 10 of Figure 7, and $numerator in line 4 of the inserted file, shown in Figure 6? When the function normal_pdf runs, it creates a new $numerator variable all of its own. It gets wiped out when the function exits.

Why is this a Good Thing? Because it makes it easier to use function libraries. You can insert normal-pdf.inc into any PHP file you want, without worrying about whether variables will get hurt. W00f!

String functions

Here’s another one:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Farm animal plurals</title>
  </head>
  <body>
    <h1>Farm animal plurals</h1>
    <p>The plural of "dog" is <?php print farm_animal_plural('dog'); ?>.</p>
    <p>The plural of "cat" is <?php print farm_animal_plural('cat'); ?>.</p>
    <p>The plural of "goose" is <?php print farm_animal_plural('goose'); ?>.</p>
    <?php
    function farm_animal_plural($animal) {
      if ( $animal == 'sheep' ) {
        return 'sheep';
      }
      if ( $animal == 'goose' ) {
        return 'geese';
      }
      if ( $animal == 'fish' ) {
        return 'fish';
      }
      return $animal . 's';
    }
    ?>
  </body>
</html>

Figure 8. String function

The function takes the name of a farm animal, and returns the plural. It deals with a few special cases, and then just returns the input value, with an “s” attached.

It isn’t a very good function.

Some things to notice about this example:

  • There are many return statements. As soon as one is encountered, PHP exits.
  • You can return any value, not just a plain variable. Line 23 attaches something to a variable before returning it.
  • You can output the return value of a function directly. You don’t need to put it in a variable first. Lines 9, 10, and 11 do this.

Summary

This lesson explains:

  • What a function is.
  • Why functions are Good Things.
  • How to create a function library.

What now?

Let’s see how you can write a PHP validation function.

A PHP validation function

Where are we?

You know how to do some validation. You know what a PHP function is. Let’s put them together, and create a PHP validation function.

This lesson’s goals

By the end of this lesson, you should:

  • Be able to write a PHP validation function.
  • Be able to call it.

Two products

Suppose we have two products. Here is a form:

Form

Figure 1. Form

As usual, the user enters the number of each product s/he wants, and clicks the button.

With the data in Figure 1, the user would get:

Output

Figure 2. Output

But what if the user typed this?

Form with bad data

Figure 3. Form with bad data

S/he would see:

Error report

Figure 4. Error report

Notice that each product has its own error line.

You can try the program. You can also download the files.

Repeating the validation tests

The same tests apply to both fields. For example:

  • The value should be a number.
  • The number should not be less than zero.

We could have separate code to test each field. But that would duplicate work. Instead, let’s use the same function to test both fields.

Here’s the function:

//Check the number ordered.
//Input:
//  $number_ordered: The number the user wants.
//  $number_on_hand: The number in inventory.
//Returns:
//  An error message, or an empty string if there was no error.
function check_number_ordered($number_ordered, $number_on_hand) {
  if ( $number_ordered == '' ) {
    return 'Please enter the number you want to order.';
  }
  else if ( ! is_numeric($number_ordered) ) {
    return 'Please enter a valid number.';
  }
  else if ( $number_ordered <= 0 ) {
    return 'Please enter a positive number.';
  }
  else if ( $number_ordered > $number_on_hand ) {
    return 'Sorry, we don\'t have that many in stock.';
  }
  return '';
}

Figure 5. Validation function

The comments from lines 1 to 6 explain what the function does and how to use it. They describe the parameters going into the function, and the return value that comes out.

We need two pieces of information to check whether the number ordered is valid. First, we need the number ordered. We can do most of the checks just with that. But we also need to know the number on hand, for one of the checks. That’s why the function has two parameters.

There are four if statements, each one checking for a different error. Let’s look at the first one.

if ( $number_ordered == '' ) {
   return 'Please enter the number you want to order.';
}

If $number_ordered is empty, the function ends immediately, sending back a message describing the error. It doesn’t get beyond line 9. Otherwise, the next if statement runs.

If there are no errors, none of the returns in the if statements will be run. So what do we do? The last line of the function is:

return '';

If the program gets to this point, none of the if statements has been true (because otherwise one of them would have exited the function already). That means there are no errors; the data is valid. The function returns an empty string, to show that there were no errors.

We could have done something else to show there were no errors. For example, we could have returned “NO ERRORS!” Returning an empty string is common practice, but not the only choice.

Let’s see how the function is used.

Calling the validation function

Here’s the code, with some less interesting stuff omitted:

<h1>Order Processing</h1>
<?php
//Get the input.
$frisbees = $_POST['frisbees'];
$giant_chew_ropes = $_POST['giant_chew_ropes'];
//Validate input.
$error_message_frisbees = check_number_ordered($frisbees, 15);
if ( $error_message_frisbees != '' ) {
  print "<p>Frisbees: $error_message_frisbees";
}
$error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);
if ( $error_message_giant_chew_ropes != '' ) {
  print "<p>Giant chew ropes: $error_message_giant_chew_ropes";
}
//Any error made?
if ( $error_message_frisbees != '' || $error_message_giant_chew_ropes != '') {
  print '<p>Please click the Back button on your browser and try again.</p>';
}
else {
  //Input OK, show the order.
  //Compute total.
  $frisbees_total = $frisbees * 8.95;
  $giant_chew_ropes_total = $giant_chew_ropes * 12.95;
  $order_total = $frisbees_total + $giant_chew_ropes_total;
  //Output.
  ?>
  <p>Thank you for your order.</p>
  ...

Figure 6. Calling the function

Line 14 is:

$error_message_frisbees = check_number_ordered($frisbees, 15);

This calls the function for the frisbees, and puts the return value in $error_message_frisbees. Line 18 is:

$error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);

This does the same thing for giant chew ropes. It puts the function’s return value in a different variable, $error_message_giant_chew_ropes.

As you can see, we’ve used the same validation function both times. If we had, say, 23 products on the page, we’d call check_number_ordered() 23 times. But we’d only write it once. If we needed to change the code, we’d only need to change one thing, and all the checks on the page would change.

Here are lines 15 to 17 in Figure 6:

if ( $error_message_frisbees != '' ) {
   print "<p>Frisbees: $error_message_frisbees";
}

This checks to see what the function sent back for frisbees. If it sent back an empty string (''), there was no error. If the function sent back something else, there was an error. Line 16 shows it.

The same check is done for $error_message_giant_chew_ropes is lines 18 to 21.

Now, if there was an error, any error at all, we want to ask the user to go back to the order page. It doesn’t matter which field the error was in. Could be with frisbees, or giant ropes, or both.

Look at lines 23 to 25:

if ( $error_message_frisbees != '' || $error_message_giant_chew_ropes != '') {
   print '<p>Please click the Back button on your browser and try again.</p>';
}

Line 23 says “if $error_message_frisbee is not empty, or $error_message_giant_chew_ropes is not empty, then ask the user to go back.”

This is what we want. If there is any error at all, go back to the order page. We only show the order output if there were no errors.

Summary

You learned:

  • How to write a PHP validation function.
  • How to call it.

What now?

All of our examples use two pages:

  • An HTML page with a form for input.
  • A PHP page to validate and process the data.

Webers often use one page for both input and server-side validation. Let’s see how that’s done.

Form and PHP validation on one page

Where are we?

You know how to get data from a form, and validate it. You know how to use a validation function. Let’s improve the way that’s done. Again.

This lesson’s goals

By the end of this lesson, you should:

  • Know how to have the HTML for the form and the PHP validation code on the same page.
  • Know how to collect error messages in a single variable, rather than treating them all separately.

The form/processor pattern

Up to now, we’ve separated the page with the input form from the page that does validation and processing. Like this:

Pattern so far

Figure 1. Pattern so far

The .hmtl page has a <form>. The action attribute of the form has the name of the processing page. When the user clicks the submit button, the form data is sent to the processing page.

The processing page validates the data. If there is a problem, it shows a message to the user, and suggests going back to the order form again.

If all the form data is OK, the order is processed. This means telling the shipping department to send the order, processing payment, and other businessy things.

A new pattern

But there’s another way of doing things. The new pattern is to put both the form and the server-side PHP validation code on the same page.

The strangest thing about this is that the page with the form submits data to itself.

A new pattern

Figure 2. A new pattern

This is helpful when you do both client-side and server-side validation, and want to show both types of errors to the user in the same way. The new pattern lets you be more consistent in the way you show errors to users.

The key to this is that the page works in two modes:

  • New input mode: The user wants to fill in the form with some new data.
  • Process input mode: Check data the user has already entered.

The page uses if statements to act differently, depending on which mode it’s in.

What are these “modes” you speak of?

Let’s be sure we know what I mean by “modes” here. You’re used to thinking about modes that are built in to devices. For example, you might have an SUV that works in two-wheel or four-wheel drive. Two different modes. You use a switch (or whatever) to choose the mode you want.

The engineers who designed the vehicle built those modes into it.

Another example is the camera in your cell phone. It can work in still photo mode. Or it can work in video mode, where it shoots movies. You choose the modes you want.

The engineers who designed the cell phone built those modes into it.

But when you write PHP, you are the engineer. You get to create modes yourself. You decide:

  • What the modes are
  • What each one does
  • How each mode is activated

You do this by the way you write your code. There is no special “mode” statement in PHP. The modes are created by the way you put together if statements and other things.

Let’s see how all this works.

What the user sees

Here’s an empty order form, from order.php.

Empty order form

Figure 3. Empty order form

Suppose the user enters some bad data into order.php:

Bad data

Figure 4. Bad data

The form data is sent from order.php to order.php; the page sends the form data to itself. Here is what it shows:

Error messages

Figure 5. Error messages

Now, what if the user enters valid data?

Valid data

Figure 6. Valid data

Here is the result:

Order confirmation

Figure 7. Order confirmation

You can try it. Enter different combinations of good and bad data into the fields, and see what you get.

Some code

Here’s order.php.

<?php
if ( $_POST ) {
  //There is order data to validate.
  require 'validation-library.inc';
    //Warning: Extra characters at the end of validation-library.inc
    //will break this page!
  //Get the order amounts.
  $frisbees = $_POST['frisbees'];
  $giant_chew_ropes = $_POST['giant_chew_ropes'];
  //Validate input.
  $error_message_frisbees = check_number_ordered($frisbees, 15);
  $error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);
  //Jump if no errors.
  if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
    $destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";
    header("Location:$destination_url");
    exit();
  }
}?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Order Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <h1>Order Form</h1>
    <?php
    //Any error messages to display?
    if ( $error_message_frisbees != '' ) {
      print "<p>Frisbees: $error_message_frisbees</p>";
    }
    if ( $error_message_giant_chew_ropes != '' ) {
      print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
    }
    ?>
    <form method="post" action="order.php">
      <p>
        Frisbees:
        <input type="text" name="frisbees" size="3"
        <?php
        if ( $_POST['frisbees'] ) {
          print ' value="' . $_POST['frisbees'] . '"';
        }
        ?>
        >
        at $8.95 each
      </p>
      <p>
        Giant chew ropes:
        <input type="text" name="giant_chew_ropes" size="3"
        <?php
        if ( $_POST['giant_chew_ropes'] ) {
          print ' value="' . $_POST['giant_chew_ropes'] . '"';
        }
        ?>
        >
        at $12.95 each
      </p>
      <p>
        <button type="submit">Order</button>
      </p>
    </form>
  </body>
</html>

Figure 8. order.php

Line 2:

if ( $_POST ) {

checks whether there is any form data coming in. If there is no post data, then $_POST will be empty, which, in PHP, is like bring false.

This is the mode check, if you want to think of it that way. If there is post data, we enter “check input” mode. Otherwise, we’re in “empty form” mode.

We created a validation function on the previous page. We’ll put it in a separate file, and bring it in like this (lines 4):

require 'validation-library.inc';

We’ll talk about the warning in lines 5 and 6 later.

Lines 8 and 9 get the form data into variables, as usual. Here’s one of the lines:

$frisbees = $_POST['frisbees'];

As before, the function check_number_ordered() checks the value passed in, and returns either an error message or a blank string (line 11):

$error_message_frisbees = check_number_ordered($frisbees, 15);

The next step is to check whether there are any errors. There were no errors if both the frisbees value and the giant ropes value were OK.

Here is the code:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
  $destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";
  header("Location:$destination_url");
  exit();
}

Part of Figure 8 (again). order.php

Line 14 has the test. Remember that the validation function check_number_ordered() returns an empty string. So that if both $error_message_frisbees and $error_message_giant_chew_ropes are empty strings, there were no errors.

If the test is true, we need to tell the browser to jump to the order processing page (order.php). To jump to another page, send the HTTP header location, like this:

header('location: Destination ');

So to jump to the order processing page (line 16):

header('location: order.php');

But we also need to send the order data to order.php. How?

By doing a pretend get.

Renata
Renata

Huh?

Remember that there are two ways to send form data:

<form method="get">

and

<form method="post">

It’s the get method we care about here.

The get method attaches form data to the URL, like this:

order.php?frisbees=2&giant_chew_ropes=4

The destination page – order.php – can retrieve the data like this:

$frisbees = $_GET['frisbees'];
$giant_chew_ropes = $_GET['giant_chew_ropes'];

OK, so how to send the data? We make a pretend get by attaching data to the URL ourselves, rather than having a form do it. Like this:

$destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";

Remember that PHP replaces variables with their values in strings with double quotes (”). So if $frisbees had 2 in it, and $giant_chew_ropes had 4 in it, we would get:

$destination_url = "process.php?frisbees=2&giant_chew_ropes=4";

This is the same URL that a form with method="get" would produce. W00f!

Renata
Renata

Cool!

Once we have the URL, we can send it to the browser:

header("Location:$destination_url");

There is no point processing the rest of the page, so the next line is:

exit();

This tells the PHP interpreter to stop processing the page immediately, and return whatever it has so far to the Web server. All that it has is the header. The Web server returns that to the browser. The browser jumps to process.php, sending the order along for the ride.

Handling errors

If there are errors, we want this:

Error messages

Figure 5 (again). Error messages

We need to do three things:

  • Show error messages.
  • Show the form again.
  • Fill in the form’s values with what the user had typed.

Here’s how the error messages are shown.

<h1>Order Form</h1>
<?php
//Any error messages to display?
if ( $error_message_frisbees != '' ) {
  print "<p>Frisbees: $error_message_frisbees</p>";
}
if ( $error_message_giant_chew_ropes != '' ) {
  print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
}
?>

Part of Figure 8 (again). order.php

Now the second thing, showing the form. We use the usual HTML tags: <form>, <input>, etc.

The third thing we need to do is restore the data the user had typed into the fields. Whether the data was valid or not.

CC
CC

Why put data the user typed back into the input fields? Especially when some of it is invalid.

Two reasons.

  • There are two fields, and one of them might be correct. We don’t want the user to have to retype that data, just because the other field was bad.
  • The user might have typed several correct characters into a field, with just one of them wrong. It would be annoying if everything in the field just vanished.

So how to do it? You need to know about another attribute of the <input> tag: value. It lets you set the contents of a form field in HTML, without the user having to type anything. For example, here’s some HTML:

<input type="text" value="Dogs rock!">

This renders as:

<code>value</code> attribute

Figure 9. value attribute

You can try it. Notice that text appears in the field, without your typing anything.

Here is some code for our order form:

Frisbees:
<input type="text" name="frisbees" size="3"
<?php
if ( $_POST['frisbees'] ) {
  print ' value="' . $_POST['frisbees'] . '"';
}
?>
>

Part of Figure 8 (again). order.php

If $_POST['frisbees'] has, say, 7 in it, the code will create the HTML:

<input type="text" name="frisbees" size="3" value="7">

That’s the end of the page. W00f!

Improving error message collection

Here’s the error checking and reporting code.

//Validate input.
$error_message_frisbees = check_number_ordered($frisbees, 15);
$error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);
//Jump if no errors.
if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
...
//Any error messages to display?
if ( $error_message_frisbees != '' ) {
  print "<p>Frisbees: $error_message_frisbees</p>";
}
if ( $error_message_giant_chew_ropes != '' ) {
  print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
}

Part of Figure 8 (again). order.php

In this code, we have a different error message variable for each product. That’s fine, because we have only two products.

What if we had more than two products? For each one, we’d need to create a new variable. And Line 14 would get more complex. And we’d need to remember to add another if statement to report the error.

Here’s a better way of doing it.

//Validate input.
$messages = ''; //Variable to accumulate messages.
$error_message = check_number_ordered($frisbees, 15);
if ( $error_message != '' ) {
  $messages .= "<p>Frisbees: $error_message</p>";
}
$error_message = check_number_ordered($giant_chew_ropes, 10);
if ( $error_message != '' ) {
  $messages .= "<p>Giant chew ropes: $error_message</p>";
}
//Jump if no errors.
if ( $messages == '' ) {
...
<h1>Order Form</h1>
<?php
//Any error messages to display?
if ( $messages != '' ) {
  print $messages;
}
?>

Figure 10. Improved error reporting

Line 11 creates a variable called $messages. It’s used to collect error messages. When a new error is detected, some text is added to this variable.

Line 12 checks the frisbees:

$error_message = check_number_ordered($frisbees, 15);

Remember that check_number_ordered() returns an empty string if $frisbees is OK, otherwise it returns an error message.

Here’s the next bit of code:

if ( $error_message != '' ) {
   $messages .= "<p>Frisbees: $error_message</p>";
}

.= is a shortcut operator. Remember that . means string concatenation, so:

'this' . 'that'

is:

thisthat

The line:

$x .= $y;

is a shortcut for:

$x = $x . $y;

So .= appends something to an existing variable.

This line:

$messages .= "<p>Frisbees: $error_message</p>";

creates an HTML paragraph tag with an error message in it, and adds it to whatever is already in $messages.

The next few lines are:

$error_message = check_number_ordered($giant_chew_ropes, 10);
if ( $error_message != '' ) {
   $messages .= "<p>Giant chew ropes: $error_message</p>";
}

If there is a problem with $giant_chew_ropes, an error message gets added to whatever is already in $messages.

If more products are added to the page, we can just repeat these few lines as needed. So if, for example, we started selling dog collars, we would have:

$error_message = check_number_ordered($dog_collars, 8);
if ( $error_message != '' ) {
   $messages .= "<p>Dog collars: $error_message</p>";
}

There is no need to create another variable for a dog collar error message. If there is an error, just add the new message to the other messages.

After checking each individual form field, we need an if statement to decide whether to jump to the order processing page. The browser should jump only if there were no errors.

Here’s what we had in the original code:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {

Each time we add a new product, we’d have to change this line. For example, if we had five products:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '' && $error_message_dog_collars == '' && $error_message_squeaky_balls == '' && $error_message_hide_bone == '' ) {

Ack! What a pain!

But with the new approach, we don’t need to do that. All error messages are added to the variable $messages. So if we want to check whether there have been any errors, we can just check that one variable (line 21 in Figure 10):

if ( $messages == '' ) {

Ten products? Twenty? It doesn’t matter. This line will never change.

When it comes to reporting errors to the user, we have this code:

if ( $messages != '' ) {
   print $messages;
}

Again, it doesn’t matter how many products there are, or how many potential error messages. Our code has collected all the error messages in $messages.

This is another example of how Webers think about productivity. Web pages change. Products are added and removed. This approach – accumulating error messages in a variable – makes it easier to make those changes.

Summary

In this lesson, you learned:

  • How to have the HTML for the form and the PHP validation code on the same page.
  • How to collect error messages in a single variable, rather than treating them all separately.

What now?

Here’s the error message display again:

Error messages

Figure 5 (again). Error messages

That’s, well, yucky. Let’s improve the user’s experience.

Better client-side error display

This lesson’s goals

By the end of this lesson, you should:

  • Know how to report errors in a professional way.
  • Understand how page-wide, global error messages help the user.
  • Understand how JavaScript functions make error reporting easier to program.

The goal

In the last couple of lessons, we improved the server-side error processing. We came up with something like this:

Ugly error messages

Figure 1. Ugly error messages

This is, well, blah. Let’s improve it. We want the order form to look like this:

Empty order form

Figure 2. Empty order form

Suppose there are errors:

Order form with errors

Figure 3. Order form with errors

Let’s show error messages like this:

Error messages

Figure 4. Error messages

Try it, and you’ll see the error messages are animated.

Field and global error messages

Each field has its own place for error messages, right below the field. In addition, at the top of the form, there’s what I am calling a “global error message.” It shows that there is some error on the page.

Why have a global error message? Two reasons.

Some errors aren’t about a particular field

Let’s change the way a blank field is interpreted. If a user leaves a field blank, we’ll assume the user doesn’t want to order any of the product. We won’t make the user enter zero into the field.

But this creates a problem. Suppose the user leaves all the fields blank and clicks the order button. There’s an error; nothing was ordered, so clicking the order button makes no sense. But the error isn’t associated with any one field.

Having a global error message gives us a place to put an error message like this:

Nothing ordered

Figure 5. Nothing ordered

Drawing attention

You might have a long form with, say, 12 fields. This gives you a long page. The user won’t be able to see all of the fields at the same time.

Browser window

Figure 6. Browser window

The user has to scroll to see the entire page.

The global error message makes it easier for the user to see whether there are any errors. The small error icon:

Error icon

helps the user scan down the page and find information about specific errors.

Let’s look at the client side error reporting: the HTML, CSS, and JavaScript. We’ll combine it with PHP in the next lesson.

HTML for the error messages

Let’s look at the HTML code for the global error message. That’s the message at the top of the page, telling the user whether there were any errors at all. Remember that it should look like this when displayed:

Global error message

Figure 7. Global error message

Here’s the HTML:

<p id="global_error_message_container" class="message_container">
  <img src="error.png" alt="Error">
  <span id="global_error_message"/>
</p>

Figure 8. HTML for the global error message

There’s a <span> inside a <p>. The <span> is used to show the text of the error message. The <p> is a container for both the <span> and an error icon. Showing and hiding the <p> will affect both the icon and the message, treating them as a single unit.

Notice how I have named them. The <span> has an id of global_error_message. The <p> has an id of global_error_message_container. I’ve used that convention throughout the page. The id of the outer element ends in _container.

The icon came from the famfamfam silk icon set. It’s one of the most complete icon sets you can find.

That was the global error message. The HTML for each field’s error message is similar. Here’s one of them.

<p>
  <input type="text" name="frisbees" id="frisbees" size="3">
  Frisbees ($8.95 each)<br>
  <span id="frisbees_message_container" class="message_container">
    <img src="error.png" alt="Error">
    <span id="frisbees_message"/>
  </span>
</p>

Figure 9. HTML for the frisbee message

The error message starts on line 136. Again, there’s an error message element and a container element. This time, the container is a <span>.

The product error messages have an extra naming convention. For product x, the elements are called x_message and x_message_container. So for frisbees, the ids are frisbees_message and frisbees_message_container.

CC
CC

Why do you use these naming rules?

Kieran
Kieran

Because it makes programming easier. You’ll see why later.

JavaScript

Here’s the code that’s executed when the user clicks the Order button.

$("#order_button").click(function() {
  //Set flag showing everything is OK.
  var data_ok = true;

  //Check the frisbees.
  var frisbees = $("#frisbees").val();
  var frisbees_message = check_order_value(frisbees);
  if ( frisbees_message != '' ) {
    data_ok = false;
    show_product_error_message(frisbees_message, 'frisbees');
  }
  else {
    hide_error_message('frisbees');
  }

  //Check the giant chew ropes.
  var giant_chew_ropes = $("#giant_chew_ropes").val();
  var giant_chew_ropes_message = check_order_value(giant_chew_ropes);
  if ( giant_chew_ropes_message != '' ) {
    data_ok = false;
    show_product_error_message(giant_chew_ropes_message, 'giant_chew_ropes');
  }
  else {
    hide_error_message('giant_chew_ropes');
  }

  //Check the squeaky balls.
  var squeaky_balls = $("#squeaky_balls").val();
  var squeaky_balls_message = check_order_value(squeaky_balls);
  if ( squeaky_balls_message != '' ) {
    data_ok = false;
    show_product_error_message(squeaky_balls_message, 'squeaky_balls');
  }
  else {
    hide_error_message('squeaky_balls');
  }

  //Make sure something was ordered.
  if ( frisbees + giant_chew_ropes + squeaky_balls == 0 ) {
    show_global_error_message("Sorry, you must order at least one item.");
    data_ok = false;
  }
  
  if ( data_ok ) {
    hide_global_error_message();
    alert('Everything is OK... so far.')
  }
});

Figure 10. JavaScript

Line 25:

var data_ok = true;

creates a variable that keeps track of whether the data is OK, that is, whether there has been an error. When we get to the end of the validation, we can check this variable to know whether there have been any errors.

Variables like this are often called “flags.” They are usually either true or false, like a physical flag being up or down.

Flag up Flag up, data OK. Variable data_ok is true.

Flag down Flag down, data not OK. Variable data_ok is false.

When the flag is up (the data is OK), we want to do one thing. When the flag is down (there has been an error), we want to do another thing.

Look at lines 28 and 29:

var frisbees = $("#frisbees").val();
var frisbees_message = check_order_value(frisbees);

The first one gets the value the user typed into the frisbee field and puts it into the variable frisbees. Line 29 calls the check_order_value() function, sending in frisbees.

check_order_value() is a JavaScript version of the PHP validation function we saw earlier. It does the same thing; returns either an error message, or an empty string if the the value is OK.

Here is the code:

//Check whether an order value is valid.
//Input
//  value_to_check: the value to check.
//Return: An error message, or empty string if no error.
function check_order_value(value_to_check) {
  if ( isNaN(value_to_check) ) {
    return 'Please enter a number';
  }
  if ( value_to_check != Math.round(value_to_check) ){
    return 'Please enter whole numbers only';
  }
  if ( value_to_check < 0 ){
    return 'Please enter positive numbers only';
  }
  return '';
}

Figure 11. check_order_value()

Line 80 contains something new.:

if ( value_to_check != Math.round(value_to_check) ){

This tests whether value_to_check is an integer, that is, a whole number. Math.round() rounds a number up or down. So:

Math.round(1.4) is 1

Math.round(1.7) is 2

Math.round(1) is 1

If you round a number that is already a whole number, you get the same number you started with. That is why the test works.

You can try round().

Back the to button pressing script. Here’s some more of it.

//Check the frisbees.
var frisbees = $("#frisbees").val();
var frisbees_message = check_order_value(frisbees);
if ( frisbees_message != '' ) {
  data_ok = false;
  show_product_error_message(frisbees_message, 'frisbees');
}
else {
  hide_error_message('frisbees');
}

Part of Figure 10 (again). JavaScript

Line 29 puts either an error message or an empty string in frisbees_message.

If frisbees_message isn’t empty (line 30), the flag gets set to false (line 31), so we can know later that there’s been a problem. Then this is run:

show_product_error_message(frisbees_message, 'frisbees');

This shows the message in the frisbees error spot. It might end up looking like this:

Frisbee error

Figure 11. Frisbee error

I want the show_product_error_message() function to work for every product. That’s why I pass in the product name (frisbees in this case), so the function will know where to show the error message.

Here’s what show_product_error_message() does, in pseudocode.

Set the product error text.
Show the text.
Set the global error text.
Show it.

Here’s the code:

//Show an error message for a product.
//Input
//  message: The message to show.
//  product_name: The product name the message is about.
//Return: nothing.
function show_product_error_message(message, product_name) {
  //Show the error message in the product's message area.
  $('#' + product_name + '_message').text(message);
  if ( $('#' + product_name + '_message_container').is(':hidden') ) {
    $('#' + product_name + '_message_container').show('medium');
  }
  //Show the global error message at the top of the page.
  show_global_error_message("Sorry, I can't process this order.");
}

Figure 13. show_product_error_message()

Look at line 96:

$('#' + product_name + '_message').text(message);

product_name is something like frisbee. So line 96 becomes:

$('#frisbee_message').text(message);

This matches the id in the HTML element:

<p>
  <input type="text" name="frisbees" id="frisbees" size="3">
  Frisbees ($8.95 each)<br>
  <span id="frisbees_message_container" class="message_container">
    <img src="error.png" alt="Error">
    <span id="frisbees_message"/>
  </span>
</p>

Figure 9 (again). HTML for the frisbee message

There it is, in line 138.

Renata
Renata

Aha! So that’s why you used those naming rules!

Kieran
Kieran

Yes! That’s it.

Here are the next lines in show_product_error_message() (Figure 12):

if ( $('#' + product_name + '_message_container').is(':hidden') ) {
  $('#' + product_name + '_message_container').show('medium');
}

Part of Figure 12 (again). show_product_error_message()

This will detect whether the message’s container is hidden, and, if it is, show it.

Remember the pseudocode.

Set the product error text.
Show the text.
Set the global error text.
Show it.

We’ve done the first two. How about the last two? Like this:

show_global_error_message("Sorry, I can't process this order.")

That’s right, another function.

//Show a global error message. It applies to the entire
//  page, not just one product.
//Input
//  message: The message to show.
//Return: nothing.
function show_global_error_message(message) {
  $("#global_error_message").text(message);
  if ($('#global_error_message_container' ).is(':hidden') ) {
    $('#global_error_message_container').show('medium');
  }
}

Figure 13. show_global_error_message()

It’s much like show_product_error_message(), but without the product name part.

Let’s look at the last part of a field’s validation:

//Check the frisbees.
var frisbees = $("#frisbees").val();
var frisbees_message = check_order_value(frisbees);
if ( frisbees_message != '' ) {
  data_ok = false;
  show_product_error_message(frisbees_message, 'frisbees');
}
else {
  hide_error_message('frisbees');
}

Part of Figure 10 (again). JavaScript

If there’s no error, then line 35 runs:

hide_error_message('frisbees');

Here’s the function:

//Hide a product error message.
//Input
//  product_name: The product name the message is about.
//Return: nothing.
function hide_error_message(product_name) {
  $('#' + product_name + '_message_container').hide('medium');
}

Figure 14. hide_error_message()

Send it a product_name, and it hides the error message for that product.

There is one more check we have to do. What if the user orders none of every product?

//Make sure something was ordered.
if ( frisbees + giant_chew_ropes + squeaky_balls == 0 ) {
  show_global_error_message("Sorry, you must order at least one item.");
  data_ok = false;
}

Figure 15. Was anything ordered?

What if there are no errors?

if ( data_ok ) {
  hide_global_error_message();
  alert('Everything is OK... so far.')
}

Figure 16. Nothing wrong

hide_global_error_message() does what you think. Here it is:

//Hide global error message.
//Return: nothing.
function hide_global_error_message() {
  if ($('#global_error_message_container' ).is(':visible') ) {
  $('#global_error_message_container').hide('medium');
  }
}

Figure 17. Hide the global error message

hide() only runs if the error message is visible.

Nothing else happens, except for an alert(). We’ll add the PHP in the next lesson.

The entire page

Here’s all the code together. Run through it again in your mind, so you see how it all fits together.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Order Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
      .message_container {
        display: none;
        font-weight: bold;
        color: red;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#global_error_message_container").hide();
        $("#frisbees").focus();
        $("#order_button").click(function() {
          //Set flag showing everything is OK.
          var data_ok = true;

          //Check the frisbees.
          var frisbees = $("#frisbees").val();
          var frisbees_message = check_order_value(frisbees);
          if ( frisbees_message != '' ) {
            data_ok = false;
            show_product_error_message(frisbees_message, 'frisbees');
          }
          else {
            hide_error_message('frisbees');
          }

          //Check the giant chew ropes.
          var giant_chew_ropes = $("#giant_chew_ropes").val();
          var giant_chew_ropes_message = check_order_value(giant_chew_ropes);
          if ( giant_chew_ropes_message != '' ) {
            data_ok = false;
            show_product_error_message(giant_chew_ropes_message, 'giant_chew_ropes');
          }
          else {
            hide_error_message('giant_chew_ropes');
          }

          //Check the squeaky balls.
          var squeaky_balls = $("#squeaky_balls").val();
          var squeaky_balls_message = check_order_value(squeaky_balls);
          if ( squeaky_balls_message != '' ) {
            data_ok = false;
            show_product_error_message(squeaky_balls_message, 'squeaky_balls');
          }
          else {
            hide_error_message('squeaky_balls');
          }

          //Make sure something was ordered.
          if ( frisbees + giant_chew_ropes + squeaky_balls == 0 ) {
            show_global_error_message("Sorry, you must order at least one item.");
            data_ok = false;
          }
          
          if ( data_ok ) {
            hide_global_error_message();
            alert('Everything is OK... so far.')
          }
        });
      });

      //Check whether an order value is valid.
      //Input
      //  value_to_check: the value to check.
      //Return: An error message, or empty string if no error.
      function check_order_value(value_to_check) {
        if ( isNaN(value_to_check) ) {
          return 'Please enter a number';
        }
        if ( value_to_check != Math.round(value_to_check) ){
          return 'Please enter whole numbers only';
        }
        if ( value_to_check < 0 ){
          return 'Please enter positive numbers only';
        }
        return '';
      }

      //Show an error message for a product.
      //Input
      //  message: The message to show.
      //  product_name: The product name the message is about.
      //Return: nothing.
      function show_product_error_message(message, product_name) {
        //Show the error message in the product's message area.
        $('#' + product_name + '_message').text(message);
        if ( $('#' + product_name + '_message_container').is(':hidden') ) {
          $('#' + product_name + '_message_container').show('medium');
        }
        //Show the global error message at the top of the page.
        show_global_error_message("Sorry, I can't process this order.");
      }

      //Show a global error message. It applies to the entire
      //  page, not just one product.
      //Input
      //  message: The message to show.
      //Return: nothing.
      function show_global_error_message(message) {
        $("#global_error_message").text(message);
        if ($('#global_error_message_container' ).is(':hidden') ) {
          $('#global_error_message_container').show('medium');
        }
      }

      //Hide a product error message.
      //Input
      //  product_name: The product name the message is about.
      //Return: nothing.
      function hide_error_message(product_name) {
        $('#' + product_name + '_message_container').hide('medium');
      }

      //Hide global error message.
      //Return: nothing.
      function hide_global_error_message() {
        if ($('#global_error_message_container' ).is(':visible') ) {
          $('#global_error_message_container').hide('medium');
        }
      }
    </script>
  </head>
  <body>
    <h1>Order Form</h1>
    <p>What would you like?</p>
    <p id="global_error_message_container" class="message_container">
      <img src="error.png" alt="Error">
      <span id="global_error_message"/>
    </p>
    <p>
      <input type="text" name="frisbees" id="frisbees" size="3">
      Frisbees ($8.95 each)<br>
      <span id="frisbees_message_container" class="message_container">
        <img src="error.png" alt="Error">
        <span id="frisbees_message"/>
      </span>
    </p>
    <p>
      <input type="text" name="giant_chew_ropes" id="giant_chew_ropes" size="3">
      Giant chew ropes ($12.95 each)<br>
      <span id="giant_chew_ropes_message_container" class="message_container">
        <img src="error.png" alt="Error">
        <span id="giant_chew_ropes_message"/>
      </span>
    </p>
    <p>
      <input type="text" name="squeaky_balls" id="squeaky_balls" size="3">
      Squeaky balls to chase ($1.95 each)<br>
      <span id="squeaky_balls_message_container" class="message_container">
        <img src="error.png" alt="Error">
        <span id="squeaky_balls_message"/>
      </span>
    </p>
    <p>
      <button id="order_button" type="button">Order</button>
    </p>
  </body>
</html>

Figure 18. The entire page

Make sure you try the page. Type in invalid data, and see what happens.

Exercise: Sign-up form

Make a sign-up page for a Web service. It starts out like this:

Empty form

Figure 1. Empty form

All fields must be completed. Suppose the user does this:

Empty field

Figure 2. An empty field

Here is the result:

Error messages

Figure 3. Error messages

Hint: Make a function called check_value() that returns either an error message or an empty string. The only error is an empty field.

You can see my solution, but don’t look at the source code until you do it yourself first!

Upload your solution to your server. Enter the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

You learned:

  • How to report errors in a professional way, with colors, icons, and animated effects.
  • How page-wide, global error messages help the user.
  • How JavaScript functions make error reporting easier to program.

What now?

Let’s put everything together. We’ll do form validation that:

  • Has professional-looking error messages.
  • Shows client-side and server-side error messages in the same way. There’s no need to bother the user with the difference.

Complete validation

Thanks to Miles Thompson for his help with this lesson.

Where are we?

OK, let’s bring it all together.

  • We’ll use the nice error reporting we saw in the previous lesson.
  • We’ll use JavaScript to check for client-side errors (like non-numbers in numeric fields), and PHP to check for errors on the server side.
  • Both client-side and server-side errors will be reported in the same way.

What the user sees

Here’s how things start:

Empty form

Figure 1. Empty form

Client-side checking makes error messages like this:

Error messages

Figure 2. Error messages

What if the user wants to order more than we have on hand?

Too many

Figure 3. Too many

The error message in Figure 3 tells the user how many we have on hand.

Again, this may not be good business practice, but it’s good for learning about PHP.

Try the page. Try making various kinds of errors. You can also download the files.

The overall pattern

The overall processing pattern is the same as before:

Overall pattern

Figure 4. Overall pattern

The form page passes data to itself for validation. If everything is OK, the page passes the data to a processing page.

There are a few improvements over the client-side only version on the previous page.

  • Empty fields are handled better.
  • Check on-hand inventory.
  • Server-side errors (like ordering more than are on hand) are reported to the user with the client-side error reporting mechanism (the animated messages with the icon).

The complete order.php code

Here’s the complete code. We’ll talk about the changes in the next section.

<?php
if ( $_POST ) {
  //There is order data to process.
  require 'validation-library.inc';
  //Get the order amounts.
  $frisbees = $_POST['frisbees'];
  if ( $frisbees == '' ) {
    $frisbees = 0;
  }
  $giant_chew_ropes = $_POST['giant_chew_ropes'];
  if ( $giant_chew_ropes == '' ) {
    $giant_chew_ropes = 0;
  }
  $squeaky_balls = $_POST['squeaky_balls'];
  if ( !isset($squeaky_balls) || $squeaky_balls == '' ) {
    $squeaky_balls = 0;
  }

  //Flag for data OK.
  $data_ok = true;
  //JavaScript to run after page load.
  $js = '';

  //Check frisbees.
  $frisbees_message = check_number_ordered($frisbees);
  if ( $frisbees_message != '' ) {
    $data_ok = false;
    $js .= 'show_product_error_message("'.$frisbees_message . '", "frisbees");';
  }

  //Check giant chew ropes.
  $giant_chew_ropes_message = check_number_ordered($giant_chew_ropes);
  if ( $giant_chew_ropes_message != '' ) {
    $data_ok = false;
    $js .= 'show_product_error_message("'.$giant_chew_ropes_message . '", "giant_chew_ropes");';
  }

  //Check squeaky balls.
  $squeaky_balls_message = check_number_ordered($squeaky_balls);
  if ( $squeaky_balls_message != '' ) {
    $data_ok = false;
    $js .= 'show_product_error_message("'.$squeaky_balls_message . '", "squeaky_balls");';
  }

  //Check that at least one item was ordered.
  if ( $frisbees + $giant_chew_ropes + $squeaky_balls == 0 ) {
    $data_ok = false;
    $js .= 'show_product_global_error_message("Sorry, you must order at least one item.");';
  }

  //Check number on hand.
  //Normally number on hand would be loaded from a database.
  //For simplicity, I just made up some numbers.
  $frisbees_on_hand = 5;
  $giant_chew_ropes_on_hand = 4;
  $squeaky_balls_on_hand = 8;
  if ( $frisbees > $frisbees_on_hand ) {
    $data_ok = false;
    $js .= "show_product_error_message('Sorry, we only have $frisbees_on_hand on hand.', 'frisbees');";
  }
  if ( $giant_chew_ropes > $giant_chew_ropes_on_hand ) {
    $data_ok = false;
    $js .= "show_product_error_message('Sorry, we only have $giant_chew_ropes_on_hand on hand.', 'giant_chew_ropes');";
  }
  if ( $squeaky_balls > $squeaky_balls_on_hand ) {
    $data_ok = false;
    $js .= "show_product_error_message('Sorry, we only have $squeaky_balls_on_hand on hand.', 'squeaky_balls');";
  }

  //Is everything OK?
  if ( $data_ok ) {
    //Process the order.
    $destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes&squeaky_balls=$squeaky_balls";
    header("Location:" . $destination_url);
    exit();
  }
}
?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Order Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style type="text/css">
      body {
        font-family: Verdana, sans-serif;
        font-size: 14px;
        background-color: #FFFFE0;
      }
      .message_container {
        display: none;
        font-weight: bold;
        color: red;
      }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#global_error_message_container").hide();
        $("#frisbees").focus();
        <?php print $js; ?>
        $("#order_form").submit(function() {
          //Set flag showing everything is OK.
          var data_ok = true;

          //Check the frisbees.
          var frisbees = $("#frisbees").val();
          var frisbees_message = check_order_value(frisbees);
          if ( frisbees_message != '' ) {
            data_ok = false;
            show_product_error_message(frisbees_message, 'frisbees');
          }
          else {
            hide_error_message('frisbees');
          }

          //Check the giant chew ropes.
          var giant_chew_ropes = $("#giant_chew_ropes").val();
          var giant_chew_ropes_message = check_order_value(giant_chew_ropes);
          if ( giant_chew_ropes_message != '' ) {
            data_ok = false;
            show_product_error_message(giant_chew_ropes_message, 'giant_chew_ropes');
          }
          else {
            hide_error_message('giant_chew_ropes');
          }

          //Check the squeaky balls.
          var squeaky_balls = $("#squeaky_balls").val();
          var squeaky_balls_message = check_order_value(squeaky_balls);
          if ( squeaky_balls_message != '' ) {
            data_ok = false;
            show_product_error_message(squeaky_balls_message, 'squeaky_balls');
          }
          else {
            hide_error_message('squeaky_balls');
          }

          //Make sure something was ordered.
          if ( frisbees + giant_chew_ropes + squeaky_balls == 0 ) {
            show_global_error_message("Sorry, you must order at least one item.");
            data_ok = false;
          }
          return data_ok;
        });
      });

      //Check whether an order value is valid.
      //Input
      //  value_to_check: the value to check.
      //Return: An error message, or empty string if no error.
      function check_order_value(value_to_check) {
        if ( isNaN(value_to_check) ) {
          return 'Please enter a number';
        }
        if ( value_to_check != Math.round(value_to_check) ){
          return 'Please enter whole numbers only';
        }
        if ( value_to_check < 0 ){
          return 'Please enter positive numbers only';
        }
        return '';
      }

      //Show an error message for a product.
      //Input
      //  message: The message to show.
      //  product_name: The product name the message is about.
      //Return: nothing.
      function show_product_error_message(message, product_name) {
        //Show the error message in the product's message area.
        $('#' + product_name + '_message').text(message);
        if ( $('#' + product_name + '_message_container').is(':hidden') ) {
          $('#' + product_name + '_message_container').show('medium');
        }
        //Show the global error message at the top of the page.
        show_global_error_message("Sorry, I can't process this order.");
      }

      //Show a global error message. It applies to the entire
      //  page, not just one product.
      //Input
      //  message: The message to show.
      //Return: nothing.
      function show_global_error_message(message) {
        $("#global_error_message").text(message);
        if ($('#global_error_message_container' ).is(':hidden') ) {
          $('#global_error_message_container').show('medium');
        }
      }

      //Hide a product error message.
      //Input
      //  product_name: The product name the message is about.
      //Return: nothing.
      function hide_error_message(product_name) {
        $('#' + product_name + '_message_container').hide('medium');
      }

    </script>
  </head>
  <body>
    <h1>Order Form</h1>
    <p>What would you like?</p>
    <p id="global_error_message_container" class="message_container">
      <img src="error.png" alt="Error">
      <span id="global_error_message"/>
    </p>
    <form id="order_form" method="post" action="order.php">
      <p>
        <input type="text" name="frisbees" id="frisbees" size="3"
        <?php
        if ( isset($_POST['frisbees']) ) {
          print ' value="' . $_POST['frisbees'] . '"';
        }
        ?>
        >
        Frisbees ($8.95 each)<br>
        <span id="frisbees_message_container" class="message_container">
          <img src="error.png" alt="Error">
          <span id="frisbees_message"/>
        </span>
      </p>
      <p>
        <input type="text" name="giant_chew_ropes" id="giant_chew_ropes" size="3"
        <?php
        if ( isset($_POST['giant_chew_ropes']) ) {
          print ' value="' . $_POST['giant_chew_ropes'] . '"';
        }
        ?>
        >
        Giant chew ropes ($12.95 each)<br>
        <span id="giant_chew_ropes_message_container" class="message_container">
          <img src="error.png" alt="Error">
          <span id="giant_chew_ropes_message"/>
        </span>
      </p>
      <p>
        <input type="text" name="squeaky_balls" id="squeaky_balls" size="3"
        <?php
        if ( isset($_POST['squeaky_balls']) ) {
          print ' value="' . $_POST['squeaky_balls'] . '"';
        }
        ?>
        >
        Squeaky balls to chase ($1.95 each)<br>
        <span id="squeaky_balls_message_container" class="message_container">
          <img src="error.png" alt="Error">
          <span id="squeaky_balls_message"/>
        </span>
      </p>
      <p>
        <button type="submit">Order</button>
      </p>
    </form>
  </body>
</html>

Figure 5. Complete order.php

Code changes

We’ve seen most of this before. But there are some changes.

Blank is zero

When a field is empty, the program decides the user wants none of that product:

//Get the order amounts.
$frisbees = $_POST['frisbees'];
if ( $frisbees == '' ) {
  $frisbees = 0;
}

Figure 6. When a field is empty

Form submission

On the previous page, where we did client-side only, we didn’t have a <form> tag. We attached validation code to a button:

$(document).ready(function() {
  ...
  $("#order_button").click(function() {
    ...
  });
});
...
<button id="order_button" type="button">Order</button>

Figure 7. Validation without a form

This time, we do have a <form>. We don’t attach the validation code to the click() event of a <button>. We attach it to the submit() event of the <form>.

$(document).ready(function() {
  ...
  $("#order_form").submit(function() {
    ...
  });
});
...
<form id="order_form" method="post" action="order.php">
  ...
  <button type="submit">Order</button>
  ...
</form>

Figure 8. Triggering validation with a form

We need a way to stop data from being submitted if it is invalid. We do this by returning either true or false from the submit() function.

  • If the submit() function returns true, the browser goes ahead and submits the data to the page given in the action property (e.g., action="order.php").
  • If the submit() function returns false, the browser does not send the data. It does nothing.

We can use this as follows:

$("#order_form").submit(function() {
  ...
  var data_ok = true;
  ...
  if ( [there is an error] ) {
    ...
    data_ok = false;
  }
  ...
  return data_ok;
});

Figure 9. Stopping form submission

The variable data_ok is set to true initially. If there is an error, data_ok is set to false. Eventually, the browser gets to the line:

return data_ok;

If there have been no errors, data_ok will still be set to true, the value it started off with. Return it, and the browser will send the data. But if there has been an error, data_ok will be false. Return it, and the browser will not send the data to the server.

Error reporting

Let’s look at the code the reports server-side errors to the user.

By the way, the variable $data_ok is a server-side PHP variable. It has nothing to do with the JavaScript variable data_ok.

//Flag for data OK.
$data_ok = true;
//JavaScript to run after page load.
$js = '';

//Check frisbees.
$frisbees_message = check_number_ordered($frisbees);
if ( $frisbees_message != '' ) {
  $data_ok = false;
  $js .= 'show_product_error_message("'.$frisbees_message . '", "frisbees");';
}

Figure 10. Error reporting

Most of it should be familiar, but there is one new thing. Line 22:

$js = '';

This PHP variable holds JavaScript code that reports errors to the user. Let’s see how it works.

Suppose the user entered “bark!” in the frisbees field. Line 25:

$frisbees_message = check_number_ordered($frisbees);

would put “Please enter a number” in $frisbees_message. Remember, this is PHP code, running on the server.

We want to give this error message to the user. We want it to look the same as the error messages produced by the client-side code.

How? Well, we already have a JavaScript function that will report error messages. We saw it on the previous page. Here it is again:

function show_product_error_message(message, product_name) {
  //Show the error message in the product's message area.
  $('#' + product_name + '_message').text(message);
  if ( $('#' + product_name + '_message_container').is(':hidden') ) {
    $('#' + product_name + '_message_container').show('medium');
  }
  //Show the global error message at the top of the page.
  show_global_error_message("Sorry, I can't process this order.");
}

Figure 11. JavaScript function to show an error message

show_product_error_message() takes two arguments: the error message, and the product name.

So, we already have this function that will report errors. But it’s a JavaScript function. It runs in the browser. We want to call it from PHP code that runs on the server.

How?

Well, how about writing PHP code that writes JavaScript code that calls the function?

Like this (line 28 in Figure 10):

$js .= 'show_product_error_message("'.$frisbees_message . '", "frisbees");';

Suppose $frisbees_message has ‘Please enter a number’ in it. We get the string:

show_product_error_message("Please enter a number", "frisbees");

The string is appended to the PHP variable $js.

We can do something similar for the on-hand check:

if ( $frisbees > $frisbees_on_hand ) {
  $data_ok = false;
  $js .= "show_product_error_message('Sorry, we only have $frisbees_on_hand on hand.', 'frisbees');";
}

Figure 12. Checking inventory on hand

Notice that we have:

$js .=

We keep adding to $js as we find errors.

What do we do with $js? If we could insert it into the page along with the other JavaScript, it would get executed, and the error message(s) would appear. Here’s code that would do it.

<script type="text/javascript">
$(document).ready(function() {
  $("#global_error_message_container").hide();
  $("#frisbees").focus();
  <?php print $js; ?>

Figure 13. Inserting JavaScript error reporting code

Line 100 puts the contents of the PHP variable $js into the output stream. The variable contains some executable JavaScript code. The code is added into the $(document).ready() (starts in line 97), so it runs when the page loads.

To see how this works, try it, and enter large numbers in the form fields:

Too many again

Figure 14. Too many again

When you get a display like this, have a look at the source code of the page. You’ll see something like this:

<script type="text/javascript">
$(document).ready(function() {
  $("#global_error_message_container").hide();
  $("#frisbees").focus();
  show_product_error_message('Sorry, we only have 5 on hand.', 'frisbees');show_product_error_message('Sorry, we only have 4 on hand.', 'giant_chew_ropes');show_product_error_message('Sorry, we only have 8 on hand.', 'squeaky_balls');

Figure 15. Generated code

Line 5 was generated by PHP code. But the browser doesn’t know where it came from. It’s just there, and gets run along with everything else.

CC
CC

I’m confused. The PHP adds JavaScript? Won’t it be part of the page forever, even when the user didn’t make a mistake?

Kieran
Kieran

Remember that order.php is a PHP program, not a static HTML file. order.php can send different HTML and JavaScript to the browser, depending on the circumstances.

So when it inserts JavaScript into the HTML stream, it doesn’t permanently change order.php itself. Just what the user sees that particular time order.php is run.

But this can be hard to understand. There’s a lot going on. Let’s break it down.

How the user experiences errors

We want to give users a smooth experience with the form. That’s what matters in the end: can users easily order products? They don’t care about clients and servers and such. They just care what they see in the browser.

Let’s see how one user, Jeff, experienced the order form. Keep this is mind:

Structure

Figure 16. Structure

Jeff
Jeff
Firefox
Browser
PHP
Server
Looking at site's home page.

Clicks link to go to order form.
   
  Asks server for order.php  
    Starts order.php.

No form data passed (1 in Figure 16).

Generate page to show empty form (3 in Figure 16).

Send HTML and JavaScript to browser.
  Render page from server. Blank form.

Blank form
 
Types "lots" into frisbee field.

Clicks Order button.

Lots
   
  Runs JavaScript validation code.

"lots" is not a valid number.

Reports error.

Lots error

Note: Nothing sent to the server. The browser handles this error itself.
 
Changes "lots" to 400.

Clicks Order button.
   
  Runs JavaScript validation code.

400 is a valid number.

Asks server for order.php

Sends data: frisbees=400
 
    Starts order.php.

Note: This is the same page! order.php sent data to itself.

Form data passed:
  frisbees=400

Note: The first time the browser asked for order.php, it passed no form data. This time is different.

Run validation check.

Only have 5 frisbees.

Generate HTML and JavaScript for page.

Add into $(document).ready():

show_product_error_message(
'Sorry, we only have 5 on hand.', 'frisbees');


Send HTML and JavaScript to browser.
  Render page.

Show error message during page load.

400 error

Note: This error looks the same as the "Invalid number" error. At this point, Jeff has only ever seen order.php. But the server has run order.php twice. The first run created a blank form. The second run created a page with an error message and a form.
 
Erases 400 from frisbees field.

Puts 4 into frisbees field.

Clicks Order button.
   
  Runs JavaScript validation code.

4 is a valid number.

Asks server for order.php

Sends data: frisbees=4

Note: Calling the same page again. Still on order.php.
 
    Starts order.php.

Form data passed:
  frisbees=4

Run validation check.

Data valid.

Generate HTTP response with header:

Location:process.php?

  frisbees=4


Send HTTP response to browser.
  Gets HTTP redirect from server.

Asks server for process.php

Note: Finally! Going to a different page.

Sends data: frisbees=4
 
    Starts process.php.

Form data passed:
  frisbees=4

Run validation check.

Data valid.

Process order. (Send messages to accounting, shipping, etc.)

Generate HTML for order confirmation page.

Send page to browser.
  Render confirmation page.  
W00f!

Prints confirmation page.
   

Try the page, and run through the same steps Jeff did.

An important note:

Jeff’s experience is seamless.

The order page reloads after Jeff enters 400 frisbees and clicks the Order button. But he might not even notice the reload, if the server is fast enough. The browser shows the same color and fonts and headings and fields.

The key to this is that the same program – order.php – creates different HTML and JavaScript, depending on the situation.

Restoring values

One last piece of code to look at. This is from the HTML that makes the order form.

<input type="text" name="frisbees" id="frisbees" size="3"
<?php
if ( isset($_POST['frisbees']) ) {
  print ' value="' . $_POST['frisbees'] . '"';
}
?>
>

Figure 17. <input> field

Look at the source code in your browser again, you’ll see:

<input type="text" name="frisbees" id="frisbees" size="3" value="100">

(I left out some spaces.)

You can see that it puts the value the user typed into the field.

But the other thing is that the <input> tag has both name and id attributes. Why both? The name is needed to send the form data to the server. The id is used by the client-side JavaScript code.

This is by far the most complex page we’ve talked about. To get a better sense of how the whole thing fits together, download the files.

Summary

This lessons shows a complete form validation example.

  • It shows good error messages.
  • Some error checking is on the server side. Some is on the client side.
  • Bother server-side and client-side error messages are reported in the same way. That’s done by writing PHP code that calls JavaScript code.

What now?

Time for more exercises.

Exercises: Checking form data

Exercise: Ordering collars

Create a form like this:

Empty form

Figure 1. Empty form

The user enters a model number and the quantity. Model number must be either 23 or 39. Quantity must be a number greater than zero.

The data is sent to a PHP page for validation. All validation takes place in PHP!

If the data is OK, just show it:

Valid data

Figure 2. Valid data

But suppose there’s an error, like no model number:

No model

Figure 3. No model

Show an error message like this:

Error message

Figure 4. Error message

Here are the error messages your page should generate:

  • Please enter the model number.
  • Sorry, model must be 23 or 39.
  • Please enter the quantity.
  • Please enter a number for quantity.
  • Please enter a quantity more than zero.

You can try my solution.

Upload your solution to your server. Put your URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: New product form

Create a form that lets a user enter information about a new product. (Later, you’ll learn how to add this data to a database. This exercise is just about the form part.)

The form looks like this when opened:

Empty form

Figure 1. Empty form

If the user leaves all the fields blank and clicks the button, show:

Empty form with error messages

Figure 2. Empty form with error messages

Numbers must be valid:

Bad number

Figure 3. Bad number

All of these checks are done on the client side.

There is one check that is done on the server side only: the selling price cannot be less than the purchase cost. If it is, show:

Purchase cost less than selling price

Figure 4. Purchase cost less than selling price

If everything is OK, show a confirmation page:

Confirmation

Figure 5. Confirmation

You can try my solution and download the files. But do it yourself first!

Here is the error icon:

Error icon

The icon came from the famfamfam silk icon set. It’s one of the most complete icon sets you can find.

Hints:

  • There are text input fields. Remember to use the stripslashes() function that we talked about earlier. For example:

$product_name = stripslashes($_POST['product_name']);
print stripslashes($_POST['description']);

  • Notice that when the selling price is less than the purchase price, the error message shows in the global error message area.

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Exercise: Idea saver

Write a PHP application that saves ideas for projects to a file. You can try the application. (I disabled the idea saving bit.)

Here is the home page:

Home page

Figure 1. Home page

Note that the input focus is in the first field when the page loads.

You can download the background image. It’s from an OSWD template. You can also download the error icon.

The user completes all of the fields. If any fields are left blank, the user sees a page like this:

Missing data

Figure 2. Missing data

Checks for missing data are done on the client.

Only some users are allowed to post ideas about some projects.

  • Users beth.jackson and zon.plaske are allowed to post ideas about the Grendel project.
  • Users jed.mccurry and fhit.jhoges are allowed to post ideas about the Bathron project.

If the user is not allowed to post about the project, the following appears:

No permission

Figure 3. No permission

The message is “Sorry, that user does not have permission to post ideas about that project. Please check the project and user name.”

If the user is allowed…

Valid data

Figure 4. Valid data

... the post is saved to a file, and a confirmation shown:

Confirmation

Figure 5. Confirmation

The project/user permission check must be done on the server! In PHP. Not in JavaScript! You can use the pattern from the Complete validation page.

Click the “Show ideas” link, and you see a list of all the ideas saved:

Idea list

Figure 6. Idea list

Upload your solution to your server. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?