19.12. Writing a Multiscreen CGI ScriptProblemYou 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. SolutionUse a hidden field to encode the current screen. DiscussionIt is easy to generate sticky hidden fields with the CGI module. The 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 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 $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 ($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 %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 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 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. See AlsoThe documentation for the standard CGI module. |