Well, actually, sorta. For the most part, I really don't want users entering just anything they want in these pages. From the time they enter basic identifying information through multiple iterations of the entry page, the only time I really want the user to have any choice about the representation of the data that is actually stored is when they enter descriptive text in the event text field. (This really does not mean that I am being a control freak. The operation of a relational database system requires that the key columns that establish relationships between tables hold consistent values, and in the case of non-key data elements that hold important information one aspect of ensuring the quality of that information is controlling user input. While perl is adept at extracting information from strings, this can add overhead to the operation of the system and one facet of optimizing the responsiveness of a system is avoiding such when you can.)
So how do I go about doing that? Well, I already use a set of radio buttons to select the number of forms displayed, and a select box with a scrolling list in the selection of the inning and the half_inning. Radio buttons are nice in certain contexts, but a scolling list does a much better job of facilitating selection from a group of more than four or five items, especially when that group changes. Therefore, that's what I used. Before I get too deeply into that, however, I want to again caution you that several elements of this implementation will change as it is torn apart and re-wrapped into a framework. Some of those changes are likely to be substantial, because the measures I have taken here provide only the barest of requisite functionality. Despite that, there are things that I have done in this process that are worthy of note, both from the standpoint of the techniques employed and because of the perspective they provide on the more advanced implementations to follow.
Obviously, if I am going to be populating a bunch of lists I do not want to be doing that by hand. Furthermore, if you think about the elements being entered you will realize that the values in some of them could vary time or from game to game. (If the interface is set up so the user has to choose from a set a pre-defined entries, these lists must be populated dynamically. Can you imagine selecting the appropriate participant from a list containg all the the players in a ten-team league?)
On to the task at hand. Just to give you a notion of I am setting up, the sel_form() subroutine now produces a page that looks like this ...
While the get_forms() subroutine generates a page that looks like this.
The really cool thing about scrolling lists is the fact that I can use them to associate a description (a name, for example) for the value that is being stored. Rather than having to look up the code associated with a given name, the list associates the two for me. Furthermote, if you start typing in the highlighted select box the browser will read your keystrokes and progressively move to the first element of the list that matches the string that has been entered. If I were to type "r", for example, the highlighted element of the list would be the first beginning with "r". Once I type "a" the focus would move to the "ra";s, and when I add "l" focus would move to the first element beginning with "ral". You have probably noticed this kind of behavior before. As a result, it is possible to achieve very high data entry speed with scrolling lists even though most of us associate them with use of the mouse.
Bart_SimpsonZzZ0001 Lois_LaneZzZ0002 Bill_ClintonZzZ0003As you can see, they follow the standard format I have used for such files, with the string "ZzZ" as a delimiter. In the cgi script I initialize the data structures that hold these values by calling the make_hash() subroutine, a new subrouine I placed in the BB_UTIL module. (Did you notice that I had added that module to the list of modules incorporated into the script?)
make_hash() is generally straightforward.
sub make_hash {
my $file=@_[0];
my ($k,$v,%hash);
open(FH,"< $file");
while (<FH>) {
($k,$v)=split(/ZzZ/,$_);
$hash{$k}=$v;
}
return \%hash;
}
As you can see, it takes as an argument the fully-specified path and name of the file holding the source information, while the subroutine reads into the scalar $file. After declaring scalars representing the key ($k), the value ($v), and the hash to be constructed, a filehandle (FH) is opened on the source file. The subroutine then steps through the file, splitting each line on the delimiter "ZzZ" and assigning the first part to the $k scalar and the second to the $v scalar, then storing the contents of the two scalars as a slice in the hash. When the end of the file has been reached, the subroutine returns a reference to the created hash, which the cgi script stores in a scalar.There are a couple of interesting asides to this. First, if you are following along and running the scripts on a machine of modest horsepower like Ralphzilla, you may have noticed that there is a bit more of a lag when calling the script. If you look back over the changes I have made in the last few revisions, you will realize that I am asking perl to do much more work generating the returned pages than I had earlier. You might say "Well, this is not exactly a high-end box. I'll just use a better machine." You would be right enough, but there are several responses to that. First, throwing hardware at a performance problem has relatively limited efficacy, partially because in and of itself faster hardware only tangentially addresses software bottlenecks. Low-end boxes are also expendable, which means that you can put one somewhere that you would never put a more capable machine and use apache as a way to access the collected data. Further, the processors embedded in devices as controllers are generally less powerful as a result of decisions to hold down power consumption and heat generation. As I have said before, if the application performs acceptably on low-end hardware, it will perform very well on more powerful equipment. There are various ways to benchmark your code in perl to see where it is spending its time, and in due course I will probably go over their use, but using a low-end box as a development platform will definitely tell you when you have put a substantial load on the system very quickly, as it will tell you when you have made a substantial improvement. Soon, I will be venturing into an area, mod_perl, that will result in substantial performance improvements in specifically this kind of circumstance.
Second, note that I call make_hash() six times, returning references to seperate hashes, and that each of the references points to a unique hash even though they were each named %hash in the same scope when they were created. Kinda interesting, huh? (Trust me on this one, that really is the way things are happening. If that were not the case, all those scrolling lists would show only one set of keys, the last generated.) Note also that if the reference (or, for that matter, the hash itself) were not passed back to the calling script, the hash would simply wink out of existence when the subroutine finished. In this case I would not have accomplished anything useful if I allowed that to happen, but there are subroutines in this system that are called simply to accomplish some task and whose attendant data structures are disposable once the subroutine has finished. In fact, as we shall see, there are circumstances in which one has to be concerned with creating data structures that are persistent at a global level, because they tie up memory and system resources. That is not a problem with which I have to deal in the current circumstance, because those structures exist only as long as any given invocation of the script and are created anew each time the script is called. The flip side of that is that such non-persistence is a source of substantial system overhead. Not surprisingly, as I start describing implementations that will reduce system overhead I will also have to start getting concerned with managing that persistence. It just goes to show, it's always something <grin>.
Onward. From this point most of the changes required here are in the BB_INTERFACE module, but I do have to get the pertinent hases to the called subroutine. The initial step in that is right after the referenced hashes are created, where I wrap four of these scalars into an array. I did that primarily because I knew I was going to be passing those four around and that by using the array as a container I would not have to worry much about typos. As I really only pass them once, it is not all that big of a deal, but I have left it as it is. The first time I pass any of the hashes is when I call the sel_form subroutine, passing it the scalars holding the references to the hashes of entry people and games.
The sel_form() subroutine in BB_INTERFACE provides a good introduction to the manner in which I have implemented the creation of these scrolling lists. I initially attempted to implement these lists by using the CGI module's scrolling list method, much as I do the lists that allow the selection of the inning and half_inning in the get_forms() subroutine. When I did, however, I found that the method was somehow losing the reference to the relevant hash when I called it. After messing about with this for a bit I decided to write a little subroutine that simply outputs the html required to display the list. As before when I have run into circumstances like this I have little doubt that the problem is somehow in the manner in which I called the method, but what the hey ... TMTOWTDI. Recognize that not all modules are of the high quality of Lincoln Stein's CGI module. If you work in perl, there is a good chance that at some point you are going to run into a module that does not behave as advertised, or at least not as expected. Do not be afraid to jump in and implement something on your own in such situations. Frequently, you will be able to define what limited your implementation of the module. Always, however, balance the time consumed by the endeavor, the maintainability of the code, and the use of external modules. You can sometimes use some methods from even recalcitrant modules in the implementation of your own custom work.
In any event, my implementation in this circumstance took the form of the creation of the subroutine sel_box() (for select box), which takes as arguments a scalar holding a reference to the pertinent hash, a reference to the CGI object, the name of the cgi parameter being selected, and a text string to preface the scrolling list.
sel_box($events,$forms,'ec',"Event Code:");In the subroutine itself, those elements are read into a series of scalars scoped to the subroutine.
sub sel_box {
my ($hash,$forms,$name,$desc)=@_;
print $$forms->p("$desc<select name=$name size=1>");
my ($k,$v);
foreach $k(sort keys %$hash) {
$v=$hash->{$k};
print ("<option value=$v>$k</option>");
}
print $$forms->p('');
}
The subroutine then outputs to the browser a line initializing the select box, preface by the text I use to describe the specific box being created. The size specified in this line determines how many elements of the list are displayed in the box. Here, as I am concerned with constructing as compact an entry box as possible, that value is set to 1. The subroutine then populates the list itself. As there is no good way to control the order in which perl stores hash slices, the hash itself has to be sorted to populate the list. Sorting a hash can represent a performance hit when the hash is large, but that is not the circumstance here. This step creates an anonymous array, which is iterated over by the foreach loop. Within that loop each element of that anonymous array is used to access the hash slice with that key, and assigns the value in that slice to the scalar $v. The subroutine then adds that element to the list within the <option> tag, with the value held in $v described by the key value $k. After each element has been output and the loop closed, the subroutine closes the select box and exits.So that's the sel_box() subroutine, probably the lynchpin of what makes this revision work. Read it over a few times until you get a good sense of how it works. It is not all that complicated, once you get past reading the statements and can envision operations on structures of data.
Given that, I decided this would be a good one to illustrate with a picture, and also show you the output from the free Visual Thought diagramming software, available from here. The result is at right. As you can see, I have used the three slice sample hash of entry people referenced by the scalar $entry_people. The hash is depicted in the same order as are the lines in the source file, but remember that there is no guarantee that the hash will be ordered in this manner even though the records were read into the hash in that order. As that order is not alphabetic, this will be a good example. As illustrated, the command "sort keys %$entry_people" creates an anonymous array with the names in alphabetic order. (Anonymous means that the array has not been given a name. This is the standard manner in which perl creates temporary structures in situations like this in which a single line of code involves several operations. Such statements are part of the perl idiom, some people use them frequently and others hardly at all. I personally tend to use them infrequently, and have tended to avoid them in this document for the simple reason that I think that they tend to make code less readable. Regardless, there are certain operations you may want to undertake in perl that literally cannot be done in any other manner, and this is one of those circumstances. The upside to this is that as you acquire familiarity with the idiom in circumstances like this, you will feel more comfortable incorporating such statements in your own code, if you so desire.)
In any event, this give me an array of elements in the order I wish. The foreach() loop iterates over each element in that array and uses that element to access the hash slice keyed to that string, storing the slice value to the scalar $v. In the accompanying illustration I have represented each of these lookups in somewhat extended fashion. It is these values, of course, $k representing the descriptive key and $v the code to be stored, that are then used to populate the scrolling list displayed by the browser in the select box.
So there you go. The sel_box() subroutine is called twice within the sel_form() subroutine, to generate the scrolling lists for the select boxes from the username and the game id.
The get_forms() subroutine, however, uses sel_box() as a building block. If you think back to one of the early incarnations of the get_forms() subroutine, I made a comment to the effect that the subroutine was a prime candidate for further breakdown, but at the time I let it stand as it was. I am going to do that now, and with your growing awareness of perl you probably know exactly the steps I am going to take. (Just a hint ... as each entry box is generated by the same set of commands, I have pulled that set out and put that functionality into the subroutine base_form().)
sub base_form {
my ($events,$role,$participant,$result,$forms)=@_;
sel_box($events,$forms,'ec',"Event Code:");
sel_box($role,$forms,'rc',"Role Code:");
sel_box($participant,$forms,'pc',"Participant Code:");
sel_box($result,$forms,'erc',"Event Result Code:");
print $$forms->p('Event Text:<input type="text" name="et" size="25">');
}
As you can see, base_form() accepts the four scalars holding references to the hashes pertinent to event, role, participant, and event result in the argument array along with the reference to the CGI object. The subroutine then simply calls the sel_box() subroutine four times with the arguments appropriate to each of the select boxes, and prints the text box for entry of whatever free-form text is used to describe the event. Within the get_form() subroutine itself, base_form() is called within the structure used to format the page, as in the snippet below:
while ($i <= $num_forms) {
print $$forms->p('<table border width="85%"><tr><td>');
base_form($events,$role,$participant,$result,$forms);
print $$forms->p('</td>');
$i++;
print $$forms->p('<td>');
base_form($events,$role,$participant,$result,$forms);
print $$forms->p('</td>'),
('</tr>');
print $$forms->p('</table>');
$i++;
}
That's it, really. I could probably drop another level of abstraction in that specific section, but as there is no compelling reason to change it I think I will let it stand as it is.There is, however, one other thing I should point out. It is not a big thing, but it is the kind of thing that can represent a difficult-to-track problem, as least until one acquires the knack of looking to the interaction of disparate elements as the source of problems. If you recall, the store() subroutine in BB_INTERFACE.pm checks for a null event code value to determine whether the specific record should be written or not. This is what makes the submission of partially-entered page easy to deal with. In the example above, the scrolling lists have no blank values, so even if a user were to explicitly not select a value, the value associated with the first element would be in the parameter list as, in effect, the default, and a record of default entries would be written to the external file and ultimately stored to the database. There are, of course, a number of ways to deal with this.
(You may be getting tired of hearing that, but once you start juggling different ways to reach a given end you will be at the point at which things start to get really fun. I once read a comment from someone who described perl as the "ultimate game", and I think that is a very good mindset to maintain. Blasting something or solving a puzzle can be good relaxation, but perl_the_game never ends. There is always a new puzzle, always another level to attain. Further, when you create a code structure you have done something real. Even those things done whimsically, like japhs, broaden the perspective you bring to more serious tasks.)
In this specific context, given that later implementations are going to incorporate somewhat more sophisticated methods of providing the material that is used to generate the lists, I took the simple expedient of editing each of the four source files and putting an empty line after the last line that held data in each one. Note that this is not the same as a line with spaces, this is a line with nothing on it but the end of the line marker. Given the presence of those lines, when the files are read by make_hash() a slice is created in which both the key and the value are empty, and as the key is empty it is the first element in the list and by default the default selection. As the value of the hash slice for which the key is an empty string is also an empty string, that is what is stored in the paramter and that entry gets skipped in the storage process as intended. Everything is just hunky-dory.
In the next section I am going to take another big step in our continuing quest and start implementing the application in mod_perl. Mod-perl is the term generally given to running a version of the apache server that is compiled with a perl interpreter embedded within it. As a result, accessing a cgi script served by an appropriately configured server is substantially faster than with the vanilla apache server, which has to invoke the perl interpreter with each invocation of the perl script. For reasons that I'll get into as I move along, most of the site frameworks based on perl prefer, if not require, that the server incorporate mod_perl.