(The technique employed here is based on that described on this page. The implementation in this chapter will be more simple than the one descibed on that page, but in upcoming chapters the basic approach will be extended in a fashion customized to this context.)
The general strategy followed when using javascript to populate a select box with a set of items determined by a selection in another select box involves using an array as the source of the items for the target box. The author of the page referenced above created a two-dimensional array that stored all of the potential items for the target select box, using the first-level array index value to reference the items pertinent to a given context. While I am likely to employ a similar structure in later chapters, in this initial version I will use a simple one-dimensional array that I populate with appropriate values when the context is determined.
The context for the operation of the select boxes is, of course, determined by an action specified in the html that defines the first select box.
$$r->print("<form name='abe'>");
$$r->print("<center><select name='abe1' onchange='dosel()' size='4'>");
$$r->print("<option value='1'>Ball</option>");
$$r->print("<option value='2'>Strike</option>");
$$r->print("<option value='3'>Batted Foul</option>");
$$r->print("<option value='4'>Batted Fair</option>");
$$r->print("</select>");
$$r->print("<select name='abe2' size='4' style='width:200;'>");
$$r->print("<option>Subcategory</option>");
$$r->print("</select></center><br>");
In the snippet above I define a form named abe (for at bat event) that contains two select boxes, abe1 and abe2. The items listed for abe1 might be considered as the basic events for an at bat: ball, strike, batted foul and batted fair. Everything else associated with an at bat is a further specification of those four base events. (I will probably put hit by pitch in that group in a later version, but almost everything else that would at first blush be considered to be an at bat event is actually associated with an event on the base paths. Probably the best examples of this are wild pitches and passed balls. These events are, by definition, reasons for runner(s) advances. If no one is on base, these events never happen; the event is just a ball (generally).) In the definition of abe1 I specify a function to be executed as the result of a change of the selection in the box with the onchange argument, in this case specifying the execution of the dosel() function, which is what effects the modification of the items in abe2. (Note that I have not constructed these select boxes using sel_box(), at least at this point, because I currently regard this as a relatively specialized use of select boxes. Further, as the target select boxes are initially specified with only skeletal contents, the benefits associated with automating the construction of these select boxes do not outweigh the effort involved for the requisite customization of the subroutine.) The width specification in the definition of abe2 probably looks unfamiliar. When select boxes are rendered by a browser, their width is automatically adjusted to the width of the widest string of text to be displayed in that box. In practice, that means that as different selections are made in the first box the width of the second box will oscillate back and forth, adjusting to the strings displayed. The example on the link at the top of this page behaves in that fashion. In the case of the page I am developing here, once I have three or four selects strung together to accomodate my progressive selection mechanism, such behavior would represent a jarring input experience because the target area would shift around as different options were selected in previous select boxes. The task of providing a stable width in a manner that is compatible across different browser environments using straight html is not possible, as no convention for such has been widely adopted. The way around this is to employ just a bit of the the cascading style sheet template that controls select box display, which is the source of the unfamiliar form of the style argument in the definition of abe2. (I am not going to get any deeper into CSS at this point, but in this instance there was literally no other choice.)The onsel() javascript function itself is relatively straightforward. (Note that I have indented lines in the function to improve its legibility. Since my initial javascript implementation of a couple of chapters ago I have experimented with the formatting of the here document, and have determined that it is the beginning and the end of those documents that are the locations that are sensitive to formatting issues.)
function dosel() {
var abe_opts=new Array();
var choice=document.abe.abe1.value;
switch (choice)
{
case "2":
abe_opts=['1','Called','2','Swinging'];
break;
case "3":
abe_opts=["3","In Play","4","Out of Play"];
break;
default:
abe_opts=["","No Subcategory Required"];
}
if (! choice) return;
document.abe.abe2.length=(0);
for (i=0;i<abe_opts.length;i+=2)
{document.abe.abe2.options[i/2]=new Option(abe_opts[i+1],abe_opts[i]);}
}
In the first two lines I instantiate an array named abe_opts, which will hold the values and options pertinent to a given selection, and a variable named choice, in which I store the value of the selected option in abe1. It is important to recognize that a javascript array is an object, while in perl an array is a simple list. A javascript array object has associated methods and properties that expose capabilities that have to be independently derived in perl. As an example, albeit a rather limited one, the length of the abe_opts array can be determined by reading its length property, as it is in this function to determine the upper bound on the execution of the for() loop, while the length of a perl array is determined by evaluating the array in scalar context, as in $length=@array. There will be other circumstances in which the advantages of this kind of approach is more apparent. (The downside to object oriented constructs is that they do require more computer resources. For example, the process of instantiating the javascript array calls a great many lower-level statements to create the structure represented by a javascript array, while for the creation of a simple list of items all perl has to do is assign blocks of memory. Object oriented structures can be implemented in perl, but that is a subject for another time.)Following the declaration of these variables is a switch construct that functions in much the same manner as does a perl switch, but with a slightly different syntax. Here the expression that will be evaluated in each of the cases, in this instance the value of the choice variable, is specified as an argument to the first line of the switch. The individual case definitions then specify only the value of the specified argument that is meant to trigger execution of the statements specified for that case. Note that the individual case statements are followed by a colon (:).
Within the individual case statements associated with the values "2" and "3", representing the selections of "strike" and "batted foul", respectively, the abe_opts array is filled with the values and options pertinent to those specific selections. The break statement following the population of the array in each instance is used to terminate processing of the switch. Worthy of note here is the fact that, in javascript, values being assigned to an existing array are enclose by square brackers ("[]"), while when assigning those values during the process of creating the array one encloses the values within parentheses. The final part of the switch statement, the default section, defines which actions are to be taken if execution of the switch has continued to the end of the switch, in this case the filling of the abe_opts array with two elements, the first null and the second the string "no subcategory required". In other words, in this particular switch, if the selected option in abe1 is either "strike" or "batted foul", the abe_opt array is filled with options that further describe those events. Otherwise, the array holds a string that will tell the user that the first selection represents all the information required.
Following the switch construct the script sets the length property of the abe2 select box to 0 to make sure that it is cleared before new values are written into it. The function then uses a for() loop over the elements in the array to add the new options to abe2. The loop grabs two elements at a time (i+=2), representing a value and associated option. Each set is added to the set of options in the target box in the following line. Here the index value specifying the appropriate option is defined as i/2, because with each set representing two elements the value of i must be divided by two to indicate the appropriate position of that option in the select box. Also worthy of note here is the fact that the javascript new option constructor employs the somewhat counter-intuitive convention of taking (option,value) as arguments rather than (value,option), which would correspond to the item order in an html option specification. This is probably because not all select boxes use associated values, which renders this form more usable that one which required one to remeber to specify a null value before the option in appropriate circumstances.
Here is a working version of this little dialogue.
|
Before I go on I want to briefly mention an aspect of javascript's object-orientation that could become pertinent at some point. If I were to replace onchange='dosel()'with onchange='dosel(this)'in the definition of abe1 the set of form information would be passed to the function. If the function were declared as function dosel(thisform)I could reference the value selected in abe1 as thisform.abe1.valuerather than document.abe.abe1.valuewhich is kind of neat. While this is of relatively limited utility here, imagine for a moment that I was building a variable set of select boxes in a fashion analogous to the way I have at times used edit_table(). In concert with the method of addressing page elements by their index values rather than by name (e.g., form[0] rather than abe), this would represent the basis of the iterative structure I would implement to accomplish such an end. In the next chapter I will extend this model to implement this progressive selection model for the various components of the data entry interface. Next - Progressively Selecting Events |