Recipe 19.12. Writing a Multiscreen CGI Script
Author: Tom Christiansen & Nathan Torkington
Date: 1999-07-02T01:45:44Z

#

| Chapter 19 CGI Programming |

## 19.12. Writing a Multiscreen CGI Script

### Problem

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.

### Solution

Use a hidden field to encode the current screen.

### Discussion

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.

### See Also

The documentation for the standard CGI module.

19.11. Creating Sticky Widgets |  | 19.13. Saving a Form to a File or Mail Pipe