Chapter 21

Recursive CGI


CONTENTS


We call this chapter "Recursive CGI" because it's about CGI programming that involves reusing CGI scripts to generate pages for different situations. We chose the word "objects" because this chapter treats CGI scripts as objects in an object-oriented programming sense.

CGI scripts can be programmed in almost any language. As long as the program can be executed by your Web server, it doesn't matter what language you've used to write the CGI script. Most CGI scripts shown in this book are written in Perl, because it's easier to prototype programs in Perl than in C or other lower-level languages. CGI scripts can be written quite successfully in C++, a language for writing object-oriented programs. You shouldn't come away from this chapter with the idea that CGI scripts are truly object-oriented, but the theme of objects receiving input and generating output should help you to visualize how CGI scripts can be written to build applications where code reuse is a key consideration.

This chapter transforms what we have been saying all along about CGI scripts and what they do. We need to address one larger issue about CGI scripting before we talk about the applications in Chapters 22, "How to Build HTML On the Fly," 23, "How to Build CGI On the Fly," and 24, "User Profiles and Tracking."

The issue is that CGI scripts are used to create environments. With static HTML documents, the flow of traversing the Web is straightforward. You can chart all the possible outcomes, all the possible routes from the home page to any other page. As soon as you build pages that request information from the user or display live information-like sports scores or weather reports-the design of the site changes. Generating pages dynamically is not random.

On a site where all the pages are static, there's no confusion about where certain links will take the user. The HTML builder fleshes out all the static pages before they're put on the Web server. Unlike the hard-coding you do for static pages, you create dynamically generated pages with links that may not even exist-links to other CGI scripts.

We're going to redefine what CGI scripts are, and then employ this new definition to show how CGI scripts can be used. We'll learn that the result of a CGI script is an object. The transition between different pages that are created dynamically causes us to be concerned about what state the user is in. We'll discuss how the state of a page generated dynamically affects other pages created by CGI scripts. These pages must all interact based upon data passed between them.

Web Objects and States

The American Heritage Dictionary, 3rd Edition, defines object as "Something perceptible by one or more of the senses, especially by vision or touch; a material thing." When developing Web sites, modify this definition to make object the following:

A page; a state that accepts input from one or more sources-usually from an HTML form; or the effect of a CGI script or other dynamic HTML event.

The Web object is a result of change in state. Coming from one page to another is change in state, so treating each page as an object lets us consider what pages do to one another. If there were no CGI programming or dynamic page generation in Web sites, then each page would not have any effect on other pages. This is true in static HTML, where each page is an isolated state that neither gives input to nor gets input from other pages.

CGI programming, however, is something that does exist, and pages in many sites are generated dynamically. We're not concerned about what CGI scripts actually generate. We're concerned about the effects that CGI scripts have on other pages in the Web site. Each page-each object of the Web site-affects other pages and other objects.

Objects define the actions that they accept. Imagine an apple. The apple is modified by the action "eat." It becomes smaller and changes shape. The person eating the apple is modified by the action "chew." The person's mouth moves, and pieces of apple change the state of the person's body, eventually forcing other actions from other objects, and so on.

A Web object is modified by the actions it accepts. An HTML form, for instance, changes into something new when it accepts the "submit" action. A home page imagemap changes into something new when it's clicked. The HTML form and the imagemap are not responsible for exactly what they become, but they are responsible for causing new actions to be given to new objects as a result of being clicked.

Actions passed to new objects are part of the change of state that Web objects often experience on a Web site. A change in state has a visible effect of some kind on the Web object. It's important, though, to investigate what really happens between Web objects when some of them undergo a change in state.

Web objects contain state information. We've already seen examples where state information is passed to and from CGI scripts to let the next instance of the CGI script know how to handle the incoming data. The act of storing that information is an issue of data preservation (see Chapter 20, "Preserving Data"). What we're looking at here is how Web objects that contain data interact with other objects and pages that are generated in a Web site.

Changing State

When pages on a site are viewed, each page is unique from other pages. The server by default does not know that the user looking at the current page is the same person who saw the previous page. There's no real sense of "previous page." The Web server and browser have to perform some interaction to maintain that relationship. Using cookies and other key information, it's possible to simulate a stateful environment where the Web server is aware that a given user is the same user who has viewed certain other pages. In general, though, a Web server does not have any relationship with the client other than knowing that the client is one of thousands (or millions) of clients who are making a request to the server for information. By default, Web servers are built to give back information to client browsers only when information is requested.

Note
Stateful is a term that refers to the state of an environment. Let's use stateful to mean a system or function that has inherent qualities that determine a mode, character, or condition of that system or function. For example, a traffic intersection is a stateful environment. There are lights and signals that determine when it is safe for cars and pedestrians to cross certain streets connected to the intersection. A Web page generation system based on dynamic CGI programming is considered stateful if the content of the pages or the URL referenced contains information that can be construed as a trigger to indicate a property of "state."

How, then, does a given object exist with information that came from a previous object? How does an HTML form object change into a result page object based on input information? Are there actions being given to the result page object that tell it what to do with the information from the parent object?

The answer to these questions lies within the Web protocol-HTTP. Yet, the answer you find there doesn't explain plainly how you can use HTTP to build a framework in which Web objects interact and influence each other. That answer comes from the application of HTML and other markup languages that generate content.

The usage of markup languages to help create instances of Web objects is necessary. Without markup languages to help motivate interactivity between Web objects, and to help provide the channels through which actions are given to objects to perform, the Web would be rather dull. You know that markup languages are the building blocks of Web objects, but they do so much more-they give objects the power to influence other objects and to create chains of objects that, in turn, are really the effects of changes in state-changes in visual and system states of the Web server and the client browser.

Our questions so far include the following:

The property of Web objects changing state implies that there's a hierarchical structure to Web objects. CGI scripts generate pages, which in turn generate pages, which in turn generate pages, and so on… This leads us to believe that there's a definite structure to the collection of objects created by CGI scripts. There's a first object and a chain of objects.

Imagine a comic book character swinging from vine to vine; each new vine is a new instance of a CGI script and the transition from vine to vine is the chain of events (or chain of objects) that can be manipulated. The choice of which vine to swing upon depends on knowing what other vines will be available to use after the complete arc of the current swing is done. The same holds true for Web objects, which include the CGI scripts themselves and the pages they create.

The first question above really asks what a Web object does to generate pages and what setup is necessary for new pages to be generated. The answer lies with the CGI script; this script is written to generate pages and perform system tasks, but it needs to make decisions based on input about what kind of page (if any) to generate.

Recall the deli HTML form example in Chapter 19, "How to Build Pages On the Fly," where the decision to generate a summary of what kind of sandwich has been ordered is based on input to the CGI script. The CGI script receives input about what ingredients to use in the sandwich, and the presence of that data causes the CGI script to go into Display Summary mode.

Other example situations in Part VII, "Advanced CGI Applications: Commercial Applications," show that it's possible for the CGI script to be in several modes at various times. One mode might be to review the contents of a shopping bag. Another mode might be to lead the user into a checkout counter environment.

The CGI script is empowered to take the user to any particular area of the site (static or dynamic) depending on what information is held by the CGI script.

The second question above relates to the process of an HTML form passing data to a CGI script. The HTML form is constructed to serve the purpose of the CGI script-not the other way around. The CGI script that handles data from an HTML form has two directions to go. The script can generate the proper HTML to build a new HTML form, perpetuating the HTML-to-CGI cycle. Or, the CGI script can generate a page that terminates the cycle of generating new pages ad nauseum.

This chapter is concerned with the first choice, for the CGI script to generate the proper HTML to build a new HTML form (or links to CGI scripts) that perpetuate the HTML-to-CGI cycle.

Finally, let's consider the third question above. Sure, the result page object knows what to do with inherited information. The data passed to CGI scripts from HTML forms-or in the URL-are flags that tell the CGI script what to do next. The data passed to the CGI script is the required action, and sets the conditions for how the CGI script will act. The CGI script is isolated until it determines what kind of page to create, based upon the data passed to it. Most likely, the CGI script is employing data preservation techniques to keep a running history of the data given to it.

An interesting result of this is that objects can, in a sense, affect themselves-give actions to themselves to re-create themselves. Sometimes they become objects identical to what they already are; in other cases, they become new objects that inherit certain data and attributes of their parent object. This is how we get recursive objects in the Web environment.

The Recursive Object

A recursive object is one that changes state to become a new object, inheriting some data and functions of its creator.

For example, consider what the following object, doAgain.cgi, does in Listing 21.1.


Listing 21.1  doAgain.cgi-A Recursive CGI Script

#!/usr/local/bin/perl

@INC = ('.', @INC);
require 'web.pl';

%Form = &getStdin;
&beginHTML;

print "<HTML>\n",
      "<TITLE>\n",
      "Basic Recursive Object, Listing 1, Chapter 21\n",
      "</TITLE>\n",
      "<BODY BGCOLOR=FFFFFF>\n";

foreach $key (sort keys %Form) {
   if ($key =~ /^stored(\d+)/) {
     $nn = $1;
     print "Data stored: Form{$key} = $Form{$key}<BR>\n";
     next;
   }
}

$nn++;

if ($Form{'newData'}) {
    $Form{"stored${nn}"} = $Form{'newData'};
    print "Data stored: Form{stored${nn}} ",
          " = $Form{'newData'}<BR>\n";
   }

print "<FORM METHOD=POST ACTION=\"/cgi-bin/doAgain.cgi\">\n";
print  "New Number: <INPUT NAME=\"newData\"><BR>\n",
       "<INPUT TYPE=SUBMIT VALUE=\"Again\">\n";
      
for ($i=1;$i<=$nn;$i++) {

   print "<strong>", $Form{"$stored${i}"} , "</strong><br>\n";

   print "<INPUT NAME=\"stored${i}\" ",
                "VALUE=", 
                "\"", $Form{"stored${i}"}, "\" ",
                "TYPE=\"HIDDEN\">\n";
}
print "</FORM>\n";

doAgain.cgi is a CGI script, but more specifically, it's a recursive object that prints out the data it knows, stores this data as hidden types, and asks for new data.

This is the HTML form that gets the user started with the recursive object doAgain.cgi in Listing 21.2.


Listing 21.2  doAgain.html-The HTML Page to Start Off the Recursive Script doAgain.cgi

<HTML>
<TITLE>
Listing 1, Chapter 21 Programs for Webmasters' Expert Solutions
</TITLE>
<BODY BGCOLOR=FFFFFF>
<H1>Listing 1, Chapter 21</H1>

This listing shows a basic recursive CGI script.

To start things off, enter a number in the box and press
start.
<P>
<FORM METHOD="POST" ACTION="/cgi-bin/doAgain.cgi">

Number: <INPUT NAME="newData"><BR>
<INPUT TYPE="SUBMIT" VALUE="Start"><BR>
</FORM>

<HR>
<A HREF="../index.html">Home</A>
</BODY>
</HTML>

See Figure 21.1 for an entry page to the doAgain.cgi script.

Figure 21.1: The entry page to the doAgain . cgi wants a number to save.

A recursive CGI script can be either the same CGI script giving actions to itself for another iteration, or a transition from one dynamic CGI script to another (see Fig. 21.2).

Figure 21.2: The recursive CGI script output after a few iterations.

Once one page is generated via a CGI script, it's possible to keep the cycle going by
making all subsequent pages dynamically generated.

Let's look at an example. First, a CGI script named stepOne.cgi generates the following output:

<a href="/cgi-bin/stepTwo.cgi">State 2</a>

If that link is followed, then a script named stepTwo.cgi generates the following output:

<a href="/cgi-bin/stepOne.cgi">State 1</a>

Now, we've created a loop. Each CGI script generates output that allows the cycle of page generation to continue dynamically flipping from stepOne.cgi to stepTwo.cgi as long as the user follows the appropriate links.

The way out of a recursive loop is to give the user a link to somewhere else in the site. The problem is that because the pages made by a recursive system are tied to the same script, the tendency is to make every page have a standard "exit link toolbar."

It would be a better idea to use state information about what page the user is actually viewing and put an exit link only on those pages where it makes sense for there to be an exit link.

For example, with the generic searching tool from Chapter 20, "Preserving Data," there was a sequence of pages the user went through from beginning the search until the end when a second search refinement was issued. An exit link shouldn't appear until the
end of the "chain" of pages, even though all the pages were built from a recursive CGI system.

Our next example is truly recursive, because it generates all new pages from the same CGI script. First, a script named simpleloop.cgi generates the following output:

<a href="/cgi-bin/simpleloop.cgi?state=1">To State 1</a>

Listing 21.3 contains the complete source code for simpleloop.cgi.


Listing 21.3  simpleloop.cgi-Source Code for simpleloop.cgi

#!/usr/local/bin/perl

@INC = ('.', @INC);
require 'web.pl';

%Form = &getStdin;
&beginHTML;

print "<HTML>\n",
      "<TITLE>Basic Recursive Object, Listing 2, Chapter 21</TITLE>\n",
      "<BODY BGCOLOR=FFFFFF>\n";

$Form{'state'} = 1 unless defined($Form{'state'});

print "<H1>Current State: $Form{'state'}</H1>\n";
 
if ($Form{'state'} == 1) {
   $newState = 2;
}
else
{
   $newState = 1;
}
print "<A HREF=\"/cgi-bin/simpleloop.cgi?state=$newState\">",
      "To State $newState</A>\n";

You'll see this technique for creating relationships between pages put to good use later in this chapter and in other chapters.

The technique is used throughout the chapter on shopping baskets and carts. On the surface, it might look like that by writing one script to generate any number of pages, you're just trying to save some file space or simplify the management of files by centralizing (some would say restricting) generation down to only one CGI script. Actually, this is just a side effect. The real reason for using recursive CGI techniques is to share code resources within the same CGI script and to encapsulate the entire environment within one CGI script.

With one CGI script, it's easier to exert control over how the pages interact with each other, and what data is passed from page to page.

With the shopping basket applications in Part VII and the Web chat applications in Chapter 14, "How Server Push Improves PlainChat," we used the same CGI script to
generate two basic kinds of pages. The transcript page that was read-only and the
transcript page that was read-write. Two pages, one script.

Recursive CGI Examples

Let's go over some examples where we use recursive CGI techniques to solve problems to make a Web site more effective.

Number Guessing Game

A number guessing game is implemented in Listing 21.4. It's a simple game where the Web prompts you to guess a number between 1 and 20.

After the first guess, the script begins the loop (the recursive loop). If the first guess is right, then there isn't any need to continue the loop and the cycle starts over with a new guess (and a new random number to guess).

But 1 in 20 is not the best odds, so the first number the user guesses is probably wrong, so tell him so and ask him to try again. Since it's the first guess, you don't have any way to give them a hint (for example, we can't tell them they are getting colder or warmer). After the second guess, the user is told how close he is to the answer only by the terms "getting warmer" or "getting colder."

Let's look at how this works. Consider this recursive CGI script, which plays a number guessing game.


Listing 21.4 Implementing a Number Guessing Game

#!/usr/local/bin/perl

@INC = ('.', @INC);
require 'web.pl';

%Form = &getStdin;
&beginHTML;

srand(time|$$);

print "<HTML>\n",
      "<TITLE>\n",
      "Basic Recursive Object, Listing 3, Chapter 21\n",
      "</TITLE>\n",
      "<BODY BGCOLOR=FFFFFF>\n";
if (!$Form{'guess'}) {
  print "<FORM METHOD=POST ",
        "ACTION=\"/cgi-bin/guessNumber.cgi\">\n",
        "First Guess: <INPUT NAME=\"guess\"><BR>\n",
        "<INPUT TYPE=SUBMIT VALUE=\"Try Guess\">\n",
        "</FORM>\n";
  exit;
} 
      


$actualNumber = $Form{'actualNumber'};
$actualNumber = int(rand(20))+1 unless $actualNumber>0;

$tries        = $Form{'tries'};
$tries        = 0 unless $tries;
$tries++;
$done = 1 if $tries>4;

if ($Form{'guess'} == $actualNumber) {
   print "You got it right in $tries tries.\n";
   $right = 1;
}
else
{ 
   print "You didn't get it.\n";

   $lastTry = $Form{'lastTry'};

   if ($lastTry) {
      $diff_last = &abs($lastTry - $actualNumber);
      $diff_now  = &abs($Form{'guess'} - $actualNumber);

     do {       
      if ($diff_last < $diff_now) {
         print "You are getting colder.<BR>\n";
      }
      elsif ($diff_last == $diff_now) {
         print "You are about the same.<BR>\n";
      }
      else {
         print "You are getting warmer.<BR>\n";
      } 
     } unless $done;
      
   }
}


if ($done) {
  if ( ! $right) {
     print "Sorry, you are all out of guesses.\n";
  }
  print "<A HREF=\"/cgi-bin/guessNumber.cgi\">Play Again?</A>\n";
}
else
{
  print "<FORM METHOD=POST ACTION=\"/cgi-bin/guessNumber.cgi\">\n",
        "New Guess: <INPUT NAME=\"guess\"><BR>\n",
        "<INPUT TYPE=SUBMIT VALUE=\"Try Guess\">\n",
        "<INPUT TYPE=HIDDEN NAME=\"lastTry\" ",
        "VALUE=\"$Form{'guess'}\">\n",
        "<INPUT TYPE=HIDDEN NAME=\"tries\" VALUE=\"$tries\">\n",
        "<INPUT TYPE=HIDDEN NAME=\"actualNumber\" ",
        "VALUE=\"$actualNumber\">\n",
        "</FORM>\n";
}; 
      

sub abs {
  local($x) = $_[0];
  $x<0?-1*$x:$x;
}

To play this game, the user just needs to go to the URL of this CGI script. It doesn't even require a primer form (a static HTML form to get the first guess). Let's examine the sections of the script to see how everything works.

You begin with the standard linkage for CGI scripts:

#!/usr/local/bin/perl

@INC = ('.', @INC);
require 'web.pl';

%Form = &getStdin;
&beginHTML;

These lines load the Perl library that has the functions getStdin and beginHTML defined.

Our game uses random numbers, so we need a line to seed the random number
generator:

srand(time|$$);

It's advisable to seed the random number generator with the time of day bit-wise
combined (using OR) with the PID of the CGI script.

We next set the title of the page and the background color:

print "<HTML>\n",
      "<TITLE>",
      "Basic Recursive Object, Listing 3, Chapter 21",
      "</TITLE>\n",
      "<BODY BGCOLOR=FFFFFF>\n";

We then need to figure out if we have a guess or not. If we don't have a guess, then
we know that the user arrived at this CGI script without guessing a number on a page
generated by this script. In that case, we generate an HTML form that allows the user to enter a guess:

if (!$Form{'guess'}) {
  print "<FORM METHOD=POST ACTION=\"/cgi-bin/guessNumber.cgi\">\n",
        "First Guess: <INPUT NAME=\"guess\"><BR>\n",
        "<INPUT TYPE=SUBMIT VALUE=\"Try Guess\">\n",
        "</FORM>\n";
  exit;
} 

Next, if we don't have a secret random number picked yet for the user to guess, we need to pick a number:

$actualNumber = $Form{'actualNumber'};
$actualNumber = int(rand(20))+1 unless $actualNumber>0;

We look for an existing number in the hidden data of the parent of this page, to make sure that we don't generate a new secret number if one already exists and the user hasn't run out of guesses.

Next, we update the number of tries the user has made:

$tries        = $Form{'tries'};
$tries        = 0 unless $tries;
$tries++;
$done = 1 if $tries>4;

Notice that if the user has used more than four tries, we set the flag $done. The rest of the CGI script generates different HTML depending upon how many turns the user has left.

If the guess is correct, we tell the user and set a flag that we are done, and have the right answer:

if ($Form{'guess'} == $actualNumber) {
   print "You got it right in $tries tries.\n";
   $right = 1;
}
else

If the guess is incorrect, we tell the user and register the guess as lastTry:

{ 
   print "You didn't get it.\n";
   $lastTry = $Form{'lastTry'};
When the user guesses again, we use the current guess along with lastTry to 
determine how close she's getting to the actual number:
if ($lastTry) {
   $diff_last = &abs($lastTry - $actualNumber);
   $diff_now  = &abs($Form{'guess'} - $actualNumber);

If this isn't the user's last turn, we give her a hint based on how close she came with the current guess and the last guess she made. If the difference between the last guess and the actual number is less than the difference between the current guess and the actual number, we say she's getting colder. If the difference is greater (the last guess is farther away from the actual number), we say she's getting warmer. If the difference from the actual number is the same, we tell her she's "about the same."

     do {
      if ($diff_last < $diff_now) {
         print "You are getting colder.<BR>\n";
      }
      elsif ($diff_last == $diff_now) {
         print "You are about the same.<BR>\n";
      }
      else {
         print "You are getting warmer.<BR>\n";
      } 
     } unless $done;
      
   }
}

Next, the script must decide what to do after checking the guess. The options are to give the user another chance to guess, or to tell the user she's out of tries. If she has guessed the actual number correctly, we tell her. If she's incorrect, but has run out of guesses, we tell her. In either case, because the game's over, we give her a link to play again if she wants to:

if ($done) {
  if ( ! $right) {
     print "Sorry, you are all out of guesses.\n";
  }
  print "<A HREF=\"/cgi-bin/guessNumber.cgi\">Play Again?</A>\n";
}
else

If the user still has tries left to use, we output the HTML form that allows her to enter a new guess:

{
  print "<FORM METHOD=POST ACTION=\"/cgi-bin/guessNumber.cgi\">\n",
        "New Guess: <INPUT NAME=\"guess\"><BR>\n",
        "<INPUT TYPE=SUBMIT VALUE=\"Try Guess\">\n",
        "<INPUT TYPE=HIDDEN NAME=\"lastTry\" ",
        "VALUE=\"$Form{'guess'}\">\n",
        "<INPUT TYPE=HIDDEN NAME=\"tries\" VALUE=\"$tries\">\n",
        "<INPUT TYPE=HIDDEN NAME=\"actualNumber\" ",
        "VALUE=\"$actualNumber\">\n",
        "</FORM>\n";
}; 

The script is in Perl4, so we have to use our own absolute value function abs to return the absolute value of the argument passed to it.

sub abs {
  local($x) = $_[0];
  $x<0?-1*$x:$x;
}

Recursive CGI scripting is a powerful tool for developing applications that require maintenance of state information. By default, the markup languages accepted by Web browsers do not enable pages to contain state information. Chapter 9, "Making a User's Life Simpler with Multipart Forms," shows how to use cookies to store persistent data, but cookie technology cannot solve all application development problems. In some cases, you don't want to clutter a user's browser with cookies for temporary storage.

The User Input Catcher

A common problem is handling bad user data. Not all users who come to the site are as thorough as the people who built the site. A place where this is most evident is when you ask users for their personal information (see Fig. 21.3). To underscore how bad the problem is, most of the time when you ask them for their personal information (billing name, address, payment method, and so on) it's because the user did something to cause that to happen! In other words, the user is the one who clicked on the button that asked for a certain item to be added to the shopping basket (in the case of the shopping basket application) and even so, they still might not be thorough enough to make the order (they initiated) complete.

Figure 21.3: This is a sample HTML form accepting personal information from a user. The application in listing 21.5 shows how to write a recursive CGI script that can handle the instances where the user doesn't fill in all the boxes correctly.

That's where a sturdy recursive CGI script can be helpful. The cycle of asking for proper and complete information can loop until the user either gets all the required data filled in or gives up and does something else. Your goal is to make sure he picks the first option and gets all the required information filled in without getting upset. Although the future of online ordering has been dramatized as a wonderful new world to do business, we're not quite to the point where it's easy enough to experience for everyone. It's a lot easier to get out of bed at 2 a.m., drive to the convenience store for a snack, then it is to navigate some Web ordering system.

That may be an exaggeration, but the point is that you should try to make all your user interfaces that request information as simple to use as possible-even when handling cases that do not work well (such as the user not providing the proper data or incomplete data).

In Listing 21.5, you see how to create an application that includes a recursive CGI system for getting customer information and not allowing the user to progress through a sequence until the data asked for is complete. It's not a troublesome task. The user isn't going to feel labored into finishing the form. Usually, the situation comes up by accident: The user just forgets to fill in a blank or types the name of his state instead of the two-letter abbreviation (for example).


Listing 21.5  goodData.cgi-A Recursive CGI Tool for Getting the Proper, Completed Data from a User

#!/usr/local/bin/perl

@INC = ('../lib', @INC);
require 'web.pl';

%Form = &getStdin;

$Me = "/cgi-bin/goodData.cgi";

$txt   = '=~ /\w+/';
$none  = '';
$email = '=~ /\w+@\w+\.\w+$/';
$fone  = '=~ /\(\d+\)\s*\d+\W\d+/';

%exprs = ('fname', $txt,
          'mi',    $none,
          'lname', $txt,
          'addr',  $txt,
          'apt',   $txt,
          'city',  $txt,
          'region', $txt,
          'zip',  $txt,
          'country', $txt,
          'email', $email,
          'dfone', $fone,
          'nfone', $fone);
@requiredList = keys %exprs;

%errmsg = ('fname', 'First names are character strings',
           'lname', 'Last names are character strings',
           'addr', 'Addresses are words and numbers',
           'apt',  'Apartment numbers and letters are just that',
           'city', 'City names are words',
           'region', 'Names of Regions are words',
           'zip', 'Zip codes are letters and or numbers',
           'country', 'Names of countries are words',
           'email', 'Email (Internet style) is user@some.domain',
           'dfone', 'Phone numbers are (###) ###?####',
           'nfone', 'Phone numbers are (###) ###?####');

&beginHTML('goodData application', 'bgcolor=ffffff');

if ($Form{'inDeep'}) {

  %isit = &notComplete(%Form);

  if (%isit) {
    &showErrors(%isit);
    &showMissing(%Form);
    &makeForm(%Form);
  }
  else
  {
    &showOk(%Form);
  }
}
else
{
  &makeForm;
}
exit(0);

###


sub notComplete {
   local(%fdata) = @_;
   local(%errors, $err, $thing);

   foreach $thing (@requiredList) {
      $errors{$errmsg{$thing}} = $thing unless &ok($thing, $fdata{$thing});
   }
   return %errors;

}

sub ok {
  local($field, $dat) = @_;
  eval "return \$dat $exprs{$field};";
}

sub showErrors {
  local(%ermess) = @_;

  print "Ooops, there were some problems. Here's a ",
        "review of the proper format for the\n",
        "fields\n<p>\n";

  print "<table>\n";

  @ks = keys %ermess;

  for($i=0;$i<=$#ks;$i++) {
    print "<tr><td>", $i+1, "</td><td>$ks[$i]</td></tr>\n";
  }

  print "</table>\n";

}


sub showMissing {
  local(%fdata) = @_;
}

sub filledForm {
  local(%fdata) = @_;
}
         
sub showOk {
  local(%fdata) = @_;
  
  print "<h1>Thanks!</h1>\n",
        "All that information will work great.<p>\n";
}

sub makeForm {
  local(%fdata) = @_;

print <<"endofit";

<form method="post" action="$Me">

Name:<br>
First <input type="text" name="fname" value="$fdata{'fname'}" size=30><br>
MI <input type="text" name="mi" value="$fdata{'mi'}" size=2>
Last <input type="text" name="lname" value="$fdata{'lname'}" size=30>
<p>
Address: <input name="addr" type="text" value="$fdata{'addr'}" size=40><br>
Apt: <input name="apt" type="text" value="$fdata{'apt'}" size=10>
City: <input name="city" type="text" value="$fdata{'city'}" size=25><br>
State: <input name="region" type="text" value="$fdata{'region'}" size=30>
<p>
Postal Code: <input name="zip" type="text" value="$fdata{'zip'}" size=12>
Country: <input name="country" type="text" value="$fdata{'country'}" size=30>
<p>
Email: <input type="text" name="email" value="$fdata{'email'}" size=30><br>
Day Phone: <input type="text" value="$fdata{'dfone'}" name="dfone">
Night Phone: <input type="text" value="$fdata{'nfone'}" name="nfone">
<p>


<input type="submit" value="Ok" name="inDeep">
</form>
endofit
}

Let's look at each section of code and show you how it works. You need to grab the HTML form data, so call getStdin function from web.pl:

%Form = &getStdin;

The HTML form accepting data from the user accepts four kinds of data:

Text
E-mail
Phone Number
None

The script needs to check the data for each of these types. The following code is patterned for matching text, e-mail, or phone numbers:

$txt   = '=~ /\w+/';
$none  = '';
$email = '=~ /\w+@\w+\.\w+$/';
$fone  = '=~ /\(\d+\)\s*\d+\W\d+/';

For each input box in the HTML form, the relation between that input box and the kind of data is the following:

%exprs = ('fname', $txt,
          'mi',    $none,
          'lname', $txt,
          'addr',  $txt,
          'apt',   $txt,
          'city',  $txt,
          'region', $txt,
          'zip',  $txt,
          'country', $txt,
          'email', $email,
          'dfone', $fone,
          'nfone', $fone);
@requiredList = keys %exprs;

If the data for a certain input box is "not formatted correctly," then give the user a specific error message that gently reminds him what the correct format is:

%errmsg = ('fname', 'First names are character strings',
           'lname', 'Last names are character strings',
           'addr', 'Addresses are words and numbers',
           'apt',  'Apartment numbers and letters are just that',
           'city', 'City names are words',
           'region', 'Names of Regions are words',
           'zip', 'Zip codes are letters and or numbers',
           'country', 'Names of countries are words',
           'email', 'Email (Internet style) is user@some.domain',
           'dfone', 'Phone numbers are (###) ###?####',
           'nfone', 'Phone numbers are (###) ###?####');

We're done setting up the CGI, let's start by giving the MIME type and setting the TITLE bar and BGCOLOR.

&beginHTML('goodData application', 'bgcolor=ffffff');

The HTML form variable 'inDeep' is set (as a hidden INPUT data type) when the user has already seen a form. It's your flag to process the data from the user. If the HTML form variable 'inDeep' is not set, then just dump the HTML form for the user without checking anything (because there would be nothing to check, it's the first iteration).

if ($Form{'inDeep'}) {
  %isit = &notComplete(%Form);

This associative array %isit is set to the unique error messages that the user should see if there is any problem with the data he entered in the HTML form.

  if (%isit) {
    &showErrors(%isit);
    &showMissing(%Form);
    &makeForm(%Form);
  }
  else

Otherwise, the data is fine and you just give the user the "next" page in the sequence of pages. This is a stub function where you add your custom display routines.

  {
    &showOk(%Form);
  }
}
else

This is the "outer else" clause from whether or not there is any reason to check the data. If the user hasn't even seen the HTML form yet, then the form variable 'inDeep' is not set and you just call the function makeForm to output the HTML form:

{
  &makeForm;
}

That's it. The main program is done. The following is where the functions used in the main program are defined:

exit(0);
###

The notComplete function is defined. It checks each form variable by name against the list of valid form variables and checks to see if the values are consistent with the regular expressions defined at the top of the program for the four types of inputs.

sub notComplete {
   local(%fdata) = @_;
   local(%errors, $err, $thing);

   foreach $thing (@requiredList) {
      $errors{$errmsg{$thing}} = $thing unless &ok($thing, $fdata{$thing});
   }
   return %errors;
}

Basically, ok is a Boolean function returning the condition of the data entered by the user against the regular expression of what the CGI script expects the data to be.

sub ok {
  local($field, $dat) = @_;
  eval "return \$dat $exprs{$field};";
}

This next function showErrors is a display routine that lists all the unique keys of the error array (the list of textual messages that the CGI script has for problems) for each of the items from the HTML form that are not formatted correctly (see Fig. 21.4).

Figure 21.4: This is the page where you show the problems with the data entered by the user. It's a display routine only.

sub showErrors {
  local(%ermess) = @_;

  print "Ooops, there were some problems. Here's a ",
        "review of the proper format for the\n",
        "fields\n<p>\n";

  print "<table>\n";

  @ks = keys %ermess;

  for($i=0;$i<=$#ks;$i++) {
    print "<tr><td>", $i+1, "</td><td>$ks[$i]</td></tr>\n";
  }

  print "</table>\n";
}

When we finally have all the correct data, this is the function that generates the next page in the sequence of pages (see Fig. 21.5).

Figure 21.5: After all the form data is formatted correctly by the user as he repeatedly tries to enter it right, he gets the "thank-you" page.

sub showOk {
  local(%fdata) = @_;
  
  print "<h1>Thanks!</h1>\n",
        "All that information will work great.<p>\n";
}

This is the function that creates the HTML form the user enters data into. If there are "previous" values from the form data before, then those values are filled in for the users so they do not have to retype them a second or third time:

sub makeForm {
  local(%fdata) = @_;

print <<"endofit";

<form method="post" action="$Me">

Name:<br>
First <input type="text" name="fname" value="$fdata{'fname'}" size=30><br>
MI <input type="text" name="mi" value="$fdata{'mi'}" size=2>
Last <input type="text" name="lname" value="$fdata{'lname'}" size=30>
<p>
Address: <input name="addr" type="text" value="$fdata{'addr'}" size=40><br>
Apt: <input name="apt" type="text" value="$fdata{'apt'}" size=10>
City: <input name="city" type="text" value="$fdata{'city'}" size=25><br>
State: <input name="region" type="text" value="$fdata{'region'}" size=30>
<p>
Postal Code: <input name="zip" type="text" value="$fdata{'zip'}" size=12>
Country: <input name="country" type="text" value="$fdata{'country'}" size=30>
<p>
Email: <input type="text" name="email" value="$fdata{'email'}" size=30><br>
Day Phone: <input type="text" value="$fdata{'dfone'}" name="dfone">
Night Phone: <input type="text" value="$fdata{'nfone'}" name="nfone">
<p>


<input type="submit" value="Ok" name="inDeep">
</form>
endofit

}

Figure 21.6 shows the functions that generates the HTML form.

Figure 21.6: If any data was previously entered, it's filled into the next iteration of the form.