Chapter 5: Advanced Forms and Perl Control Structures
In the last chapter you learned how to decode form data, and mail it to yourself. However, one problem with the guestbook program is that it didn't do any error-checking or specialized processing. You might not want to get blank forms, or you may want to require certain fields to be filled out. You might also want to write a quiz or questionnaire, and have your program take different actions depending on the answers. All of these things require some more advanced processing of the form data, and that will usually involve using control structures in your Perl code.
Control structures include conditional statements, such as if/elsif/else blocks, as well as loops like foreach
, for
and while.
If Conditions
You've already seen if/elsif in action. The structure is always started by the word if
, followed by a condition to be evaluated, then a pair of braces indicating the beginning and end of the code to be executed if the condition is true. The condition is enclosed in parentheses:
if (condition) { code to be executed }
The condition statement can be anything that evaluates to true or false. In Perl, any string is true except the empty string and 0. Any number is true except 0. An undefined value (or undef
) is false.You can also test whether a certain value equals something, or doesn't equal something, or is greater than or less than something. There are different conditional test operators, depending on whether the variable you want to test is a string or a number:
-
Relational and Equality Operators
Test | Numbers | Strings |
$x is equal to $y | $x == $y | $x eq $y |
$x is not equal to $y | $x != $y | $x ne $y |
$x is greater than $y | $x > $y | $x gt $y |
$x is greater than or equal to $y | $x >= $y | $x ge $y |
$x is less than $y | $x < $y | $x lt $y |
$x is less than or equal to $y | $x <= $y | $x le $y |
If it's a string test, you use the letter operators (eq, ne, lt, etc.), and if it's a numeric test, you use the symbols (==, !=, etc.). Also, if you are doing numeric tests, keep in mind that $x >= $y is not the same as $x => $y. Be sure to use the correct operator!
Here is an example of a numeric test. If $varname is greater than 23, the code inside the curly braces is executed:
if ($varname > 23) { # do stuff here if the condition is true }
If you need to have more than one condition, you can add elsif and else blocks:
if ($varname eq "somestring") { # do stuff here if the condition is true } elsif ($varname eq "someotherstring") { # do other stuff } else { # do this if none of the other conditions are met }
The line breaks are not required; this example is just as valid:
if ($varname > 23) { print "$varname is greater than 23"; } elsif ($varname == 23) { print "$varname is 23"; } else { print "$varname is less than 23"; }
You can join conditions together by using logical operators:
Logical OperatorsOperator | Example | Explanation |
&& | condition1 && condition2 | True if condition1 and condition2 are both true |
|| | condition1 || condition2 | True if either condition1 or condition2 is true |
and | condition1 and condition2 | Same as && but lower precedence |
or | condition1 or condition2 | Same as || but lower precedence |
Logical operators are evaluated from left to right. Precedence indicates which operator is evaluated first, in the event that more than one operator appears on one line. In a case like this:
condition1 || condition2 && condition3
condition2 && condition3
is evaluated first, then the result of that evaluation is used in the || evaluation.
and
and or
work the same way as &&
and ||
, although they have lower precedence than their symbolic counterparts.
Unless
unless
is similar to if
. Let's say you wanted to execute code only if a certain condition were false. You could do something like this:
if ($varname != 23) { # code to execute if $varname is not 23 }
The same test can be done using unless
:
unless ($varname == 23) { # code to execute if $varname is not 23 }
There is no "elseunless", but you can use an else
clause:
unless ($varname == 23) { # code to execute if $varname is not 23 } else { # code to execute if $varname IS 23 }
Validating Form Data
You should always validate data submitted on a form; that is, check to see that the form fields aren't blank, and that the data submitted is in the format you expected. This is typically done with if/elsif blocks.
Here are some examples. This condition checks to see if the "name" field isn't blank:
if (param('name') eq "") { &dienice("Please fill out the field for your name."); }
You can also test multiple fields at the same time:
if (param('name') eq "" or param('email') eq "") { &dienice("Please fill out the fields for your name and email address."); }
The above code will return an error if either the name or email fields are left blank.
param('fieldname')
always returns one of the following:
undef — or undefined |
fieldname is not defined in the form itself, or it's a checkbox/radio button field that wasn't checked. |
the empty string | fieldname exists in the form but the user didn't type anything into that field (for text fields) |
one or more values | whatever the user typed into the field(s) |
If your form has more than one field containing the same fieldname, then the values are stored sequentially in an array, accessed by param('fieldname')
.
You should always validate all form data — even fields that are submitted as hidden fields in your form. Don't assume that your form is always the one calling your program. Any external site can send data to your CGI. Never trust form input data.
Looping
Loops allow you to repeat code for as long as a condition is met. Perl has several loop control structures: foreach
, for
, while
and until.
Foreach Loops
foreach
iterates through a list of values:
foreach my $i (@arrayname) { # code here }
This loops through each element of @arrayname, setting $i to the current array element for each pass through the loop. You may omit the loop variable $i:
foreach (@arrayname) { # $_ is the current array element }
This sets the special Perl variable $_ to each array element. $_ does not need to be declared (it's part of the Perl language) and its scope localized to the loop itself.
For Loops
Perl also supports C-style for
loops:
for ($i = 1; $i < 23; $i++) { # code here }
The for
statement uses a 3-part conditional: the loop initializer; the loop condition (how long to run the loop); and the loop re-initializer (what to do at the end of each iteration of the loop). In the above example, the loop initializes with $i being set to 1. The loop will run for as long as $i is less than 23, and at the end of each iteration $i is incremented by 1 using the auto-increment operator (++).
The conditional expressions are optional. You can do infinite loops by omitting all three conditions:
for (;;) { # code here }
You can also write infinite loops with while.
While Loops
A while loop executes as long as particular condition is true:
while (condition) { # code to run as long as condition is true }
Until Loops
until
is the reverse of while.
It executes as long as a particular condition is NOT true:
until (condition) { # code to run as long as condition is not true }
Infinite Loops
An infinite loop is usually written like so:
while (1) { # code here }
Obviously unless you want your program to run forever, you'll need some way to break out of these infinite loops. We'll look at breaking next.
Breaking from Loops
There are several ways to break from a loop. To stop the current loop iteration (and move on to the next one), use the next
command:
foreach my $i (1..20) { if ($i == 13) { next; } print "$i\n"; }
This example prints the numbers from 1 to 20, except for the number 13. When it reaches 13, it skips to the next iteration of the loop.
To break out of a loop entirely, use the last
command:
foreach my $i (1..20) { if ($i == 13) { last; } print "$i\n"; }
This example prints the numbers from 1 to 12, then terminates the loop when it reaches 13.
next
and last
only effect the innermost loop structure, so if you have something like this:
foreach my $i (@list1) { foreach my $j (@list2) { if ($i == 5 && $j == 23) { last; } } # this is where that last sends you }
The last
command only terminates the innermost loop. If you want to break out of the outer loop, you need to use loop labels:
OUTER: foreach my $i (@list1) { INNER: foreach my $j (@list2) { if ($i == 5 && $j == 23) { last OUTER; } } } # this is where that last sends you
The loop label is a string that appears before the loop command (foreach, for, or while). In this example we used OUTER as the label for the outer foreach loop and INNER for the inner loop label.
Now that you've seen the various types of Perl control structures, let's look at how to apply them to handling advanced form data.
Handling Checkboxes
Checkboxes allow the viewer to select one or more options on a form. If you assign each checkbox field a different name, you can print them the same way you'd print any form field using param('fieldname')
.
Here is the HTML code for a set of checkboxes:
<b>Pick a Color:</b><br> <form action="colors.cgi" method="POST"> <input type="checkbox" name="red" value=1> Red<br> <input type="checkbox" name="green" value=1> Green<br> <input type="checkbox" name="blue" value=1> Blue<br> <input type="checkbox" name="gold" value=1> Gold<br> <input type="submit"> </form>Working example: http://www.cgi101.com/book/ch5/colors.html
This example lets the visitor pick as many options as they want — or none, if they prefer. Since this example uses a different field name for each checkbox, you can test it using param:
my @colors = ("red","green","blue","gold"); foreach my $color (@colors) { if (param($color)) { print "You picked $color.\n"; } }Source code: http://www.cgi101.com/book/ch5/colors-cgi.html
Since we set the value of each checkbox to 1 (a true value), we didn't need to actually see if param($color)
was equal to anything — if the box is checked, its true. If it's not checked, then param($color)
is undefined and therefore not true.
The other way you could code this form is to set each checkbox name to the same name, and use a different value for each checkbox:
<b>Pick a Color:</b><br> <form action="colors.cgi" method="POST"> <input type="checkbox" name="color" value="red"> Red<br> <input type="checkbox" name="color" value="green"> Green<br> <input type="checkbox" name="color" value="blue"> Blue<br> <input type="checkbox" name="color" value="gold"> Gold<br> <input type="submit"> </form>Working example: http://www.cgi101.com/book/ch5/colors2.html
param('color')
returns a list of the selected checkboxes, which you can then store in an array. Here is how you'd use it in your CGI program:
my @colors = param('color'); foreach my $color (@colors) { print "You picked $color.<br>\n"; }Source code: http://www.cgi101.com/book/ch5/colors2-cgi.html
Handling Radio Buttons
Radio buttons are similar to checkboxes in that you can have several buttons, but the difference is that the viewer can only pick one choice. As with our last checkbox example, the group of related radio buttons must all have the same name, and different values:
<b>Pick a Color:</b><br> <form action="colors.cgi" method="POST"> <input type="radio" name="color" value="red"> Red<br> <input type="radio" name="color" value="green"> Green<br> <input type="radio" name="color" value="blue"> Blue<br> <input type="radio" name="color" value="gold"> Gold<br> <input type="submit"> </form>Working example: http://www.cgi101.com/book/ch5/colors3.html
Since the viewer can only choose one item from a set of radio buttons, param('color')
will be the color that was picked:
my $color = param('color'); print "You picked $color.<br>\n";Source code: http://www.cgi101.com/book/ch5/colors3-cgi.html
It's usually best to set the values of radio buttons to something meaningful; this allows you to print out the button name and its value, without having to store another list inside your CGI program. But if your buttons have lengthy values, or values unsuitable for storing in the value field, you can set each value to an abbreviation, then define a hash in your CGI program where the hash keys correspond to the abbreviations. The hash values can then contain longer data.
Let's try it. Create a new HTML form called colors4.html:
Program 5-1: colors4.html - Favorite Colors HTML Form<html><head><title>Pick a Color</title></head> <body> <b>Pick a Color:</b><br> <form action="colors4.cgi" method="POST"> <input type="radio" name="color" value="red"> Red<br> <input type="radio" name="color" value="green"> Green<br> <input type="radio" name="color" value="blue"> Blue<br> <input type="radio" name="color" value="gold"> Gold<br> <input type="submit"> </form> </body></html>
Working example: http://www.cgi101.com/book/ch5/colors4.html
Next create colors4.cgi. This example not only prints out the color you picked, but also sets the page background to that color. The %colors hash stores the various RGB hex values for each color. The hex value for the selected color is then passed to CGI.pm's start_html
function as the bgcolor (background color) parameter.
#!/usr/bin/perl -wT use strict; use CGI qw(:standard); use CGI::Carp qw(warningsToBrowser fatalsToBrowser); my %colors = ( red => "#ff0000", green => "#00ff00", blue => "#0000ff", gold => "#cccc00"); print header; my $color = param('color'); # do some validation - be sure they picked a valid color if (exists $colors{$color}) { print start_html(-title=>"Results", -bgcolor=>$color); print "You picked $color.<br>\n"; } else { print start_html(-title=>"Results"); print "You didn't pick a color! (You picked '$color')"; } print end_html;
Source code: http://www.cgi101.com/book/ch5/colors4-cgi.html
Handling SELECT Fields
SELECT fields are handled almost the same way as radio buttons. A SELECT field is a pull-down menu with one or more choices. Unless you specify a multiple select (see below), the viewer can only choose one option. Here is the HTML for creating a SELECT field:
<select name="color"> <option value="red"> Red <option value="green"> Green <option value="blue"> Blue <option value="gold"> Gold </select>Working example: http://www.cgi101.com/book/ch5/colors5.html
As with radio buttons, you access the selection in your CGI program using param('color')
:
my $color = param('color'); print "You picked $color.<br>\n";Source code: http://www.cgi101.com/book/ch5/colors5-cgi.html
Multiple-choice SELECTs
Multiple SELECTs allow the viewer to choose more than one option from the list, usually by option-clicking or control-clicking on the options they want. Here is the HTML for a multiple SELECT:
<select name="color" multiple size=3> <option value="red"> Red <option value="green"> Green <option value="blue"> Blue <option value="gold"> Gold </select>
In your CGI program, param('color')
returns a list of the selected values, just as it did when we had multiple checkboxes of the same name:
my @colors = param('color'); foreach my $color (@colors) { print "You picked $color.<br>\n"; }
So now you've seen every type of form element (except for file-uploads, which we'll look at in Chapter 14), and in every case you've seen that CGI.pm's param function returns the value (or values) from each form field. The value returned by param
is always a list, but for text, textarea, password, radio, and single select fields you can use it in a scalar context. For checkboxes and multiple select fields, you use it in an array context.
In the next chapter we'll learn how to read and write data files, so you'll be able to save and analyze the data collected by your forms.