Implementing the entry loop within this stacked handler framework is an illuminating process, involving modifying the subroutines in the BB_APP_INTERFACE module in a fashion corresponding to the changes implemented in the previous section, as well as other changes that extend the adaptation of the subroutines in the module to the Apache API as implemented in the Apache module. It is worthy of note that this process would be considerably eased if I were to accept the overhead of using the methods embodied in the CGI.pm module. That overhead can be perfectly acceptable in some circumstances. A mid-range server could service several clients with this framework using the CGI module without missing a beat. That level of hardware is not, however, assumed in the model I am employing here. I am using surplus equipment specifically because it is surplus. If a reasonable expenditure of effort can extend the functionality of equipment considered expendable, then the contexts in which applications can be employed are extended. But beyond all that, not using the CGI module forces me to delve a little more deeply into the inner workings of mod_perl and Apache, and it is that kind of thing that helps to extend skills beyond the mundane.
The first thing I had to do as I got into this process was to get straight on what I was passing into the subroutine, and where I was getting it from. If you have been here right along, you know that I spent some time on that as I developed the CGI-based entry system, developing a structure in which a single large hidden field was used to pass many of the values required for system operation between invocations of the script. At the time I commented that as a number of those values could conceivably change in any given invocation, thus requiring that the value be retrieved as a parameter on the next invocation of the script, the utility of the single large parameter was limited. (Actually, I could of course work around that, but it would take an unusual circumstance in which a large number of infrequently-modified parameters had to be passed from one invocation to the next to justify the overhead associated with the application logic associated with parsing the large parameter string and comparing the extracted values with other values to determine which is appropriate.) In the current implementation I have already pulled user_name and num_forms from the set of items that had to be passed around in this manner by using cookies to maintain those values, so I decided to simply pass the remaining four or five parameters around explicitly rather than going to the trouble of wrapping them together and pulling them back apart again. Now that I have used a single large parameter field, however, if it becomes pertinent I can implement something like that again. On the other hand, this is perl, and there's more than one way to do it. If you liked using the get_state() and write_state() subroutines, feel free to incorporate them into this structure.
Regardless of all that, in this implementation the entry process is started if the $user_name scalar is visible to the body subroutine in BB_STACKED.
elsif ($user_name) {
if (! $params{'session_id'}) {
($session_id,$pass,$inning_order,$inning,$prev_half_inning,$half_inning)=get_session();
}
elsif ($params{'session_id'}) {
$session_id=$params{'session_id'};
$inning_order=$params{'inning_order'};
$inning=$params{'inning'};
$half_inning=$params{'half_inning'};
$prev_half_inning=$params{'prev_half_inning'};
$pass=$params{'pass'};
}
@passed=($session_id,$pass,$num_forms,$game_id,$inning,$inning_order,$half_inning,$prev_half_inning);
get_form(@passed,\$r,$events,$role,$participants,$result);
}
As you can see from the included snippet, if the $user_name scalar is present
the environment is tested for the presence of the session_id parameter. If
that parameter is not present the get_session() subroutine is executed,
establishing initial values for the several data elements that are used to
track the status of a given entry session, and if the parameter does exist that
same set of parameters are pulled from the %params hash. At this point those
elements are used to create the @passed scalar, which is passed to the
get_form() subroutine along with a reference to the request object and the four
references to the hashes used to populate the select boxes.
The data entry scenario I am going to implement here is, of course, a very close analog to what was implemented in the version of the entry system implemented purely as a cgi script. One of the primary differences between that version and this one is reflected in the absence of the action parameter through which the cgi-based application controls its execution. I could have continued to use that value, but as you will see shortly the navigational model employed here provides another alternative.
Within the get_form() subroutine itself, the primary modifications are to the form of the print statement, both in the manner in which output is directed to the request object and in removing the more problematic multi-line print statements. A primary rationale for passing a reference to the request object was to maintain as much similarity between the two versions as possible. Similar modifications, of course, have been made to the base_form() and sel_box() subroutines. I have also made a modest change in the if statement that determines whether the given form should be printed on the left side of the page or the right if fewer than eight forms per page have been chosen. Rather than explicitly comparing the value of $test to 0, I simply test for its existence since in the way perl evaluates truth ($test = 0) => (! $test) . Although the truth value of the two statements is the same, the form used now is more elegant and generally considered the preferred form. You may also note that this version does not display the little image that has been used in previous versions when the forms are staggered. This has to be handled a little differently in mod-perl and I don't want to raise that issue at this point. I will, however, be getting to it soon.
I said above that I have adopted a different method of controlling the looping behavior of the system when I want it to loop through a series of data entry screens. Obviously, this was important in the cgi script, the thing simply would not have run without the action parameter to establish the next action as appropriate. In the current context, however, that control is even more important. I am going to have a lot of things hanging off this structure, and the first requirement of a user interface is that it behave predictably. There will later be choices that will have to be made regarding what should happen under given sets of circumstances, but at this point I just want to get the looping construct functioning within the system. The strategy I adopted to accomplish that here is to use the action directive in the html form definition, which allows the specification of a URI to which the form data will be sent upon submission. In the get_form() subroutine that statement is therefore
$$forms->print("<form action='/bb_app/store/store'>");
and the corresponding line in the store() subroutine is
$$forms->print("<html><form action='/bb_app/system/4'>");
Once the form is submitted, it will send a URI to the server just as happens
when a menu item is selected. In the case of these forms, however, a
substantial chunk of information will be tacked onto the tail of the URI. Once
the URI is parsed, of course, the string that is tacked onto the end of the URI
will be split between the assumed file name, the additional path information,
and the parameters, and the pattern matches in the body() subroutine against
that path information will direct execution appropriately. If the store()
subroutine has submitted the form the associated action will, of course, result
in the get_form() subroutine being executed. If the get_form() subroutine
generated the page that submitted the form, the path information will include
the string "store", which will of course result in the execution of the store()
subroutine.
It is in the store() subroutine that the more challenging changes were required, largely because dealing with the submission of pages with multiple copies of the same data elements represents one of the more technically-challenging elements of browser-based applications. One result of that is the relative infrequency of similar pages on the internet. That's because it is generally easier to have the user submit an individual form many times. (To be fair, implicit in the environment in which I envision this application as running is greater support availability than is generally the case for a web application.)
In the cgi script implemented previously, multiple values assigned to a parameter of the same name are returned by the param() method as an array, individual elements of which I access through their index values (their positions in the array). Obviously, since one of my design goals here is to avoid the use of the CGI module, I have to set up an alternative. Unfortunately, the alternative method of retrieving parameters I have used to this point, using the args() method of the apache request object, does not parse the parameter string with the same level of sophistication as does the param() method of the CGI module, and attempting to access multi-valued parameters through a hash filled with the output of that method will simply return the final values of a parameter with that name. To actually get all of the values of a given parameter into an array, as the current configuration of the store() subroutine requires, I have to go beyond a standard use of the args() method. There are two ways of doing this that are most frequently used. I experimented with both as I worked to get a data structure that can be used to write records appropriately, as I will detail shortly, so I have incorporated both into the early part of the store() subroutine to illustrate their implementation.
The most simple way to get this done involves the use of the Apache::Request module, which provides essentially the same functionality as the CGI module's param() method. To implement this alternative I would incorporate the library into the BB_STACKED module ("use Apache::Request;"), and then instantiate a new Apache::Request object, either by reading in the request directly with shift
my $r=Apache::Request->new(shift);or by subclassing a previously-defined Apache request object.
my $req=Apache::Request->new($r);At that point a multi-valued parameter can be read into an array just as I did with the version implemented with the cgi script. Given that that is pretty easy, some may wonder what I'm making such a big deal about. Well, just because it may appear that the two resultant structures should contain the same stuff, that doesn't mean that they do. Bear with me, my meaning will be apparent. Further, it is simply a good idea to periodically stretch to an implementation slightly more low-level than that which is enough to make things work. (Given that lower-level generally represents more hand-rolled, complex code, does this violate the tenet of not making things more complex than is required to get the job done? No, not really. (You knew I was going to say that, didn't you?) First, I am not saying that the lower-level code should necessarily be implemented. In some circumstances, sure, but in others it could create problems at later development stages. Second, it is not appropriate to automatically remove functionality from the application because it is difficult to implement. Sometimes, the potential gain is not worth the additional complexity, but one is better able to judge that trade-off with a greater appreciation of just how much is going to have to be done to add the functionality in question.) Generally, this kind of experimentation allows the manipulation of variables in a controlled environment where inputs and outputs are isolated and understood. This is an excellent way to extend both awareness and skill.
Back to brass tacks. The construction of the custom data structure to house the parameters begins with the assignment of the output of the args() method to the array @args, creating an array comprised of matched pairs of parameter names and values.
my @args=$forms->args;
my %params;
while (my($name,$value) = splice @args,0,2) {
push @{$params{$name}}, $value;
}
After declaring the %params hash (so named to avoid confusion with the param()
method of the request object), the subroutine enters a while loop that uses
the splice command to take each of the sets of parameter name,value and place
them in a custom construct, a
hash of arrays
, one of the standard ways in which perl stores one-to-many relationships in
memory. The only line within the while() loop takes the $name,$value pair
pulled from the @args array, and pushes the value onto the end of an array
referenced as the value in the hash slice for which the parameter name is the
key. A few lines down, arrays named for each multi-valued parameter are
constructed by accessing the array referenced by that hash key.
@ec=@{$params{'ec'}};
@rc=@{$params{'rc'}};
@pc=@{$params{'pc'}};
@erc=@{$params{'erc'}};
@et=@{$params{'et'}};
Now I just wrote that, but that's not to say that I would not have to read it
four or five times to have any hope of visualizing what is really going on.
Hence, this illustration.
Now I am to the point at which I will start looping through these arrays, writing records. (Ah, but there is a small problem here. Can you spot it in the illustration above? I will give you until the end of this section to spot it.) When I wrote these records in the cgi script I determined whether or not to write the line based on the presence or absence of an event code value. After all, if there is no event there is nothing to record, right? Unfortunately, that implementation doesn't work here, althought it seems like it should. There must be some slight difference between what is stored in the @ec array as I constructed it here and what is constructed by the CGI module. Rather than pursuing that track, I decided to derive an implementation that would be generalizable to both situations. As I describe that process it is going to sound very straightforward, but it was not that way as it went on. I travelled numerous side paths during the process, although that was in part occassioned by my desire to traipse all over the turf rather than just put together something that worked.
It is possible, of course, to construct an implementation here that is dependent on the manner in which parameters are retrieved from the request object. If the parameters are retrieved from an Apache::Request object, the strategy employed must work with the data structure that is returned by the param() method of that object. If, however, the alternative method described above is employed, I can short-circuit that process and only write elements to that structure that are not blank. That, of course, would lead to the creation of arrays that had only non-blank records. If a given data entry person had a propensity for submitting partial records, or if in practice it were to be found that entry people frequently submitted partial pages to "clean the slate", as it were, adopting this strategy would result in a marginal performance gain, bypassing the execution of several steps at the price of one additional step always executed. This circumstance is a good illustration of both the benefits and the costs associated with going "lower-level". There is no question that such an implementation would be slightly faster, both from not adding records to the hash of arrays and not having to do the validity verification when the lines are written. On the other hand, the code would be more difficult to maintain, and would have to be extensively documented internally in order that someone unfamiliar with it could understand what was going on without having to spend a great deal of time figuring it out on their own. As I move forward I may base a later implementation entirely on this custom solution, but to preserve the sense of an evolutionary transition I will leave that verification at the writing process for now.
The solution I have adopted at this point, and which exhibits the same kind of behavior as did the CGI-based version, is to do a pattern match on the event code value rather than simply check for its existence. A deceptively simple solution, the statement
if ($ec =~ /^\S/)simply checks to make sure that the event code value is not whitespace (blank). Specifically, this expression looks for a nonwhitespace value in the first postion of the array element, that's what the caret (^) means. A slightly more sophisticated version would check to make sure that the value was composed of a valid event code value. That is not really required here, as the entered value is coming from a select box I am already determining the set of possible entries.
So that's it. Drop the samples into the appropriate location, restart apache, and once you select a user name you can bomb along entering records just as before. In the next section I will look at more meaningful ways of having the user sign in, along with some other odds and ends, before I start working on more functionality to hand off this structure.
Oh, wait a minute! Did you spot the problem? If not, look at the values in the @pc array in the illustration above. Those are names in there, not codes, which means that somehow the wrong stuff is coming out of the select box in which the participant is selected. In this circumstance, the explanation is almost trivial. I had inadvertently deleted the text files that are the source for these boxes some time ago, and restored them from the spot in which I had originally created them. I did not, however, realize that the version of the participant_code.txt file that was in that location had not been changed to reflect the requirements of the manner in which the select box is currently constructed; it was still an earlier version in which the first and last names of the participant were separate entries, and when the lines in the file are parsed in the make_hash() subroutine, the value for the last name is treated as the participant code. At some point I may make that fancier, but at the level of the entry of a given game the number of possible participants is limited enough that it is not a real necessity, although I may change the list so the last name is printed first. The point of this illustration is that much of the time it is possible to spot potential problems with one aspect of an application by keeping your eyes open while you are doing something else.