NAME
CGI::Application::Plugin::GenVal - Generate input forms with client/server validation
DESCRIPTION
This module aims to make the tricky task of setting up forms with accompanying client/server side validation much simpler. Once setup rather than editing HTML, javascript and perl all you need to do is edit one central YAML file. It uses Data::Formvalidator and a modified version of Data.FormValidator.js. It also utilizes CGI::Application::Plugin::YAML if available, otherwise loads YAML::Any. Newer versions of Data::FormValidator use compiled regexps and new style contraint methods. This broke compatibility when converting the profile to JavaScript. This module fixes that issue by having the constraints in YAML which are then compiled into the new format for Data::FormValidator and converted to the old format for Data.FormValidator.js. Allowing you to get the best from both worlds.
This is alpha, anything could change dramatically!!!
SYNOPSIS
This module allows you to consolidate your form generation, client and server side validation into a central YAML schema file.
use CGI::Application::Plugin::GenVal qw( :std );
Create Data::FormValidator Perl and JavaScript profiles, return YAML schema:-
( $dfv_profile, $js_profile, $yaml_schema ) = $self->GenVal->gen_dfv( {
schema => 'formschema.yml.pl',
form => 'form1',
required_hash => { 'field_name_1' => 1, 'field_name_2' => 1 },
required => [ 'field_name_3', 'field_name_3' ],
msgs => {
missing => 'Required',
invalid => 'Invalid',
constraints => {
'eq_with' => 'Must match',
},
},
} );
Generate input field:-
$self->GenVal->gen_input( {
field => $field,
value => $value,
details => $yaml_schema->{field_input}->{$field},
style => $yaml_schema->{field_input_style},
error => { type => 'good' },
} );
Example code... In YAML file:-
---
field_text:
name: Name
website: Website
email: Email
email-confirm: Confirm Email
phone: Phone Number
field_input:
name:
max: 24
size: 30
type: text
constraint: /^(?:[\w\- \,\.]*){0,24}$/
required: yes
website:
max: 128
size: 134
type: text
constraint: /^https?:\/\/[\w\- .]*$/
required: no
email:
max: 64
size: 70
type: text
required: yes
constraint:
- /^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,4})$/i
- constraint: method FV_eq_with('email-confirm')
email-confirm:
max: 64
size: 70
type: text
required: yes
constraint:
- /^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,4})$/i
- constraint: method FV_eq_with('email')
phone:
max: 20
size: 26
type: text
constraint: /^[0-9 \-\+\(\)]*$/i
required: no
field_input_style:
all:
normal:
style:
background: '#cdffbe'
border: '1px solid #28bc40'
border-color: '#009d16 #00e921 #00e921 #009d16'
good:
style:
background: '#cdffbe'
border: '1px solid #28bc40'
border-color: '#009d16 #00e921 #00e921 #009d16'
bad:
style:
background: '#ffbebe'
border: '1px solid #d81f1f'
border-color: '#940000 #e20404 #e20404 #940000'
signup:
name
website
email
email-confirm
phone
In HTML Template:-
<form action="cgiapp.cgi" method="post" onSubmit="return signupValidate(this);" name=signup>
<input type=hidden name=rm value="processform" />
<table border=0 cellpadding=4 cellspacing=0>
<tmpl_loop name=signup_fields>
<tr>
<td>
<tmpl_if name=field_required><b>*</b></tmpl_if>
<tmpl_var name=field_text>
<div id="error_<tmpl_var name=field_name>">
<b><tmpl_var name=field_error></b>
</div>
</td>
<td><tmpl_var name=field_input></td>
</tr>
</tmpl_loop>
</table>
<input type=submit name=submit value="Submit" />
</form>
In your CGI::Application runmode:-
use Data::FormValidator;
### Display and process form
sub processform {
my $self = shift;
### Load dfv profiles and schema
my ( $dfv_profile, $js_profile, $yaml_schema ) = $self->GenVal->gen_dfv( {
schema => 'signup.yml.pl',
form => 'signup',
msgs => {
missing => 'Required',
invalid => 'Invalid',
constraints => {
'eq_with' => 'Must match',
},
},
} );
### Check if we've received data and validate
my $errorhash;
if ( $self->query->param( 'submit' ) ) {
my $results = Data::FormValidator->check( $self->query, $dfv_profile );
### get error details
$errorhash = $results->msgs;
### Forword to next runmode if no errors
unless ( keys %$errorhash ) {
return $self->forward('saveform');
}#else
}#if
### Prepare form input field style
my $errordetails = {
type => 'normal',
};
### Loop through fields for this form
my @signup_fields;
foreach my $field ( @{ $yaml_schema->{signup} } ) {
### Style fields to good or bad if input did not validate
if ( $errorhash ) {
if ( $errorhash->{"err_$field"} ) {
$errordetails = {
type => 'bad',
text => $errorhash->{"err_$field"},
};
}#if
else {
$errordetails->{type} = 'good';
}#else
}#if
### Fix required for tmpl_if
### Needed because we allow people to set required to 'no'
my $required = 0;
if ( $yaml_schema->{field_input}->{$field}->{required} && lc( $yaml_schema->{field_input}->{$field}->{required} ) ne 'no' ) {
$required = 1;
}#if
### Generate input field and prepare for template
my $value = $self->query->param( $field );
push( @signup_fields, {
field_name => $field,
field_required => $required,
field_text => $yaml_schema->{field_text}->{$field},
field_error => $errorhash->{"err_$field"},
field_input => $self->GenVal->gen_input(
$field,
$value,
$yaml_schema->{field_input}->{$field},
$yaml_schema->{field_input_style},
$errordetails,
),
});
}#foreach
my $template = $self->load_tmpl( 'form.html', die_on_bad_params => 0 );
$template->param(
{
js_profile => $js_profile,
signup_fields => \@signup_fields,
}
);
return $template->output();
}#sub
Object
GenVal
This is the only method returned. It is in fact an object with the following methods.
Methods
gen_input
This method generates the input fields. The following describes it's input parameters which must be passed in as a hash reference.
style
The fields can have different styles, such as normal
(used the first time form is displayed), good
(if the input value validated) and bad
(if the input value failed validation). These styles can be applied to specific field types, or as the default for all field types by defining a key called all
. The style
parameter should be passed in as a hash reference in the format:-
$style = {
all => {
normal => {
style => {
background => '#cdffbe',
border => '1px solid #28bc40',
border-color => '#009d16 #00e921 #00e921 #009d16',
},
},
good => {
style => {
background => '#cdffbe',
border => '1px solid #28bc40',
border-color => '#009d16 #00e921 #00e921 #009d16',
},
},
bad => {
style => {
background => '#ffbebe',
border => '1px solid #d81f1f',
border-color => '#940000 #e20404 #e20404 #940000',
},
},
},
select => { ### Overload 'all' defaults for 'select' field types
normal => {
style => {
background => '#ffffff',
},
},
};
This is usually stored in your YAML schema rather than your Perl code.
field_input_style:
all:
normal:
style:
background: '#cdffbe'
border: '1px solid #28bc40'
border-color: '#009d16 #00e921 #00e921 #009d16'
good:
style:
background: '#cdffbe'
border: '1px solid #28bc40'
border-color: '#009d16 #00e921 #00e921 #009d16'
bad:
style:
background: '#ffbebe'
border: '1px solid #d81f1f'
border-color: '#940000 #e20404 #e20404 #940000'
select:
normal:
style:
background: '#ffffff'
field
This is the field name
value
This is the field value (if any)
details
You must define the details of your form field, such as type, size, etc. The details parameter must be a hash reference with keys of type
(values text, password, select, custom) max
(used for text and password field types) size
(field display size) source
(data source for some types).
These are generally stored in your YAML schema:-
field_input:
name:
max: 24
size: 30
type: text
constraint: /^(?:[\w\- \,\.]*){0,24}$/
required: yes
website:
max: 128
size: 134
type: text
constraint: /^https?:\/\/[\w\- .]*$/
required: no
Notice how the schema includes extra keys for constraint and required. They are not used here, but do not cause any problems.
For select
field types, you must provide a data source. This is passed in value
, and expected to return a hash reference of labels (key being the option value, value being the option label), an array reference of option values (to get the order) and scalar of the default value. The source must be defined as a string in the form:-
source => 'sub self SUBNAME', ### Object method
Or
source => 'sub SUBNAME', ### Subroutine
Which are translated to:-
( $labels, $values, $default ) = $self->SUBNAME( $value );
And
( $labels, $values, $default ) = SUBNAME( $value );
For custom
field types, you must also provide a data source. This is passed in value
and a CGI.pm style tag attribute. It's expected to return the HTML for the form field. The source must be defined as a string in the form:-
source => 'sub self SUBNAME', ### Object method
Or
source => 'sub SUBNAME', ### Subroutine
Which are translated to:-
$html = $self->SUBNAME( $value, $style );
And
$html = SUBNAME( $value );
Examples in YAML:-
field_input:
month:
type: select
source: sub self month_select
constraint: /^[\w]*$/
timezone:
type: custom
source: sub create_timezones
required: yes
constraint: /^[\w\-\/]*$/
error
This parameter is used to define the style used, and provide any error text. It must be a hash reference with keys type
to match the style type (normal, good, bad) text
the error message if any
$errorhash = {
type => 'normal'
};
$errorhash = {
type => 'bad',
text => 'Field required',
};
gen_dfv
This method generates the DFV profiles for Perl and JavaScript from YAML schema. It returns both profiles and the YAML schema. The YAML schema can be passed in, or you can pass a filename instead. The following describes it's input parameters which must be passed in as a hash reference.
prefix
Set a prefix for field error messages. This is the same as DFV {msgs}->{prefix}. Defaults to 'err_' to make 'err_FIELDNAME' => 'Required', etc.
any_errors
Sets a flag in the results if any errors were returned. Same as DFV {msgs}->{any_errors}. Defaults to 'some_errors'.
required
List of required fields. Defaults to empty. This list is added to from the YAML schema field_input settings.
required_hash
Hash reference of required fields. This is useful if you have your required fields defined by a configuration file. Can be in the form:-
{required_hash}->{fieldname}->{required} = 1;
or
{required_hash}->{fieldname} = 1;
constraints_loaded
Hash list of constrant methods from Data::FormValidator::Constraints that have already been loaded. This is useful if you have multiple forms to save importing them again.
schema
This is either your pre-loaded YAML schema, or the file location. Such as:-
{schema} => $LoadedYAML,
or
{schema} => 'myform.yml.pl',
Schema must be in the format:-
---
field_text:
FIELDNAME: DESCRIPTION
FIELDNAME2: DESCRIPTION2
field_input:
FIELDNAME:
constraint: /^(?:[\w\- \,\.]*){0,24}$/
required: yes
FIELDNAME2:
constraint: /^https?:\/\/[\w\- .]*$/
required: no
FORMNAME:
FIELDNAME
FIELDNAME2
You must have a key called field_input that contains a hash of the fieldnames, which in turn contain a hash of the constraint and if the field is required. The FORMNAME list if used to generate the optional fields for this form.
form
The name of this form (required). Naming the form allows you to have multiple forms on the same page.
msgs
This is simply passed through to the DFV {msgs} key. If you set {msgs}->{prefix} or {msgs}->{any_errors} they will override the {prefix} and {any_errors} described above.
About Constraints
Newer versions of Data::FormValidator use compiled regexps and new style contraint methods. This broke compatibility when converting the profile to JavaScript. This module fixes that issue by having the constraints in YAML which are then compiled into the new format for Data::FormValidator and converted to the old format for Data.FormValidator.js. Allowing you to get the best from both worlds.
All constraints must be in a key called 'constraint'. This is loaded to 'constraint_methods' for Perl and 'constraints' for JavaScript.
You can define multiple constraints by having the 'constraint' key point to an array, such as:-
field_input:
FIELDNAME:
constraint:
- /^[a-z]*$/i
- /^[A-C]*$/
This would generate Perl of:-
$dfv->{constraint_methods}->{FIELDNAME} = [
qr/^[a-z]*$/i
qr/^[A-C]*$/
];
JavaScript of (well perl version of javascript):-
$dfv->{constraints}->{FIELDNAME} = [
'/^[a-z]*$/i'
'/^[A-C]*$/'
];
If JavaScript doesn't support the regexp such as those ending in or /s or /x, then the constraint isn't loaded into JavaScript:-
field_input:
FIELDNAME:
constraint:
- /^[a-z]*$/i
- /^[A-C]*$/s
This would generate Perl of:-
$dfv->{constraint_methods}->{FIELDNAME} = [
qr/^[a-z]*$/i
qr/^[A-C]*$/s
];
JavaScript of (well perl version of javascript):-
$dfv->{constraints}->{FIELDNAME} = '/^[a-z]*$/i';
You can also use the new DFV built in methods, such as FV_eq_with. This is downgraded to an old style version for JavaScript:-
field_input:
email:
required: yes
constraint:
- /^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,4})$/i
- method FV_eq_with('email-confirm')
email-confirm:
required: yes
constraint:
- /^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,4})$/i
- method FV_eq_with('email')
This would generate Perl of:-
$dfv->{constraint_methods} =
'email' => [
qr/^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,4})$/i,
\&FV_eq_with( 'email-confirm' ), ### Similar, FV_eq_with actually returns a subroutine
],
'email-confirm' => [
qr/^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,4})$/i,
\&FV_eq_with( 'email' ), ### Similar, FV_eq_with actually returns a subroutine
],
JavaScript of (well perl version of javascript):-
$dfv->{constraints} =
'email' => [
'/^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,4})$/i',
{
constraint => 'FV_eq_with',
params => [ 'email-confirm' ],
name => 'eq_with',
},
],
'email-confirm' => [
qr/^(([a-z0-9_\.\+\-\=\?\^\#]){1,64}\@(([a-z0-9\-]){1,251}\.){1,252}[a-z0-9]{2,4})$/i,
{
constraint => 'FV_eq_with',
params => [ 'email' ],
name => 'eq_with',
},
],
About JavaScript
The returned JavaScript code includes a function named 'formValidate' (where form is form name) encased in a <SCRIPT> tag ready to be parsed into your HTML page. You also need to update your <FORM> tag to include:-
onSubmit="return formValidate(this);"
Such as:-
<form action="cgiapp.cgi" method="post" onSubmit="return formnameValidate(this);" name=formname>
Export groups
Only an object called GenVal is exported.
:all exports:-
GenVal
:std exports:-
GenVal
Thanks to:-
Come join the bestest Perl group in the World!
Bristol and Bath Perl moungers is renowned for being the friendliest Perl group in the world. You don't have to be from the UK to join, everyone is welcome on the list:- http://perl.bristolbath.org
AUTHOR
Lyle Hopkins ;)