You want to write a single CGI script that can return several different pages to the browser. For instance, you want a single CGI script for administering a database of products. The script will be called to display the form to add a product, to process the add-product form, to display a list of products to delete, to process the delete-product form, to display a list of product to edit, to display a form of the product's attributes for the user to change, and to process the edit-product form. You can use these multiscreen CGI scripts to form an elementary shopping-cart-type application.
Use a hidden field to encode the current screen.
It is easy to generate sticky hidden fields with the CGI module. The
hidden
function returns HTML for a hidden widget and will use the widget's current value if you only give
hidden
the widget name:
use CGI qw(:standard); print hidden("bacon");
To determine which page ("display product list", "display all items in shopping cart", "confirm order") to display, use another hidden field. We'll call this one
.State
so it won't conflict with any field we might have called
State
(for instance, in credit card billing information). To let the user move from page to page, use submit buttons that set
.State
to the name of the page to go to. For instance, to make a button to take the user to the "Checkout" page, use:
print submit(-NAME => ".State", -VALUE => "Checkout");
We wrap this in a function to make it easier to type:
sub to_page { return submit( -NAME => ".State", -VALUE => shift ) }
To decide what code to display, check the
.State
parameter:
$page = param(".State") || "Default";
Put the code to generate each page in separate subroutines. You could decide which subroutine to call with a long
if
...
elsif
...
elsif
:
if ($page eq "Default") { front_page(); } elsif ($page eq "Checkout") { checkout(); } else { no_such_page(); # when we get a .State that doesn't exist }
This is tedious and clumsy. Instead use a hash that maps a page name to a subroutine. This is another strategy for implementing a C-style
switch
statement in Perl.
%States = ( 'Default' => \&front_page, 'Shirt' => \&shirt, 'Sweater' => \&sweater, 'Checkout' => \&checkout, 'Card' => \&credit_card, 'Order' => \&order, 'Cancel' => \&front_page, ); if ($States{$page}) { $States{$page}->(); # call the correct subroutine } else { no_such_page(); }
Each page will have some persistent widgets. For instance, the page that lets the user order t-shirts will want the number of t-shirts to persist even when the user continues and orders shoes as well. We do this by calling the page-generating subroutines with a parameter that lets them know whether they're the active page. If they're not the active page, they should only send back hidden fields for any persistent data:
while (($state, $sub) = each %States) { $sub->( $page eq $state ); }
The
eq
comparison returns true if the page is the current page, and false if it isn't. The page-generating subroutine then looks like this:
sub t_shirt { my $active = shift; unless ($active) { print hidden("size"), hidden("color"); return; } print p("You want to buy a t-shirt?"); print p("Size: ", popup_menu('size', [ qw(XL L M S XS) ])); print p("Color:", popup_menu('color', [ qw(Black White) ])); print p( to_page("Shoes"), to_page("Checkout") ); }
Because the subroutines all generate HTML, we have to print the HTTP header and start the HTML document and form before we call the subroutines. This lets us print a standard header and footer for all the pages, if we want. Here, we assume we have subroutines
standard_header
and
standard_footer
for printing the headers and footers:
print header("Program Title"), start_html(); print standard_header(), begin_form(); while (($state, $sub) = each %States) { $sub->( $page eq $state ); } print standard_footer(), end_form(), end_html();
Don't make the mistake of encoding prices in the forms. Calculate prices based on the values of the hidden widgets, and sanity-check the information where you can. For example, compare against known products, to make sure they're not trying to order a burgundy XXXXXXL t-shirt.
Using hidden data is more robust than using cookies, because you can't rely on the browser supporting or accepting cookies. A full explanation is in Recipe 19.10 .
We show a simple shopping cart application as the program chemiserie at the end of this chapter.
The documentation for the standard CGI module.
Copyright © 2001 O'Reilly & Associates. All rights reserved.