(You are Anonymous)

custom error message using Data::Form Validator

By Qiang Li , Sep 5th, 2007

purpose of this article

I want to use/manage custom form error messages in a separate file. this article is inspired by Michael peters Smolder (smolder.sf.net) app.

define and check our register form constraint

Register.pm

use HTML::FillInForm;
use Data::FormValidator::Constraints qw/email FV_eq_with FV_length_between 
                                                      FV_min_length  FV_max_length/;
use My::Constraints qw/v_exist_user v_valid_characters/;	
#
# Show the register form or partially filled form.
#
sub register {
   my ( $self, $tt_params ) = @_;

   $tt_params ||= {};
   return $self->tt_process($tt_params);
}

#
sub process_register {
   my $self    = shift;

   my $form = {
       required           => [qw(username password password_confirm email)],
       constraint_methods => {
              username => [ FV_length_between(6,30), v_valid_characters(), v_exist_user() ],
              password => [ FV_min_length(7), FV_eq_with('password_confirm'), FV_max_length(50) ],
              email    => email(),
       }
   };

   my $results = $self->check_rm( 'register', $form )
     || return $self->check_rm_error_page();

   # redirect to home page....
}

list of constraints

Constraint.pm

package My::Constraints;
use strict;
use warnings;

our @ISA       = qw(Exporter);
our @EXPORT_OK = qw(
 v_exist_user
 v_valid_characters
);

sub v_valid_characters {
   return sub {
       my ($dfv, $username) = @_;
       unless ( $username =~ /^([a-zA-Z0-9])+\.?([a-z0-9])+$/ ) {
           $dfv->name_this('invalid_characters');
           return;
       }
       return 1;
   }
}

sub v_exist_user {
   return sub {
       my ($dfv, $username) = @_;
       if ( My::DB::Account->find_user( $username ) ) {
           $dfv->name_this('username_taken');
           return;
       } else {
           return 1;
       } 
   }
}

CGI::Application Base.pm where we loop through the failed form fields and fill the error message to template.

i use CGI::Application::Plugin::Config::Simple here to access the error message defined in a separate file.

note the comment in sub dfv_tt_params.. i am only getting the first failed constraint if there are more than one constraint failed for one field. you can stick an array into the $tt_param{ "err_$field" } if you want to capture them.

__PACKAGE__->add_callback('tt_pre_process', sub {
                             my ($self, $file, $vars) = @_;
                             if ( my $dfv_tt_params = $self->dfv_tt_params ) {
                                 while (my ($k,$v) = each %$dfv_tt_params) {
                                     $vars->{ $k } = $v;
                                 }
                             }
                             return;
                         }
);

__PACKAGE__->add_callback(
   init => sub {
       my $self  = shift;
       $self->param(
           'dfv_defaults' => {
               filters    => ['trim'],
               msgs       => { any_errors => 'some_errors',
                               prefix  => 'err_',
                               format  => '',
               },
           }
       );
   }
);

sub dfv_tt_params {
   my $self = shift;
   # loop through invalid 
   # find constraint name for each field.
   # access the real message with  $self->config_param('FIELD_NAME.err_CONSTRAINT_NAME'),

   # skip if check_rm is not called.
   # probably no need of dfv in this case.
   my $r;
   eval { $r = $self->dfv_results };
   return if $@;

   my %tt_param;
   if ( $r->has_invalid ) {
       my $invalid = $r->invalid;
       for my $field ( keys %$invalid ) {
           # we could have gone thr the list and get all failed constraints.
           # i think one failed message per field should be good enough.
           my $constraint_name = shift @{ $invalid->{$field} };
           # regex   
           if ( ref $constraint_name ) {
               $tt_param{ "err_$field" } = $self->config_param("$field.err_$field");
               next;
           }
           $tt_param{ "err_$field" } = 
                    $self->config_param("$field.err_$constraint_name");
       }
   }

   if ($r->has_missing) {
       my $missing = $r->missing;
       for my $field (@$missing) {
           $tt_param{ "err_$field" } = 'Required field cannot be left blank';
       }
   }

   return \%tt_param;
}    

here is the register form template

[% IF some_errors %]
<p class="warn">
  Error: There are problems processing the form.
</p>
[% END %]

<form action="/register.pl" method="post" name="register" id="register">
<input type="hidden" name="back" value="">

<table>
<tr>
 <th>Username</th>
 <td><input type="text" name="username" value="" size="20" /></td>
</tr>
[% IF err_username %]
<tr><td></td><td>[% err_username %]</td></tr>
[% END %]

<tr>
 <th>Password</th>
 <td><input type="password" name="password" value="" size="20" /></td>
</tr>
[% IF err_password %]
<tr><td></td><td>[% err_password %]</td></tr>
[% END %]
<tr>
 <th>Confirm Password</th>
 <td><input type="password" name="password_confirm" value="" size="20" /></td>
</tr>

<tr>
 <th>Email</th>
 <td><input type="text" name="email" value="" size="20" /></td>
</tr>
[% IF err_email %]
<tr><td></td><td>[% err_email %]</td></tr>
[% END %]

<tr>
 <td colspan="2" align="right"><input type="submit" id="submit" value="Register" /></td>
</tr>
<input type="hidden" name="rm" value="process_register">
</table>
</form>

messages.conf

[password]
err_eq_with=password doesn\'t match.
err_max_length=must have at most 50 characters.
err_min_length=must have at least 7 characters.

[username]
err_length_between=must be between 6 and 30 characters long.
err_invalid_characters=only letters (a-z)\, numbers (0-9)\, and periods (.) are  allowed.
err_username_taken=this username is taken.

[email]
err_email=invalid email address