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

I'm writing a CGI script where a person will select their name, then enter a lat / long for a location, then multiple lat / longs for warning points. There will be anywhere from 1 to infinity warning points. Every time a warning point is added the script will refresh and show the added warning points plus a new space to enter in an additional warning point.

I pass the new warning points back to the CGI script as "WPLongX" and "WPLatX" where X is the data point number.

I'm having trouble thinking how I'm going to get the variable number of warning points that are passed back to my CGI script out and stored into an array.

I'm storing all the warning points in the "@warningPoints" array with WPLong the first element and WPLat the seconds, this repeats itself through the array. Any suggestions on how to make this script take multiple warning points?
#!/usr/bin/perl -wT use CGI::Carp qw(fatalsToBrowser); use strict; use CGI; my $cgi = CGI->new(); # Create new CGI object. my $AuthorID; #Make $AuthorID global my $AuthorName; #Make $AuthorName global my $LocationLongitude; #Location longitude global my $LocationLatitude; #Location latitude global my $WarningLongitude; my $WarningLatitude; my @warningPoints; if ($cgi->param("LocationLongitude") && $cgi->param("LocationLatitude" +)) { # If Lat/Long present, add your warning points $LocationLongitude = $cgi->param("LocationLongitude"); $LocationLatitude = $cgi->param("LocationLatitude"); $AuthorID = $cgi->param("AuthorID"); $AuthorName = $cgi->param("AuthorName"); &print_warning_page; exit; } if ($cgi->param("AuthorID")) { #If author is passed back to the scrip +t, save the author info and goto the location page $AuthorID = $cgi->param("AuthorID"); &print_location_page; exit; } else {&print_author_page;} sub print_author_page { &print_header; print $cgi->start_form; print $cgi->popup_menu(-name=>'AuthorID', -values=>[qw/bob cecil/], -labels=>{'1'=>'bob', '2'=>'cecil'}, -default=>'bob'); print "<br>"; print $cgi->submit('Go'); print $cgi->end_form; } sub print_location_page { &print_header; print $cgi->start_form; print $cgi->hidden('AuthorID',"$AuthorID"); $LocationLatitude = $cgi->textfield(-name=>'LocationLatitude',-siz +e=>10,-maxlength=>10); $LocationLongitude = $cgi->textfield(-name=>'LocationLongitude',-s +ize=>10,-maxlength=>10); print <<EOF; <table border="0"> <tr> <td height="37">AuthorID:&nbsp</td> <td height="37">$AuthorID</td> <td height="37"></td> </tr> <tr> <td>Location Longitude:&nbsp</td> <td>$LocationLongitude</td> <td>( X Coord )</td> </tr> <tr> <td>Location Latitude:&nbsp;</td> <td>$LocationLatitude</td> <td>( Y Coord )</td> </tr> </table> EOF print "<br>"; print $cgi->submit('Go'); print $cgi->end_form; } sub print_warning_page { &print_header; print $cgi->start_form; print $cgi->hidden('AuthorID',"$AuthorID"); print $cgi->hidden("LocationLongitude","$LocationLatitude"); print $cgi->hidden("LocationLatitude","$LocationLatitude"); $WarningLatitude = $cgi->textfield(-name=>'Latitude',-size=>10,-ma +xlength=>10); $WarningLongitude = $cgi->textfield(-name=>'Longitude',-size=>10,- +maxlength=>10); print <<EOF; <table border="0"> <tr> <td height="37">AuthorID:&nbsp</td> <td height="37">$AuthorID</td> <td height="37"></td> </tr> <tr> <td>Location Longitude:&nbsp</td> <td>$LocationLongitude</td> <td>( X Coord )</td> </tr> <tr> <td>Location Latitude:&nbsp;</td> <td>$LocationLatitude</td> <td>( Y Coord )</td> </tr> EOF my $point = 1; foreach (@warningPoints) { my $WPLong = shift @warningPoints; my $WPLat = shift @warningPoints; print <<EOF; <tr> <td>Warning Longitude $point:&nbsp</td> <td>$WPLong</td> <td>( X Coord )</td> </tr> <tr> <td>Warning Latitude $point:&nbsp;</td> <td>$WPLat</td> <td>( Y Coord )</td> </tr> EOF print $cgi->hidden("WPLong$point","$WPLong"); print $cgi->hidden("WPLat$point","$WPLat"); $point++; } print <<EOF; <tr> <td>Warning Longitude:&nbsp</td> <td>$WarningLongitude</td> <td>( X Coord )</td> </tr> <tr> <td>Warning Latitude:&nbsp;</td> <td>$WarningLatitude</td> <td>( Y Coord )</td> </tr> </table> EOF print $cgi->checkbox(-name=>'checkbox_name', -checked=>1, -value=>'ON', -label=>'This is the last warning point.'); print "<br>"; print $cgi->submit('Go'); print $cgi->end_form; } sub print_header { print $cgi->header; }

Replies are listed 'Best First'.
Re: Need Help With Writers Block
by Fang (Pilgrim) on Sep 05, 2005 at 06:45 UTC

    I'm storing all the warning points in the "@warningPoints" array with WPLong the first element and WPLat the seconds, this repeats itself through the array. Any suggestions on how to make this script take multiple warning points?

    How about using a hash of hashes? It would fit better with your needs. You can use numbers as the keys, which will be considered strings by perl for that purpose anyway.

    %warningPoints = ( 1 => { WPLong => 10, WPLat => 20, }, 2 => { WPLong => 30, WPLat => 40, }, ... );

    To get multiple latitude and longitude values, you can create as many text fields as needed in your form, all named latitude or longitude, depending on what they expect for a value. The param() method will then return a list consisting of all the corresponding values. An example:

    #!/usr/bin/perl use warnings; use strict; use Data::Dumper; use CGI; my $q = new CGI; print $q->header(), $q->start_html(); print $q->start_form(); print $q->textfield(-name => 'Latitude', -size => 10, -maxlength => 10 +, -override => 1); print $q->textfield(-name => 'Longitude', -size => 10, -maxlength => 1 +0, -override => 1); print $q->textfield(-name => 'Latitude', -size => 10, -maxlength => 10 +, -override => 1); print $q->textfield(-name => 'Longitude', -size => 10, -maxlength => 1 +0, -override => 1); print $q->submit(-name => 'choice', -value => 'Submit'); print $q->end_form(); my $choice = lc($q->param('choice')); if ($choice eq 'submit') { my @lat = $q->param('Latitude'); my @long = $q->param('Longitude'); print $q->pre(Dumper(\@lat)); print $q->pre(Dumper(\@long)); } print end_html();

    The coordinates of the first point would then be ($long[0], $lat[0]), and so on for all points.

    One good thing about this method is that you can add text fields with the help of some JavaScript, not having to submit the form each time. One bad thing is you have to use the -override switch to avoid the form, as well as the user, from being slightly confused in case you print it after submission.

Re: Need Help With Writers Block
by graff (Chancellor) on Sep 05, 2005 at 07:21 UTC
    I think your choices are:

    (1) set it up so that the client submits a POST (as opposed to a GET), and uploads the lat/lon list as text data (but this will make more work for parsing the text), or

    (2) send the lat/lon list as array-type parameters (a given param name may occur multiple times in the query, and CGI allows you to load all params with a given name into an array); this is discussed in the CGI man page.

    In the latter case, you may want to make "LatLon" a single cgi param with some internal syntax (e.g. use values that look like "12.34n56.78w" -- something that is easy to handle with a regex and is reasonably readable/usable by humans). Either that, or else you'll need to be extra careful about keeping each distinct "Lat" and "Lon" param in the correct order, so that the pairs don't get scrambled.

    Apart from that, you might want to look into HTML::Template and take the HTML text out of the code -- the module has some features (looping constructs) that will be very useful here. Once you catch on to it, your code will be a lot simpler and shorter (and your HTML will be easier to maintain, too).

    And why do you have your own "print_header" function? Why not just do "print $cgi->header" when you need to?

    Update: Sorry, I may have missed the point of your question. Where are the values in "@warningpoints" supposed to come from?

Re: Need Help With Writers Block
by monarch (Priest) on Sep 05, 2005 at 06:47 UTC

    A couple of approaches:
      1. insert a hidden field containing a row count, then when you process the page iterate a call to $cgi->param() with each variable name/number.
      2. get all the parameter names (@names = $cgi->param) and grep for the variable names you want.

Re: Need Help With Writers Block
by jhourcle (Prior) on Sep 05, 2005 at 13:08 UTC

    The easiest way to handle this is to only allow them to modify one tuple at a time -- present the current list, but not in editable fields, with an 'edit' button next to each one, which will bring up a new window (or frame), and only allow them to add one new item at a time.

    An alternate way is to choose a naming scheme for your fields such that you can easily build sets from the names. eg, 'Lat_1' is paired with 'Long_1'. However, in that case, you'll need to go through the list of fields returned ($cgi->param()) to see if there are fields of interest, as opposed to just checking if a certain field is there.

    The next alternative requires javascript, which means it might fail horribly for some people -- you can be assured that in DOM, you can keep track of matching pairs of fields, so if one of the fields of interest is modified, you can update a hidden composite field, so you only have to iterate over the composite field, which is assured to be paired correctly (well, assuming they have javascript ... if they don't, you have to fall back to one of the other methods, but you also have to make sure that you pass back that javascript wasn't on, and the hidden fieleds aren't to be trusted. (you could insert the hidden fields through document.write() calls in <script></script> blocks, but I'm not sure that the extra work is worth it)

    Although it's fairly safe to assume that the browser will return two sets of the same named fields in the same order, you can't necessarily go through the lists with the same iterator, and assume they'll be pairs -- the browser doesn't have to send empty fields, so an empty value in each list, but in different sets will completely screw up your results.

Re: Need Help With Writers Block
by raptnor2 (Beadle) on Sep 05, 2005 at 17:57 UTC
    Try writing tests for it. This will change your perspective and help you see what it should do. If this doesn't work, check the source into your code repository, delete any code that seems convoluted and start fresh. Both have worked well for me - today in fact.

    As humans we are context based, which means that we can get locked into the problems of our solutions. Both methods above enable you to shift your context, and clear your head.

    Hope this helps.

    Cheers,

    John