Navigation


Lesson: Formy goodness

Goals

Let's look at an example. It's an order form for buying pirates online. It looks like this when it starts:

Order form

The user can buy cap'ns for $90 each, and swabs for $60. The form starts out with 0 in both fields. The user enters some numbers, and clicks Preview. Some totals will be shown. The user can change the order quantities, or click Confirm to finish the order.

Try it. See what happens when you make mistakes. Leave some fields empty and click Preview. Put some text into a field and click Preview.

Here is the code:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
        "http://www.w3.org/TR/html4/strict.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
        <title>Order Form</title>
        <style type="text/css">
            body {
                font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
                font-size: 1.5em;
                font-weight: bold;
                padding: 0;
                margin: 0;
                color:#ffffcc;
            }
            
            #main{
                margin-left: auto;
                margin-right: auto;
                background-image: url(1_smaller.jpg);
                border: 1px black solid;
                width: 752px;
                height: 420px;
                padding: 1em;
            }
            
            p {
                padding:0;
                margin:0;
            }
            
            h1 {
                color: #eeeeee;
                padding:0;
                margin:0;
            }
            
            .hint {
                font-size: 0.8em;
            }
            
            button {
                font-size: 0.7em;
                width: 5em;
            }
            
            #status_message {   1 
                display: none;
            }
            
            .bad_thing_happened {   2 
                color: red;
            }
        </style>
        <script type="text/javascript" src="lib/jquery-1.3.2.min.js"></script>   3 
        <script type="text/javascript">
            $(document).ready( function() {   4 
                $('#preview').click(function() {
                    preview_order();   5 
                });
                $('#confirm').click(function() {
                    confirm_order();   6 
                });
                //Disable confirm button if user presses a key in a quantity field.
                $('input').keypress( function() {   7 
                   $('#confirm').attr('disabled',true);
                });
                //Final setup.
                $('#confirm').attr('disabled',true);   8 
                $('#captains').val(0);
                $('#swabs').val(0);
                $('#captains').focus();
            });
            
            //Let the user preview the order.
            function preview_order() {
                //Clear old status message.
                $('#status_message').hide('slow');   9 
                //If field is MT, put 0 in it.
                if ( $('#captains').val() == '' ) {   10 
                    $('#captains').val(0);
                }
                if ( $('#swabs').val() == '' ) {
                    $('#swabs').val(0);
                }
                //Validate the fields.
                error_messages = '';   11 
                if ( isNaN($('#captains').val()) || $('#captains').val() < 0 ) {
                    error_messages += 'Please enter a valid number of cap\'ns.<br>';
                }
                if ( isNaN($('#swabs').val()) || $('#swabs').val() < 0 ) {
                    error_messages += 'Please enter a valid number of swabs.';
                }
                if ( error_messages != '' ) {   12 
                    //There were errors.
                    $('#status_message').html(error_messages);
                    $('#status_message').addClass('bad_thing_happened');
                    $('#status_message').show('slow');
                    //Stop processing.
                    return;
                }
                //Data in fields is valid.
                //Check for ordering nothing.   13 
                if ( $('#captains').val() == 0 && $('#swabs').val() == 0 ) {
                    $('#status_message').html(
                        'If you want to order, enter the number of cap\'ns and swabs.');
                    $('#status_message').removeClass('bad_thing_happened');
                    $('#status_message').show('slow');
                    return;
                }
                //Compute the totals.   14 
                var total_captains = $('#captains').val() * 90;
                var total_swabs = $('#swabs').val() * 60;
                var total = total_captains + total_swabs;
                //Show the totals.   15 
                $('#captains_total').html(total_captains);
                $('#swabs_total').html(total_swabs);
                $('#total').html(total);
                //Show the status message.   16 
                $('#status_message').html(
'Press <b>Confirm</b> to finish the order, or change the quantities.');
                $('#status_message').removeClass('bad_thing_happened');
                $('#status_message').show('slow');
                //Enable the confirm button.
                $('#confirm').attr('disabled',false);   17 
            }
            
            //User confirmed the order.
            function confirm_order() {
                alert('Arrgh! The pirates are on their way.');
            }
        </script>        
    </head>
    <body>
        <div id='main'>
            <h1>Order</h1>
            <p>Order your pirates here. Complete the form and
            press Preview. If it's OK, press Confirm.</p>
            <table border="0" cellspacing="10">
                <tr>
                    <th>&nbsp;</th>
                    <th>&nbsp;</th>
                    <th>&nbsp;</th>
                    <th>Total</th>
                </tr>
                <tr>
                    <td>Cap'ns</td>
                    <td><input type="text" id="captains" size="5"></td>   18 
                    <td>x $90 each</td>
                    <td id="captains_total" align="right">0</td>   19 
                </tr>    
                <tr>
                    <td>Swabs</td>
                    <td><input type="text" id="swabs" size="5"></td>   20 
                    <td>x $60 each</td>
                    <td id="swabs_total" align="right">0</td>   21 
                </tr>
                <tr>
                    <td colspan="4" id="total" align="right">0</td>   22 
                </tr>
            </table>
            <p>
                <button id="preview" title="Preview the order">Preview</button>    23 
                &nbsp;&nbsp;&nbsp;
                <button id="confirm" title="Confirm the order">Confirm</button>   24 
            </p>
            <p id="status_message"></p>   25 
        </div>
    </body>
</html>

Let's look at the HTML first. The <input> field are at 18 and 20. This is where the user enters the number of cap'ns and swabs s/he wants to buy. Notice the ids of the fields. These are what we'll use to refer to the fields by.

The main outputs are the totals. The total price of the cap'ns is at 19. So if the user orders two cap'ns, we want the number (2 x 90) = 180 to appear here. The total for swabs is at 21, and the total for the entire order is at 22. This will be the sum of the values in 19 and 21. Again, we'll use the ids of the fields to refer to them.

The Preview button at 23 does most of the work. If the data entered by the user is valid, the code shows the user the totals, and enables the Confirm button (24). The user clicks Confirm to complete the order.

What is this "enabled" stuff all about? Here are two buttons. One is enabled, and the other is not.

  

On the pirate form, we only want to let the user confirm an order after s/he has looked at the totals. So, the Confirm button is disabled until the user clicks the Preview button, and the totals are computed.

OK, let's look at the code. jQuery is loaded as usual, at 3. The ready() method runs when the document is loaded, at 4. At 5, a click handler is set up for the Preview button. Notice that instead of putting the code directly at 5, I moved it to a function instead. This makes the ready() code shorter and easier to read. I did the same at 6, for the Confirm button.

The code at 7 looks a little strange. Here it is:

$('input').keypress( function() {
    $('#confirm').attr('disabled',true);
});

$('input') means find all of the <input> elements on the page. There are two of them: the order quantity fields. The keypress() event is triggered when the user presses a key. So, when the user presses a key in either of the input fields, the function is executed.

$('#confirm') refers to the Confirm button. attr(x, y) sets attribute x to the value y. This code disables the button.

The code at 8 does some final initialization. It disables the Confirm button, puts zeros in the order quantities, and sets the focus into a field.

Why is there code to disable the Confirm button at both 7 and 8? The line at 7 is executed only when there is a keypress in an <input> field. This doesn't happen right away. So 8 is needed to disable Confirm when the program loads.

Most of the work is done in the preview_order() function. Remember, this function is called when the user enters the number of cap'ns and swabs s/he wants to buy, and clicks Preview.

The code at 9 hides the status message. "Huh?" you ask, "What's that?" I'm glad you asked. We need a way to give feedback to the user. We could use alert()s, but people forget those messages. Instead, it would be better to have a place on the screen to show error and other messages.

Look at 25. It's a <div> for showing status messages. It starts off empty (nothing between <div> and </div>) and hidden. When we want to tell the user something, we'll put some text into it, and show it.

The preview_order() function uses the status_message div to tell things to the user. But the preview_order() function can run several times. Maybe a user types in 10 cap's and 50 swabs, clicks Preview, looks at the price, says "Arrgh! Too much loot! I can't afford that!" The user reduces the order quantity, and runs preview_order() again (by clicking the Preview button). The function hides status_message, until it knows what it wants to tell the user.

At 10, preview_order() checks the order quantity fields. If they are empty, it puts zeros in them.

At 11, the real validation starts. We have to decide how to handle errors. The user could make either one or two errors. The number of cap'ns could be bad, the number of swabs could be bad, or they could both be bad. So, if we come across an error, what should we do?

We have two choices. We could show just one of them, and wait for the user to fix that one before doing more checking. That can be annoying. They get one error message, they fix it, only to get another one on the next field.

Another way to do it is to show all the error messages at once (if there are any). Then the user can fix them all at once.

The code starting at 11 shows a pattern for doing this (remember that a "pattern" is a common way of doing things). First, we create a variable, error_messages, to hold all the error messages. It starts out empty. Then there are a bunch o' if statements, checking for each error. (There are only two in this program, but some Web forms have many fields, and there might be different types of errors that can happen on each one.) If an error is found, an error message is appended to the variable error_messages. When all the checks are done, we look at error_messages. If it's empty, there were no errors. If it isn't empty, there was at least one error, and we show the message(s) to the user.

Let's look at the code in more detail. isNaN($('#captains').val()) will be true if the contents of the field is not a number. $('#captains').val() < 0 will be true if the field contains a number that is less than zero. If either one of these is true, the if statement is true, since || means "or," and we append an error message to the existing set of error messages.

Look at this statement:

error_messages += 'Please enter a valid number of cap\'ns.<br>';

The += means "add to the end of." It's the same as:

error_messages = error_messages + 'Please enter a valid number of cap\'ns.<br>';

But it's less typing.

The second ' in the statement is escaped. I wanted the ' to be part of the actual error message shown to the user, but if I don't escape it, then JS will think that it closes the string that started with the first '. The \' tells JS that I want the ' to be treated as a normal character that should be part of the message. I don't want it to terminate the string.

Notice the <br> tag. What's it for? Remember that there could be more than one error message. I want them on separate lines, so it's easier for the user to read. Try it out. Enter bad data in both fields, and you'll see two error messages on two separate lines.

After all the error checking has been done, this statement:

if ( error_messages != '' ) {

will test whether there were any errors. If there were any at all, error_messages will not be empty. Now it's time to use the status message. First, its content is set using the html() method. Next, a class is applied. All it does is make the text red, but it could do more if we wanted. The status message is then shown. Finally, the return statement exits the function.

So, here is the pattern for checking input fields and reporting multiple errors all at once:

  1. Create an empty variable to hold the error messages.
  2. Test for errors one at a time. If there is an error, append a message to the error message variable.
  3. After all the tests are complete, check whether the error message variable has anything in it. If it does, show the error(s), and stop processing.

Look for patterns in your own programs. It will make your life easier.

The code at 14 computes the order totals. The stuff at 15 shows them. The code at 16 shows a status message, telling the user s/he can use the Confirm button. 17 enables the button.

Use alert() statements to study the program. For example, maybe you're not sure how this works:

 

if ( error_messages != '' ) {

Change it to:

 

alert('error_messages: ' + error_messages);
if ( error_messages != '' ) {

You'll be able to better understand how the program works.

Notes

Notes content

Exercises

Exercise 4. Firs' mates.

Add another type of pirate people can buy: firs' mates. They cost $80 each. Add the HTML field and labels, change the error processing, add to the total calculation, etc.

Solution. Try it yourself first.

Exercise 5. Cherubs.

Create an order form for cherubs. They cost $120 each. If you order four or more, delivery is free. Otherwise, delivery costs $20 per cherub.

Here is a screen shot:

Screen shot

Solution. Try it yourself first.