http://qs1969.pair.com?node_id=272814

MrCromeDome has asked for the wisdom of the Perl Monks concerning the following question:

Greetings,

A dear friend of mine has recruited me to assist her with a project for the community college she works for. The end result of the project is a several page web application that tests a student's abilities to perform basic tasks on the Internet (filling out forms, following links, e-mailing, etc.). I pretty much have a handle on the project, but one piece of it is still managing to elude me.

A sample of the form that is giving me a headache can be seen at http://www.cromedome.net/test01.html. The "time matrix" is to be filled out by the student - submitting of the form takes adds that information to our database and presents the student with the next part of the application. The problem is that I'm trying to come up with the most efficient way of generating and extracting information from that form (the form must remain in that general format). Here's what I've come up with, but none of these options seem too appealing at present:

1) Create a checkbox for each cell of the table. Have one parameter for each checkbox on the form. Manually check each one of these checkboxes and update the database with the appropriate information. This is obviously the method I like least.

2) Create a checkbox for each cell of the table. Iterate through the list of all parameters to our script, and extract information for only those parameters that are checkboxes. Update the database with the appropriate information. Seems much more manageable than #1, but there's still the tedium of creating each individual checkbox on the form.

3) Use Perl and CGI.pm to programmatically generate groups of checkboxes, one group for each day. This seems a slick way of doing it, but how can I produce a checkbox group where each checkbox is in it's own table cell? The only way I know how to do something similar would be to create one checkbox group for each timeslot, then use the linebreak property to print the checkbox for each day on it's own line. But that seems cheesy.

In all the Perl and web programming I've done, I've never really had to work with this complex of a form before. I guess what I'm looking for is any insight that you might have for me. What's the best way to construct this form? What's the best way to extract the parameters that submitting the form will produce? I'm not the person who will ultimately be supporting this, so simpler is definitely better.

Thanks in advance,
MrCromeDome

  • Comment on Converting large numbers of checkboxes to small number of params

Replies are listed 'Best First'.
Re: Converting large numbers of checkboxes to small number of params
by eric256 (Parson) on Jul 09, 2003 at 20:36 UTC
    I'm not sure i understand what you mean when you say "group" but creating those rows of checkboxs shouldn't be too rough.
    my @Days = ("Monday","Tuesday","Wednesday","Thursday","Friday","Saturd +ay","Sunday"); my $html; foreach my $Day (@Days) { $html .= "<tr>\n\t<td>$Day</td>\n"; foreach (1...15) { $html .= "\t<td><input type=checkbox name=$Day$_></td>\n"; } $html .= "</tr>\n"; }
    That generates code almost exactly like the current code. Of course you need to add the beginning of the form, and the table headers, a any formating options.
    note: i added the \n and \t just for display purpouses they can obviously be removed.
    update: You can remove the $_ from the naming of the boxes to have them be 'groups', i'm not sure how that effects how they are passed though, and you need to add values to distinguish between them :-)

    Eric Hodges
      When I mean checkbox group, I was referring to the checkbox_group() method within CGI.pm. I like your solution - doing it that way hadn't quite occurred to me. I don't usually like producing HTML without CGI.pm, but this time thinking outside of the box can be a good thing.

      Perhaps I can do something similar for parsing the params. *ponders*

      MrCromeDome

Re: Converting large numbers of checkboxes to small number of params
by ChrisR (Hermit) on Jul 09, 2003 at 20:54 UTC
    I took a look at the web page and noticed one thing up front that will probably cause you some difficulty. Each checkbox has the same name. This will make it impossible to distinguish one from another. Your best be here is to come up with a naming convention for the checkboxes. I would use the day follwed by a delimeter follwed by the hour. For example: monday-7, monday-8 ... monday-16, monday-17, tuesday-7, tuesday-8, etc.

    I don't think you will find a way to avoid having a different name for each checkbox. You can still use a loop to create the form quite easily without a lot of code.

    Parsing the query string out can be made very simple as well. Try this snippet:
    if ($ENV{'QUERY_STRING'} ne '') { # for get requests $webparams = "$ENV{'QUERY_STRING'}"; } else { # for post requests (you should always use post) read(STDIN, $webparams, $ENV{'CONTENT_LENGTH'}); } $webparams =~ s/\&/\n/g; $_ = $webparams; %webparams2 = /^(.*?)=(.*)$/gm;
    This will return a hash (%webparams2) containg an entry for every checkbox that was checked when the form was submitted. The keys will be the names of the checkboxes. Now you can just loop through the hash or use map to create your database statement.

    I hope this helps. If you would like a more complete example including form generation and db statement preparation, just let me know.

    Chris
      Another good idea might be to make the names of you checkboxes match the field names in your db. That would slightly reduce the code needed to create the statement.

      Please excuse the second post but I don't know how to update it. Perhaps someone can tell me?

      Chris
Re: Converting large numbers of checkboxes to small number of params
by oknow (Chaplain) on Jul 09, 2003 at 22:15 UTC

    Would something more like this do what you are looking for? I do not usually like to mix using CGI to write out the tags, and writing them myself... But this should at least be on the right track.

    Instead of having 100 differently named checkboxes, why not just use 1 checkbox name for each day? All the boxes that are checked will be read by CGI as an array of the values of the boxes that were checked.

    #! /usr/bin/perl use strict; use CGI; my @days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"); my @hours = ( "7am-8am", "8am-9am", "9am-10am", "10am-11am" ); my $q=CGI->new(); print $q->header, $q->start_html; print $q->start_form; print '<table><tr>', $q->td(); for my $hour (@hours) { print $q->td($hour); } print '</tr>'; for my $day (@days) { print '<tr>'; print '<td>', $day, '</td>'; for my $hour (@hours) { print $q->td($q->checkbox(-name=>$day, -value=>$hour, -label=>'' ) ); } print '</tr>'; } print '</table>'; print $q->submit; print $q->end_form; # Display the values to make sure it's doing the right thing print '<br>Values:<br><br>'; for my $day (@days) { print $day, '<br>'; print join('<br>', $q->param($day)); print '<br><br>'; } print $q->end_html;
      "I do not usually like to mix ... But this should at least be on the right track."

      I don't like to mix hardcoded HTML tags and CGI.pm methods either, but i like what your code does. :) Here is another version that only uses CGI.pm:

      #!/usr/bin/perl -T use strict; use warnings; use CGI::Pretty qw(:standard); my @hour = qw(7am-8am 8am-9am 9am-10am 10am-11am); my @day = qw( Monday Tuesday Wednesday Thursday Friday Saturday Sunday ); print header, start_html, start_form, table( Tr(th['&nbsp;',@hour]), map { my $day = $_; Tr( th({align=>'right'},$_), td[map checkbox("_$day",0,$_,''), @hour], ) } @day ), submit, end_form, h2('Values'), ul( map { li(substr($_,1,length $_)) . ol(li[param($_)]) } grep {/^_/} param(), ), end_html, ;
      By appending an underscore to each of the names of the check boxes, i can extract them out quite easily. This does, however, complicate things when displaying those names (hence the substr call). Hope this helps. :)

      jeffa

      L-LL-L--L-LL-L--L-LL-L--
      -R--R-RR-R--R-RR-R--R-RR
      B--B--B--B--B--B--B--B--
      H---H---H---H---H---H---
      (the triplet paradiddle with high-hat)
      
      Just thought I'd point out some features of CGI.pm
      use CGI; die CGI->start_table, CGI->start_Tr, CGI->end_Tr, CGI->end_table; __END__ <table><tr></tr></table> at - line 2.
Re: Converting large numbers of checkboxes to small number of params
by saintbrie (Scribe) on Jul 09, 2003 at 20:53 UTC
    Sticking with #3 above. (this is untested code... it probably won't compile, but you get the idea).
    my @days = qw( Monday Tuesday Wednesday Thursday Friday Saturday Sunda +y ); my @hours = (7..21); # use the start hour for the value; my $out = qq(<tr> <td>Day</td>); for my $hour (@hours) { $out .= "<td>$hour - " . $hour + 1 . "</td>"; for my $day (@days) { $out .= qq( <tr> <td>$day</td>); for my $hour (@hours) { $out .= qq(<td><input type="checkbox" name="$day" value="$hour" />< +/td>); } $out .= qq(</tr>); } # so now you've printed out the form; # now to get the data out of it: use CGI; my $q = CGI->new; for my $day (@days) { @hours = $q->param($day); # the hours for the day are now in the array. $Hours->{$day} = \@hours; }
Re: Converting large numbers of checkboxes to small number of params
by SilverB1rd (Scribe) on Jul 09, 2003 at 21:42 UTC
    I suggest you look into HTML::Template, it can simplify perl/cgi/html projects alot.

    ------
    PT - Perl Tanks %100 Perl programming game
    The Price of Freedom is Eternal Vigilance

      try this for fun - it accepts the text box values as anrray #!/usr/bin/perl ## relatively painless iteration over multiple selection boxes use CGI qw(:standard); @sel = param('sel'); @entry = ("first", "second", "third", "fourth", "fifth", "sixth", "sev +enth", "eighth", "ninth", "tenth"); print "Content-Type: text/html\n\n"; if ($sel[0] ne '') { # do database stuff here (each entry in the sel array is the index to + your array of data for the database) ## just for fun show boxes checked foreach $ent (@sel) { print "you checked the $entry[$ent] box<br>\n"; } } print <<EOF <form name=fred action=jd.cgi> <table> <tr> <td> one <input type=checkbox name=sel value=0> </td> <td> two <input type=checkbox name=sel value=1> </td> <td> three <input type=checkbox name=sel value=2> </td> <td> four <input type=checkbox name=sel value=3> </td> <td> five <input type=checkbox name=sel value=4> </td> </tr> <tr> <td> six <input type=checkbox name=sel value=5> </td> <td> seven <input type=checkbox name=sel value=6> </td> <td> eight <input type=checkbox name=sel value=7> </td> <td> nine <input type=checkbox name=sel value=8> </td> <td> ten <input type=checkbox name=sel value=9> </td> </tr> </table> <input type=submit value=showme> </form> EOF Hope this helps John
        Ughh! If you are going to use CGI, then use CGI!!
        #!/usr/bin/perl -T use strict; use warnings; use CGI::Pretty qw(:standard); my @entry = ( [qw(one first)], [qw(two second)], [qw(three third)], [qw(four fourth)], [qw(five fifth)], [qw(six sixth)], [qw(seven seventh)], [qw(eight eighth)], [qw(nine ninth)], [qw(ten tenth)], ); print header, start_html('test'); print map "$entry[$_][1] box checked".br, param 'sel' if param 'go'; print start_form('fred'), table( Tr(td[map checkbox('sel',undef,$_,$entry[$_][0]), 0..4]), Tr(td[map checkbox('sel',undef,$_,$entry[$_][0]), 5..9]), ), submit(-name=>'go',-value=>'showme'), end_form, end_html, ;
        ;)

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)
        
Re: Converting large numbers of checkboxes to small number of params
by talexb (Chancellor) on Jul 09, 2003 at 21:23 UTC

    You've got the right idea -- use something like 1-7 for the days and 7-21 for the hours, then get Perl to generate field names of the form foo_(day)_(hour). This gives you unique checkbox names. Then when you get the form submitted, do a nested set of loops and see if each checkbox is selected or not. I'd probably pop that information straight into a database table.

    Yeah, use a nested loop to output the checkboxes, and use the outter loop to handle the opening and closing tr, and the inner loop for the opening and closing td elements. Piece of cake.

    --t. alex
    Life is short: get busy!
Re: Converting large numbers of checkboxes to small number of params
by freddo411 (Chaplain) on Jul 09, 2003 at 21:47 UTC
    Unless I missed something, our post doesn't imply that that form Must* be programmatically gnerated. You could simply serve up the HTML via a template like so:
    use Template; use CGI qw/:standard -debug/; # Present form for data entry my $template = Template->new(); my $vars = ""; # no vars in this template print header; $template->process("my-Template-filename.tmpl", $vars) || &error( "Template process failed: ", $template->error() ); # my predefined error function
    Processing the form when submited is covered by the post by saintbrie.

    -------------------------------------
    Nothing is too wonderful to be true
    -- Michael Faraday

      Template is the way to go for projects like this. It really makes things much easier for a team. HTML coders can get the thing to look just right while programmers feed them the data. Separation of form and content. It's a beautiful thing. You could actually do this programmatically with Template. It has builtin iterators itself. It'd look a little different, but would be similar to the nested loops concept. In fact, I think that Template has a table function you might be able to use: Template::Table

      But you have to go through the {sarcasm}joy{/sarcasm} of installing Template first. It isn't that bad, actually, especially for just the plain jane version. You might look into it.

Re: Converting large numbers of checkboxes to small number of params
by MrCromeDome (Deacon) on Jul 14, 2003 at 04:56 UTC
    For those who may find my end solution helpful, I have posted the code that I have actually used in my project. I thought the reponses given to me were very informative and well thought out, and am very grateful for receving them. I ended up taking a couple of ideas, namely from jeffa and oknow, and from a follow-up chat with tilly. I let my friend do all the HTML design work on this one, and in the end we made a template of the form. As pointed out by ChrisR, the checkboxes in the sample form were named the same, so she cleaned them up and we had a sample form that resembled this:
    <tr align="center" bgcolor="#eeeeee"> <td bgcolor="#cccccc"><b>Monday</b> </td> <td width="6%"><input type="checkbox" name="Monday" value="7a8a" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="8a9a" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="9a10a" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="10a11a" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="11a12p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="12p1p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="1p2p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="2p3p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="3p4p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="4p5p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="5p6p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="6p7p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="7p8p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="8p9p" +/> </td> <td width="6%"><input type="checkbox" name="Monday" value="9p10p" +/> </td> </tr>
    Each day was passed to my Perl script as an array of checkboxes. Reading and parsing them as parameters was then as easy as:
    # Extract and display a summary of information from the form. my @days = qw(Monday Tuesday Wednesday Thursday Friday Saturday Su +nday); foreach my $day (@days) { my @times = $request->param($day); # Count the number of days the user selected something if(scalar @times) { $count += @times; } # Pretty up time times foreach(@times) { s/(\d{1,2}[ap])(\d{1,2}[ap])/\1-\2/; s/(a|p)/\1m/g; } # Display the list for each day $page .= "$day: " . (scalar @times == 0 ? "None" : "@times") . + $request->br(); } # Show the total number of hours selected in the summary $page .= $request->p() . "Total Hours Selected: $count" . $request +->p();
    Thanks again to those who took the time to comment on this.

    MrCromeDome