Now that I've covered a basic data entry environment, I'm going to start wrapping what I've done in a somewhat larger structure that more closely resembles the kind of environment you would normally associate with a data entry application. In this section I'm going to pay relatively less attention to specific code statements, concentrating somewhat more on the structure of the script, and how it works withing the CGI framework of submittal and response. Something like 75% of the code that is in this script is in the examples I've already walked through; what is different about this one is that structure. The key to remember about using CGI scripts to dynamically generate pages is that the script is both sending a web document to the client browser and accepting input from that page. You might say that pretty much any program that involves a user interface does that, on one machine if not between machines. What makes CGI programming just a bit tricky is its stateless nature. One of the primary advantages of developing dynamic pages with a script like this is that it is relatively easy to maintain ... everything is right there in the script, or referenced there. In effect, the logic that would be part of individual html documents and CGI scripts are placed under the umbrella of a single script. This is one facet of why the management of state is so important in CGI programming. In a standard data entry context like the one this script constructs, the sequence of actions in a single session would be something like this:
1) user's browser sends the http request to the web server, specifying the cgi-bin directory and the name of the script. (On my machine, in the local network environment, the address of any script would be simply http://ralphzilla/cgi-bin/scriptname.cgi, where scriptname would be the script being called, since ralphzilla is in my local hosts file.)
2) Web server receives request, and starts perl, running the script. Since nothing is appended to to the request, when the script parses the request it finds no attached parameters, so it executes the subroutine that sends back formatted html that draws whatever is appropriate for the initial connection.
3)The user interacts with the page, taking appropriate action, and clicks on the submit button. The data elements within the defined form are sent to the web server, by default in a string appended to the url as discussed in the previous section.
4)The perl interpreter invoked by the web server parses the string, and responds based on that submitted information.
This conversation could go on forever, as long as the browser kept calling the script with appropriate parameters. Generally, in a data entry context such as this, the script will iteratively execute a small set of subroutines over and over during the conversation.

So on to this script. Just to give you a sense of what the conversation is like here, the first time it is called by a client the script produces the page at right, which allows the user to select the number of forms to be displayed on a single page.
Once the user has selected that value and clicked on the submit button, the web server responds by returning the html to draw the entry page. This will take the form of either the example to the right, which you saw in the previous section, the page that would be generated if the user were to select eight as the number of forms to be displayed ...

or this page, something similar to which would be generated if the user were to select any number of forms other than eight.

After the user submits the forms, the page to the right is displayed, confirming the number of records that were written. Once the submit button is clicked, the entry page is redrawn, with the selected number of forms displayed on the page.
You might want to download the tar file for this set of examples and grab this script (full.cgi) from the archive so you can refer to it as you desire.
|
#!/usr/bin/perl -wT ##in essence, this script creates a scorekeeping interface for the baseball database BEGIN { # Set the DISPLAY variable to the name of the local machine # where the debugger window and web browser appear. $ENV{DISPLAY} = "mymachine:0" ; } ##set up modules and pragmas use strict; use CGI qw (:all); use FileHandle; ##instantiate a new CGI object and retrieve key parameters ... if there is no action paramter, assign "start" to the @action scalar my $forms=new CGI; my $action=$forms->param('action') || 'start'; my $num_forms=$forms->param('num_forms'); ##depending on the value in the $action scalar, either select the number of forms to be displayed, display a page of forms to be entered into, or write off the ##values that were entered into the form. &sel_form if ($action eq 'start'); &get_form if ($action eq 'enter'); &store if ($action eq 'store'); |
If you look through the first few lines of the script, you'll see that it begins much as do the previous samples. That's a little bit misleading ... the main body of this script contains only about 20 lines, including comments and white space, that generally construct the environment for the script as a whole. This main body is used as an event handler, reading the environment and passing control of what is done and what is directed back to the browser to the appropriate subroutine (those chunks of code that begin with a sub and the name of the subroutine, and that are enclosed by curly braces ({}). There is nothing mysterious about subroutines - they are structures of code that perform some conceptually well-defined function. Sometimes the subroutines perform routine actions that you know you are going to use throughout the application. For example, in other contexts I've defined a subroutine that does nothing more than remove white spaces from the beginning and the end of a string. That can be done quite easily with Perl's regular expression matching capability, and there are those who feel that simply doing the regexp (the shorthand way to refer to a regular expression match) is just as legible as defining a subroutine that does the same thing, but at the time, and probably even now, I felt that an appropriately defined subroutine produced more legible code, especially because in that context I was going to be doing a lot of them. The modules used in Perl scripts essentially consist of collections of subroutines oriented toward a specific set of tasks associated with a given area. (In object-oriented parlance, subroutines are referred to as methods. In most ways, the difference is not a whole lot more complicated than that. We may get into that in more detail at some point later.) I am ultimately likely to turn our scripts into a module. In other circumstances subroutines represent chunks, like these, that are executed conditionally. In all circumstances, the primary purpose of subroutines is to increase the legibility and maintainability of the code. As you familiarize yourself with this script you'll see that it could be structured with those chunks defined as subroutines included in conditional statements in the main body of the script. Doing that, however, is much more difficult to read and maintain. There is a term for doing things that way - it's called writing spaghetti code, which refers to the fact that it is difficult to unravel ... you can't tell where it leads. I've written some spaghetti. We all do. In fact, it is possible to make more use of subroutines than is appropriate, and to "spaghetti-ize" your code by losing all sense of what is going on by sending execution through level after level of unnecessary subroutine calls. Programming can be a lot like writing - it's just as possible to lose the reader by being too esoteric as it is to lose them through incoherence. So am i doing that? Back to the script. The parameter that controls the script is named action. The first step the script takes after instantiating the CGI object $forms (instantiate means create a new instance of the object class, kinda like saying $sammy_sosa=new baseball_player();) is to retrieve the value of the action paramter from the CGI environment and put it into the $action scalar. If there is no action paramter in the environment, this is assumed to be the first pass through the script and the value "start" is assigned to the $action scalar. (I'll get to how the action parameter gets into the environment in a little bit.) In a sense, the next three lines are the heart of the script, because in these three lines execution is dispatched to the appropriate subroutine. These lines are the traffic cop, or the railroad yard switch if you prefer that metaphor, of the application. If the value in the $action scalar is "start", execution of the script is sent to the sel_form subroutine, which sends back to the browser the html for the page that prompts the user to select the number of entry forms they wish to have displayed on a single page. If the value of the $action scalar is set to "enter", execution is sent to the get_form subroutine, displaying the selected number of forms on a page. To this point, I've used the term "form" to refer both to the html/cgi form and to an individual set of data elements grouped together on the form and representing, essentially, one row on one table in the database. (Although these entry forms could just as easily represent a composite record drawn from several tables ... the destination or source of the values in the form elements are not germane to this current discussion.) The html/cgi form is a construct created by the W3C html specifications to represent an area in the browser pages that is used to exchange data between the browser and the server. In that sense it is less concerned with a form in the sense of a data entry tool that it is with defining "form-ness". To use another analogy, this State has a body the purpose of which is to anoint the "form-ness" of any state form, rendering it official. The html/cgi form is like that, less concerned with what's being moved around that that it can be moved around. The data entry form, however, is explicitly concerned with what is being moved around, and is probably a great deal more like what you are used to when you think about a form. Just keep the distinction straight in your own mind, and you won't have much difficulty. Should the $action scalar hold the value "store", execution will be directed to the store subroutine. (Makes sense, huh?). Currently, this subroutine simply writes the entered records into the a comma-delimited external file as described in the preceding section, and generates a page confirming the number of records that have been written. |
The manner of execution of each of the subroutines is discussed in nore detail in the following sections
We've now got a working structure that loops on itself, with each iteration writing the entered records to the file /home/www/recs. Run it a few times, pausing before you click on the submit button on the final page to take a look at that file. (If you don't pause there you won't see it, because if you look back at the code for the get_form subroutine you'll remember that one of the first things we do when we go through that subroutine is to unlink that file.) In the next iteration of the script we're going to actually store the data, and do a few other things to make the operation of the script more bullet-proof ... ah, maybe in these politically-correct days I should say more stable <grin>.