(You are Anonymous)

Setting Drop Down Menu Values in CGI::Application projects

Originally compiled by Steve Comrie, September 11th, 2003. Editted significantly by Mark Stosberg

What's the best way to create a <select> form element using CGI::Application?

General Discussion

This is taken from Sam Tregar's HTML::Template FAQ

There is much disagreement on this issue. My personal preference is to use CGI.pm's excellent popup_menu() and scrolling_list() functions to fill in a single <tmpl_var select_foo> variable.

To some people this smacks of mixing HTML and code in a way that they hoped HTML::Template would help them avoid. To them I'd say that HTML is a violation of the principle of separating design from programming. There's no clear separation between the programmatic elements of the <form> tags and the layout of the <form> tags. You'll have to draw the line somewhere - clearly the designer can't be entirely in charge of form creation.

It's a balancing act and you have to weigh the pros and cons on each side. It is certainly possible to produce a <select> element entirely inside the template. What you end up with is a rat's nest of loops and conditionals. Alternately you can give up a certain amount of flexibility in return for vastly simplifying your templates. I generally choose the latter.

Another option is to investigate HTMLFill In Form which some have reported success using to solve this problem.

CGI.pm popup_menu() basics

Here's an example of creating a <select> tag using CGI.pm's popup_menu, with the data provided from a database:

my %types = @{
     $DBH->selectcol_arrayref(
           "SELECT type_id,pretty_name FROM types "
           ,{ Columns=>[1,2]})};

 # $t is an existing HTML::Template object
 # $self is a CGI::App object
 $t->param( type_popup=> $self->query->popup_menu(
               -name=>'type_id',
               # Sort by the values rather than the keys
               -values=>[sort { $types{$a} cmp $types{$b} } keys %types],
               -labels=>\%types,
               ),

HTML::FillInForm basics

The HTMLFill In Form module takes an existing HTML form and fills it in with data you provide as a perl data structure, returning the merged document.

HTML::FillInForm has a simple interface and works well for just about any HTML form-filling job. If your form is generated dynamically, just pass the generated form through your templating system before sending it to the browser.

Besides delivering an easy solution to populate <select> tags, HTML::FillInForm is handy for developing "update" forms. Without it, you might need to add extra template tokens for every field, like this:

<input type="text" name="color" value="<!-- tmpl_var name="color" -->">

With HTML::FillInForm, you skip adding all these tags because it will take care of filling in the form for you. Just the basic tag declaration is sufficient:

<input type="text" name="color">

Removing the need for this extra code removes a potential source of bugs.

Here's an example of using CGI::Application::Plugin::FillInForm to populate an "update" form for an application:

my $item= $DBH->selectrow_hashref("SELECT * 
    FROM dealers where dealer_id = ?",{},$FORM{dealer_id});

my $t = $self->load_tmpl('admin-content-edit.html');
$t->param($item);

# This use line could be safely put in a base class, since
# it only loads HTML::FillInForm as needed
use CGI::Application::Plugin::FillInForm qw/fill_form/;
return $self->fill_form(\$t->output,$item);

CGI::Application::ValidateRM uses HTML::FillInForm to display a form reloaded with the original input intact, plus additional error messages diplayed.

The following CGI::App mailing list members have also recommened HTML::Fill In Form: Steve Comrie, Mark Stosberg, Brett Sanger, Cess Hek, Jason Purdy

Alternative: Just make the first item dynamic

Here's an alternative that is spotted less in the wild on the Web:

You can make just the first item on the list dynamic. This does mean that the default is listed twice: (once at the top and once in the list). In some cases this may be confusing to users. To other users, however, having the current value repeated at the top instead of hidden among all the other options, may even be an added value.

You can visually separate this with a line.

<select name=episode>

<tmpl_if episode/value>
    <option value="<tmpl_var episode/value>"><tmpl_var episode/name></option>
    <option disabled>---------</option>
</tmpl_if>
<option value="1">Episode I</option>
<option value="2">Episode II</option>
<option value="3">Episode III</option>
<option value="4">Episode IV</option>

</select>

Alternative: Roll your own

Jeff MacDonald provided this example of building your own select tag from scratch. This technique allows you to have a little more HTML in your template rather than generated by CGI.pm.

$sql = "SELECT id, name, CASE WHEN id = 123 THEN 'SELECTED' END AS selected FROM sometable";

$prepare/execute/yada yada
$tmp->param(LIST => $sth->fetchall_arrayref({}));

Where LIST is a list in HTML::Template format in the template. that might look like this.

<select>
<TMPL_LOOP NAME=OPEN_LIST>
  <option value="<TMPL_VAR NAME=id>" <TMPL_VAR NAME=selected>><TMPL_VAR NAME=name></option>
</TMPL_LOOP>
</select>

Comments

If you really want to have some fun, try defining your forms using a .xml file and then using XML::Simple to read the file into a hash and pass it to HTML::Template, then store user feedback in a separate .xml file, get XML::Simple to read that file into a hash and pass it to HTML::Fill In Form .. then output the template with the form filled in. On the other end write the saved .xml file using the contents of $cgi->Vars and you've got an instant dynamic form reading from a master .xml template and store all user records in individual .xml files that can be editing simply.

Sorry for the run on sentence.

Steve Comrie

Using Template Toolkit's CGI Plugin

If you're using Template Toolkit instead of HTML::Template (as I believe you should), then you can use the CGI plugin (Template::Plugin::CGI) in your templates to build HTML form elements in your templates. This allows you to pass just the data to the template instead of the full HTML of the controls. This both maintains the separation of logic and data and also makes your templates more readable.

In the context of a Template Toolkit template, first you import the CGI plugn:

[% USE CGI %]

And later, having passed in an array ref containing values (state_values) and a hash ref providing the labels (state_labels), you'd do this:

State:  [% CGI.popup_menu(name   => 'state',
                          id     => 'state',
                          values => state_values,
                          labels => state_labels,) %]

Berg Brains

See Also