=head1 NAME HTML::FormTemplate - Make data-defined persistant forms, reports =cut ###################################################################### package HTML::FormTemplate; require 5.004; # Copyright (c) 1999-2004, Darren R. Duncan. All rights reserved. This module # is free software; you can redistribute it and/or modify it under the same terms # as Perl itself. However, I do request that this copyright information and # credits remain attached to the file. If you modify this module and # redistribute a changed version then please attach a note listing the # modifications. This module is available "as-is" and the author can not be held # accountable for any problems resulting from its use. use strict; use warnings; use vars qw($VERSION @ISA); $VERSION = '2.03'; ###################################################################### =head1 DEPENDENCIES =head2 Perl Version 5.004 =head2 Standard Modules I =head2 Nonstandard Modules Class::ParamParser 1.041 HTML::EasyTags 1.071 Data::MultiValuedHash 1.081 CGI::MultiValuedHash 1.09 =cut ###################################################################### use Class::ParamParser 1.041; @ISA = qw( Class::ParamParser ); use HTML::EasyTags 1.071; use Data::MultiValuedHash 1.081; use CGI::MultiValuedHash 1.09; ###################################################################### =head1 SYNOPSIS #!/usr/bin/perl use strict; use warnings; use HTML::FormTemplate; use HTML::EasyTags; my @definitions = ( { visible_title => "What's your name?", type => 'textfield', name => 'name', is_required => 1, }, { visible_title => "What's the combination?", type => 'checkbox_group', name => 'words', 'values' => ['eenie', 'meenie', 'minie', 'moe'], default => ['eenie', 'minie'], }, { visible_title => "What's your favorite colour?", type => 'popup_menu', name => 'color', 'values' => ['red', 'green', 'blue', 'chartreuse'], }, { type => 'submit', }, ); my $query_string = ''; read( STDIN, $query_string, $ENV{'CONTENT_LENGTH'} ); chomp( $query_string ); my $form = HTML::FormTemplate->new(); $form->form_submit_url( 'http://'.($ENV{'HTTP_HOST'} || '127.0.0.1').$ENV{'SCRIPT_NAME'} ); $form->field_definitions( \@definitions ); $form->user_input( $query_string ); my ($mail_worked, $mail_failed); unless( $form->new_form() ) { if( open( MAIL, "|/usr/lib/sendmail -t") ) { print MAIL "To: perl\@DarrenDuncan.net\n"; print MAIL "From: perl\@DarrenDuncan.net\n"; print MAIL "Subject: A Simple Example HTML::FormTemplate Submission\n"; print MAIL "\n"; print MAIL $form->make_text_input_echo()."\n"; close ( MAIL ); $mail_worked = 1; } else { $mail_failed = 1; } } my $tagmaker = HTML::EasyTags->new(); print "Status: 200 OK\n", "Content-type: text/html\n\n", $tagmaker->start_html( 'A Simple Example' ), $tagmaker->h1( 'A Simple Example' ), $form->make_html_input_form( 1 ), $tagmaker->hr, $form->new_form() ? '' : $form->make_html_input_echo( 1 ), $mail_worked ? "

Your favorites were emailed.

\n" : '', $mail_failed ? "

Error emailing your favorites.

\n" : '', $tagmaker->end_html; =head1 DESCRIPTION This Perl 5 object class can create web fill-out forms as well as parse, error-check, and report their contents. Forms can start out blank or with initial values, or by repeating the user's last input values. Facilities for interactive user-input-correction are also provided. The class is designed so that a form can be completely defined, using field_definitions(), before any html is generated or any error-checking is done. For that reason, a form can be generated multiple times, each with a single function call, while the form only has to be defined once. Form descriptions can optionally be read from a file by the calling code, making that code a lot more generic and robust than code which had to define the field manually. =head1 OVERVIEW If the calling code provides a MultiValuedHash object or HASH ref containing the parsed user input from the last time the form was submitted, via user_input(), then the newly generated form will incorporate that, making the entered values persistant. Since the calling code has control over the provided "user input", they can either get it live or read it from a file, which is transparent to us. This makes it easy to make programs that allow the user to "come back later" and continue editing where they left off, or to seed a form with initial values. (Field definitions can also contain initial values.) Based on the provided field definitions, this module can do some limited user input checking, and automatically generate error messages and help text beside the appropriate form fields when html is generated, so to show the user exactly what they have to fix. The "error state" for each field is stored in a hash, which the calling code can obtain and edit using invalid_input(), so that results of its own input checking routines are reflected in the new form. This class also provides utility methods that you can use to create form field definitions that, when fed back to this class, generates field html that can be used by CGI scripts to allow users with their web browsers to define other form definitions for use with this class. Note that this class is a subclass of Class::ParamParser, and inherits all of its methods, "params_to_hash()" and "params_to_array()". =head1 RECOGNIZED FORM FIELD TYPES This class recognizes 10 form field types, and a complete field of that type can be made either by providing a "field definition" with the same "type" attribute value, or by calling a method with the same name as the field type. Likewise, groups of related form fields can be made with either a single field definition or method call, for all of those field types. Standalone fields of the following types are recognized: =over 4 =item 0 B - makes a reset button =item 0 B - makes a submit button =item 0 B - makes a hidden field, which the user won't see =item 0 B - makes a text entry field, one row high =item 0 B - same as textfield except contents are bulleted out =item 0 B" form tags. sub _make_textarea_html { my ($self, $defin) = @_; # Set up default attributes common to textarea tags. my %params = ( %{$defin->fetch_value( $FKEY_TAG_ATTR )}, name => $defin->fetch_value( $FKEY_NAME ), ); my $default = $defin->fetch_value( $FKEY_DEFAULTS ); # Make the field HTML and return it. my $tagmaker = $self->{$KEY_TAG_MAKER}; return( $tagmaker->make_html_tag( 'textarea', \%params, $default ) ); } ###################################################################### # _make_textarea_group_html( DEFIN ) # This private method assists _make_field_html() by specializing in making # a group of "" form tags. sub _make_textarea_group_html { my ($self, $defin) = @_; # Set up default attributes common to textarea tags. my %params = ( %{$defin->fetch_value( $FKEY_TAG_ATTR )}, name => $defin->fetch_value( $FKEY_NAME ), ); my @defaults = $defin->fetch( $FKEY_DEFAULTS ); # Make sure we have enough group members. my $wanted = $defin->fetch_value( $FKEY_MIN_GRP_COUNT ); my $have = @defaults; if( $have < $wanted ) { push( @defaults, [map { '' } (1..($wanted - $have))] ); } # Make the field HTML and return it. my $tagmaker = $self->{$KEY_TAG_MAKER}; return( $tagmaker->make_html_tag_group( 'textarea', \%params, \@defaults, 1 ) ); } ###################################################################### # _make_input_html( DEFIN ) # This private method assists _make_field_html() by specializing in making # single "" form tags. sub _make_input_html { my ($self, $defin) = @_; my $type = $defin->fetch_value( $FKEY_TYPE ); # Set up default attributes common to all input tags. my %params = ( %{$defin->fetch_value( $FKEY_TAG_ATTR )}, type => $INPUT_TAG_IMPL_TYPE{$type}, name => $defin->fetch_value( $FKEY_NAME ), value => $defin->fetch_value( $FKEY_DEFAULTS ), ); my $label = ''; # Set up attributes that are unique to check boxes and radio buttons. # One difference is that user input affects the "checked" attribute # instead of "value". if( $type eq 'checkbox' or $type eq 'radio' ) { $params{value} = $defin->fetch_value( $FKEY_VALUES ); defined( $params{value} ) or $params{value} = 'on'; $params{checked} = $defin->fetch_value( $FKEY_DEFAULTS ); $label = $defin->fetch_value( $FKEY_LABELS ); defined( $label ) or $label = $params{name}; $defin->fetch_value( $FKEY_NOLABELS ) and $label = ''; # For most input tag types, an empty "value" attribute is useless so # get rid of it. For buttons an empty value leads to no button label. } else { $params{value} eq '' and delete( $params{value} ); } # Make the field HTML and return it. my $tagmaker = $self->{$KEY_TAG_MAKER}; return( $tagmaker->make_html_tag( 'input', \%params, $label ) ); } ###################################################################### # _make_input_group_html( DEFIN ) # This private method assists _make_field_html() by specializing in making # a group of "" form tags. sub _make_input_group_html { my ($self, $defin) = @_; my $type = $defin->fetch_value( $FKEY_TYPE ); # Set up default attributes common to all input tags. my %params = ( %{$defin->fetch_value( $FKEY_TAG_ATTR )}, type => $INPUT_TAG_IMPL_TYPE{$type}, name => $defin->fetch_value( $FKEY_NAME ), value => scalar( $defin->fetch( $FKEY_DEFAULTS ) ) || [], ); my @labels = (); # Set up attributes that are unique to checkboxes and radio buttons. # One difference is that user input affects the "checked" attribute # instead of "value". if( $type eq 'checkbox_group' or $type eq 'radio_group' ) { my $ra_values = $defin->fetch( $FKEY_VALUES ) || ['on']; $params{value} = $ra_values; # The definition property "defaults" may be either an array ref # or a hash ref. If it is a hash ref then the hash keys would # correspond to field values and the hash values would be either # true or false to indicate if it is selected. If it is an array # ref then the array elements would be a list of field values, # all of which are selected. This code block takes either # variable type and coerces the data into an array ref that has # the same number of elements as there are field values, and each # corresponding element is either true or false; this format is # what HTML::EasyTags needs as input. my $ra_defaults = $defin->fetch( $FKEY_DEFAULTS ) || []; # array if( ref( $ra_defaults->[0] ) eq 'HASH' ) { $ra_defaults = $ra_defaults->[0]; # hash } if( ref( $ra_defaults ) eq 'ARRAY' ) { $ra_defaults = {map { ( $_ => 1 ) } @{$ra_defaults}}; # hash } $ra_defaults = [map { $ra_defaults->{$_} } @{$ra_values}]; # ary $params{checked} = $ra_defaults; # The definition property "labels" may be either an array ref # or a hash ref. If it is a hash ref then the hash keys would # correspond to field values and the hash values would be the # labels associated with them; this is coerced into an array. # If it is an array ref then the elements already are # counterparts to the field value list. If any labels are # undefined then the appropriate field value is used as a label. my $ra_labels = $defin->fetch( $FKEY_LABELS ) || []; # array if( ref( $ra_labels->[0] ) eq 'HASH' ) { $ra_labels = $ra_labels->[0]; # hash $ra_labels = [map { $ra_labels->{$_} } @{$ra_values}]; # ary } foreach my $index (0..$#{$ra_values}) { unless( defined( $ra_labels->[$index] ) ) { $ra_labels->[$index] = $ra_values->[$index]; } } $defin->fetch_value( $FKEY_NOLABELS ) and $ra_labels = []; @labels = @{$ra_labels}; # Make sure we have enough group members. } else { my $wanted = $defin->fetch_value( $FKEY_MIN_GRP_COUNT ); my $have = @{$params{value}}; if( $have < $wanted ) { push( @{$params{value}}, [map { '' } (1..($wanted - $have))] ); } } # Make the field HTML and return it. my $tagmaker = $self->{$KEY_TAG_MAKER}; return( $tagmaker->make_html_tag_group( 'input', \%params, \@labels, 1 ) ); } ###################################################################### # _make_select_html( DEFIN ) # This private method assists _make_field_html() by specializing in making # single "" form tags, which include a group of