The
Docbase::Input module will automatically preview
input received from a form’s handler. We’ll see how that
works shortly. But validating the fields of a record is the
responsibility of each Docbase instance. That’s done by
customizing the handler for each instance. The handler is always
called submit.pl. For the ProductAnalysis docbase,
the customized handler—shown in Example 6.4—is stored as
/web/cgi-bin/Docbase/ProductAnalysis/submit.pl
.
Example 6-4. The ProductAnalysis Version of submit.pl
#!/usr/bin/perl -w ########################################################### # boilerplate for every docbase ########################################################### use strict; use Docbase::Input; use TinyCGI; my $tc = TinyCGI->new(); my $vars = $tc->readParse(); # collect CGI args print $tc->printHeader; # start emitting page my $app = $vars->{app}; my $di = Docbase::Input->new($app); foreach my $key (keys %$vars) # basic cleanup of input text { $vars->{$key} = $di->cleanseInput($vars->{$key}); } my ($warnings,$errors) = docbase_validate(); # validate input $di->processInput($vars,$warnings,$errors); # process input and warnings/errors ########################################################### # custom validation tailored for each docbase ########################################################### sub docbase_validate { my (@warnings) = (); my (@errors) = (); my $mailpat = $di->getMailPat; # accumulate warnings unless ( $vars->{'contact'} =~ m#$mailpat# ) # email address wanted { push (@warnings,"<b>contact</b> does not appear to contain an email address "); } unless ( $vars->{'contact'} =~ m#d{3,4}# ) # phone number wanted { push (@warnings,"<b>contact</b> does not appear to contain a phone number "); } # accumulate errors foreach my $key (keys %$vars) { if (! defined $vars->{$key}) # all fields must be non-empty { push (@errors,'<b>' . $key . '</b> is empty, but required'), } if ( $vars->{$key} =~ /<.+>/ ) # no HTML tags allowed { push (@errors, '<b>' . $key . '</b> must not contain HTML tags'), } } return (@warnings,@errors); }
The first half of this handler is a boilerplate that’s the same
for every Docbase instance. To tailor it for a new instance, you
write a version of docbase_validate( )
that
enforces rules specific to this instance, classifies problems as
either errors or warnings, and returns these as a pair of lists.
Docbase users
don’t normally compose lengthy text in the HTML form.
Typically, they paste in text created by a word processor. That text
sometimes includes character codes that you’ll want to convert.
For example, some Macintosh programs represent a single quote as
xD5. The cleanseInput( )
method of
Docbase::Indexer turns that into the ASCII single
quote, x27. It’s just a set of search-and-replace operations,
like this:
sub cleanseInput { my ($self, $s) = @_; $s =~ s/x00/ /g; $s =~ s/xd1/--/g; $s =~ s/xd2/"/g; $s =~ s/xd3/"/g; $s =~ s/xd5/'/g; $s =~ s/x81/Æ/g; return $s; }
You can easily extend this method to handle other conversions as the need arises.
The
docbase_validate( )
function implements a
crucial but often overlooked principle: handlers should parse forms
completely and always report all errors and omissions. Failure to do
this is endemic on the Web, and it drives users crazy. There’s
nothing worse than bouncing off a server once to discover that your
password is too short, then again to find out that your password
includes an invalid character, then again to learn that, oh by the
way, the company field that you had left blank is actually required.
A form handler sees all the data you send, every time you send it.
Why not look at all the data and respond intelligently and completely
every time?
To do that, make two lists—one for warnings, one for errors. A warning raises an issue that the user can choose to either address or ignore; these are reported in the Optional Changes section of the preview. In this example, absence of an email address in the contact field triggers a warning. An error raises an issue that the user must deal with; these are reported under the heading Required Changes. In this example, any field that is empty, or that contains an HTML tag, triggers an error.
The docbase_validate( )
function accumulates
warnings and errors, then returns the two lists. Note how the Perl
idiom for doing that requires you to pass a reference to each of the
sublists, rather than the sublists themselves. The latter method
won’t cause an obvious error, but it will mash the two sublists
into a single list, and you’ll lose the distinction between
warnings and errors. Note also the two idioms for checking variables.
To check a specific variable, bind a test to a slot in the hashtable.
To check all variables, do that binding inside a loop that iterates
over the whole
hashtable.
The submit.pl script passes the form variables,
and the lists of warnings and errors, to the processInput(
)
method of the Docbase::Input module,
shown in Example 6.5.
Example 6-5. The processInput Method
sub processInput { my ($self,$vars,$warnings,$errors) = @_; my $warn_count = scalar(@$warnings); # count warnings my $err_count = scalar(@$errors); # count errors if ( $err_count > 0 ) # mention required changes { if ( $warn_count > 0 ) # also mention optional changes { _doWarnings($self->{app},$warn_count,$warnings); } print "<p><b>$self->{app}: Required changes: </b> "; my ($i, $c); for $i (1..$err_count) { print "<br>" . ++$c . ') ' . $errors->[$i-1]; } print "<p>Please use your browser's Go Back button "; print "to rewind and fix the required changes."; } elsif ( $warn_count > 0) # mention optional changes { _doWarnings($self->{app},$warn_count,$warnings); print "<p>You can go back and make these optional changes. "; print "Or, if what you see below is OK, you can submit it."; print $self->_previewDoc($vars); # preview } else { print $self->_previewDoc($vars); # preview } }
There are three possible outcomes. If there were errors, these are reported (along with warnings, if any) on a Required Changes page that advises the user to go back and correct them.
If there were only warnings, these are reported on an Optional Changes page that also includes a preview of the record. Here, the user has a choice: go back and fix what caused the warnings, or submit the previewed record as is.
Finally, if there were no errors or warnings, only the previewed record appears. The user can submit it or, if something needs changing, can rewind and make those changes.