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. Discussion
It 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. |
|