HTML::FormHandler::ManUser:Contributed PHTML::FormHandler::Manual::Cookbook(3)NAMEHTML::FormHandler::Manual::Cookbook - FormHandler use recipes
VERSION
version 0.35005
SYNOPSIS
Collection of use recipes for HTML::FormHandler
No form file, no template file...
I had to create a tiny little form this week for admins to enter a
comment, and it seemed silly to have to create a form file and a
template file. I remembered that you can set the TT 'template' to a a
string reference and not use a template at all, which is nice when
FormHandler will create the form HTML for you anyway.
sub comment : Chained('base_sub') PathPart('comment') Args(0) {
my ( $self, $c ) = @_;
my $form = HTML::FormHandler->new( field_list =>
[ comment => { type => 'Text', size => 60 },
submit => {type => 'Submit'} ] );
$form->process($c->req->params);
if ( $form->validated ) {
$self->admin_log( $c, "Admin::Queue", "admin comment",
$form->field('comment')->value );
$c->flash( message => 'Comment added' );
$c->res->redirect( $c->stash->{urilist}->{view} );
}
my $rendered_form = $form->render;
$c->stash( template => \$rendered_form );
}
This creates the form on the fly with a comment field and a submit
button, renders it using the default TT wrappers, then logs the
comment. No other files at all....
FormHandler isn't really necessary for validation here, but it does
make it possible to have a simple, standalone method.
Dynamically change the active fields
A common use case is for forms with some fields that should be
displayed in some circumstances and not in others. There are a number
of ways to do this. One way is to use the 'field_list' method:
sub field_list {
my $self = shift;
my @fields;
<build list of fields>
return \@fields;
}
This only happens at form construction time, however. Another method
that works is to define all of the possible fields in your form, and
mark some of them 'inactive';
package MyApp::Variable::Form;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'foo';
has_field 'bar' => ( inactive => 1 );
1;
Set to 'active' or 'inactive' on the 'process' call:
$form->process( params => $params, active => ['foo', 'bar'] );
...
$form->process( params => $params, inactive => ['bar'] );
If you need to check some other state to determine whether or not a
field should be active, you can do that using a Moose method modifier
on 'set_active':
before 'set_active' => sub {
my $self = shift;
$self->active(['foo', bar']) if ( <some_condition> );
};
Fields set to active/inactive on the 'process' call be automatically
set back to inactive when the form is cleared, so there's no need to
reset.
If you want the fields activated for the life of an object, set active
on new:
my $form = MyApp::Form::User->new( active => ['opt_in', 'active']);
Add custom attributes to FormHandler fields
If you want to add custom attributes to the FormHandler fields but
don't want to subclass all the fields, you can apply a role containing
the new attributes to an HTML::FormHandler::Field in your form.
Use 'traits' on the individual fields to apply a role to field
instances. Use the form attribute 'field_traits' to apply a role to
all field instances in the form.
package MyApp::Form::Test;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'foo' => ( traits => ['MyApp::TraitFor::Test'] );
has '+field_traits' => ( default => sub { ['Some::Trait', 'Another::Trait'] } );
Or set the traits on new:
my $form = MyApp::Form::User->new( field_traits => ['MyApp::TraitFor::Test'] );
my $form = MyApp::Form::User->new(
field_list => [ '+foo' => { traits => [...] } ]);
To apply the role to a field base class, use 'apply_traits' on that
class:
HTML::FormHandler::Field->apply_traits( 'Some::Test' );
HTML::FormHandler::Field::Text->apply_traits( 'Another::Trait' );
Select lists
If you want to set the default value of a select field to 0 (or some
other default):
sub default_license {
my ( $self, $field, $item ) = @_;
return 0 unless $item && $item->license_id;
return $item->license_id;
}
If the table defining the choices for a select list doesn't include a
'no choice' choice, in your template:
[% f = form.field('subject_class') %]
<select id="select_sc" name="[% f.name %]">
<option value="">--- Choose Subject Class---</option>
[% FOR option IN f.options %]
<option value="[% option.value %]"
[% IF option.value == f.fif %]selected="selected"[% END %]>
[% option.label | html %]</option>
[% END %]
</select>
Or customize the select list in an 'options_' method:
sub options_country {
my $self = shift;
return unless $self->schema;
my @rows =
$self->schema->resultset( 'Country' )->
search( {}, { order_by => ['rank', 'country_name'] } )->all;
return [ map { $_->digraph, $_->country_name } @rows ];
}
The database and FormHandler forms
If you have to process the input data before saving to the database,
and this is something that would be useful in other places besides your
form, you should do that processing in the DBIx::Class result class.
If the pre-processing is only relevant to HTML form input, you might
want to do it in the form by setting a flag to prevent database
updates, performing the pre-processing, and then updating the database
yourself.
has_field 'my_complex_field' => ( type => 'Text', noupdate => 1 );
The 'noupdate' flag is set in order to skip an attempt to update the
database for this field (it would not be necessary if the field doesn't
actually exist in the database...). You can process the input for the
non-updatable field field in a number of different places, depending on
what is most logical. Some of the choices are:
1) validate (for the form or field)
2) validate_model
3) model_update
When the field is flagged 'writeonly', the value from the database will
not be used to fill in the form (put in the "$form->fif" hash, or the
field "$field->fif"), but a value entered in the form WILL be used to
update the database.
If you want to enter fields from an additional table that is related to
this one in a 'single' relationship, you can use the DBIx::Class
'proxy' feature to create accessors for those fields.
Set up form base classes or roles for your application
You can add whatever attributes you want to your form classes. Maybe
you want to save a title, or a particular navigation widget. You could
even save bits of text, or retrieve them from the database. Sometimes
doing it this way would be the wrong way. But it's your form, your
choice. In the right circumstances, it might provide a way to keep code
out of your templates and simplify your controllers.
package MyApp::Form::Base;
use Moose;
extends 'HTML::FormHandler::Model::DBIC';
has 'title' => ( isa => 'Str', is => 'rw' );
has 'nav_bar' => ( isa => 'Str', is => 'rw' );
sub summary {
my $self = shift;
my $schema = $self->schema;
my $text = $schema->resultset('Summary')->find( ... )->text;
return $text;
}
1;
Then:
package MyApp::Form::Whatsup;
use Moose;
extends 'MyApp::Form::Base';
has '+title' => ( default => 'This page is an example of what to expect...' );
has '+nav_bar' => ( default => ... );
...
1;
And in the template:
<h1>[% form.title %]</h1>
[% form.nav_bar %]
<p><b>Summary: </b>[% form.summary %]</p>
Or you can make these customizations Moose roles.
package MyApp::Form::Role::Base;
use Moose::Role;
...
package MyApp::Form::Whatsup;
use Moose;
with 'MyApp::Form::Role::Base';
...
Split up your forms into reusable pieces
A person form:
package Form::Person;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'name';
has_field 'telephone';
has_field 'email' => ( type => 'Email' );
sub validate_name {
....
}
no HTML::FormHandler::Moose;
1;
An address form:
package Form::Address;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'street';
has_field 'city';
has_field 'state' => ( type => 'Select' );
has_field 'zip' => ( type => '+Zip' );
sub options_state {
...
}
no HTML::FormHandler::Moose;
1;
A form that extends them both:
package Form::Member;
use Moose;
extends ('Form::Person', 'Form::Address');
use namespace::autoclean;
1;
Or if you don't need to use the pieces of your forms as forms themself,
you can use roles;
package Form::Role::Address;
use HTML::FormHandler::Moose::Role;
has_field 'street';
has_field 'city';
has_field 'state' => ( type => 'Select' );
has_field 'zip' => ( type => '+Zip' );
sub options_state {
...
}
no HTML::FormHandler::Moose::Role;
1;
You could make roles that are collections of validations:
package Form::Role::Member;
use Moose::Role;
sub check_zip {
...
}
sub check_email {
...
}
1;
And if the validations apply to fields with different names, specify
the 'set_validate' on the fields:
with 'Form::Role::Member';
has_field 'zip' => ( type => 'Integer', set_validate => 'check_zip' );
Access a user record in the form
You might need the user_id to create specialized select lists, or do
other form processing. Add a user_id attribute to your form:
has 'user_id' => ( isa => 'Int', is => 'rw' );
Then pass it in when you process the form:
$form->process( item => $item, params => $c->req->parameters, user_id => $c->user->user_id );
Handle extra database fields
If there is another database field that needs to be updated when a row
is created, add an attribute to the form, and then process it with "
before 'update_model' ".
In the form:
has 'hostname' => ( isa => 'Int', is => 'rw' );
before 'update_model' => sub {
my $self = shift;
$self->item->hostname( $self->hostname );
};
Then just use an additional parameter when you create/process your
form:
$form->process( item => $item, params => $params, hostname => $c->req->host );
Record the user update
Use the 'before' or 'after' method modifiers for 'update_model', to
flag a record as updated by the user, for example:
before 'update_model' => sub {
my $self = shift;
$self->item->user_updated if $self->item;
};
Additional changes to the database
If you want to do additional database updates besides the ones that
FormHandler does for you, the best solution would generally be to add
the functionality to your result source or resultset classes, but if
you want to do additional updates in a form you should use an 'around'
method modifier and a transaction:
around 'update_model' => sub {
my $orig = shift;
my $self = shift;
my $item = $self->item;
$self->schema->txn_do( sub {
$self->$orig(@_);
<perform additional updates>
});
};
Doing cross validation in roles
In a role that handles a number of different fields, you may want to
perform cross validation after the individual fields are validated. In
the form you could use the 'validate' method, but that doesn't help if
you want to keep the functionality packaged in a role. Instead you can
use the 'after' method modifier on the 'validate' method:
package MyApp::Form::Roles::DateFromTo;
use HTML::FormHandler::Moose::Role;
has_field 'date_from' => ( type => 'Date' );
has_field 'date_to' => ( type => 'Date' );
after 'validate' => sub {
my $self = shift;
$self->field('date_from')->add_error('From date must be before To date')
if $self->field('date_from')->value gt $self->field('date_to')->value;
};
Changing required flag
Sometimes a field is required in one situation and not required in
another. You can use a method modifier before 'validate_form':
before 'validate_form' => sub {
my $self = shift;
my $required = 0;
$required = 1
if( $self->params->{field_name} eq 'something' );
$self->field('some_field')->required($required);
};
This happens before the fields contain input or values, so you would
need to look at the param value. If you need the validated value, it
might be better to do these sort of checks in the form's 'validate'
routine.
sub validate {
my $self = shift;
$self->field('dependent_field')->add_error("Field is required")
if( $self->field('some_field')->value eq 'something' &&
!$self->field('dependent_field')->has_value);
}
In a Moose role you would need to use a method modifier instead.
after 'validate' => sub { ... };
Don't forget the dependency list, which is used for cases where if any
of one of a group of fields has a value, all of the fields are
required.
Supply an external coderef for validation
There are situations in which you need to use a subroutine for
validation which is not logically part of the form. It's possible to
pass in a context or other sort of pointer and call the routine in the
form's validation routine, but that makes the architecture muddy and is
not a clear separation of concerns.
This is an example of how to supply a coderef when constructing the
form that performs validation and can be used to set an appropriate
error using Moose::Meta::Attribute::Native::Trait::Code. (Thanks to
Florian Ragwitz for this excellent idea...)
Here's the form:
package SignupForm;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has check_name_availability => (
traits => ['Code'],
isa => 'CodeRef',
required => 1,
handles => { name_available => 'execute', },
);
has_field 'name';
has_field 'email';
sub validate {
my $self = shift;
my $name = $self->value->{name};
if ( defined $name && length $name && !$self->name_available($name) ) {
$self->field('name')->add_error('That name is taken already');
}
}
1;
And here's where the coderef is passed in to the form.
package MyApp::Signup;
use Moose;
has 'form' => ( is => 'ro', builder => 'build_form' );
sub build_form {
my $self = shift;
return SignupForm->new(
{
check_name_availability => sub {
my $name = shift;
return $self->username_available($name);
},
}
);
}
sub username_available {
my ( $self, $name ) = @_;
# perform some sort of username availability checks
}
1;
Example of a form with custom database interface
The default DBIC model requires that the form structure match the
database structure. If that doesn't work - you need to present the form
in a different way - you may need to fudge it by creating your own
'init_object' and doing the database updates in the 'update_model'
method.
Here is a working example for a 'family' object (equivalent to a 'user'
record') that has a relationship to permission type roles in a
relationship 'user_roles'.
package My::Form::AdminRoles;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has 'schema' => ( is => 'ro', required => 1 ); # Note 1
has '+widget_wrapper' => ( default => 'None' ); # Note 2
has_field 'admin_roles' => ( type => 'Repeatable' ); # Note 3
has_field 'admin_roles.family' => ( type => 'Hidden' ); # Note 4
has_field 'admin_roles.family_id' => ( type => 'PrimaryKey' ); # Note 5
has_field 'admin_roles.admin_flag' => ( type => 'Boolean', label => 'Admin' );
# Note 6
sub init_object {
my $self = shift;
my @is_admin;
my @is_not_admin;
my $active_families = $self->schema->resultset('Family')->search( { active => 1 } );
while ( my $fam = $active_families->next ) {
my $admin_flag =
$fam->search_related('user_roles', { role_id => 2 } )->count > 0 ? 1 : 0;
my $family_name = $fam->name1 . ", " . $fam->name2;
my $elem = { family => $family_name, family_id => $fam->family_id,
admin_flag => $admin_flag };
if( $admin_flag ) {
push @is_admin, $elem;
}
else {
push @is_not_admin, $elem;
}
}
# Note 7
# sort into admin flag first, then family_name
@is_admin = sort { $a->{family} cmp $b->{family} } @is_admin;
@is_not_admin = sort { $a->{family} cmp $b->{family} } @is_not_admin;
return { admin_roles => [@is_admin, @is_not_admin] };
}
# Note 8
sub update_model {
my $self = shift;
my $families = $self->schema->resultset('Family');
my $family_roles = $self->value->{admin_roles};
foreach my $elem ( @{$family_roles} ) {
my $fam = $families->find( $elem->{family_id} );
my $has_admin_flag = $fam->search_related('user_roles', { role_id => 2 } )->count > 0;
if( $elem->{admin_flag} == 1 && !$has_admin_flag ) {
$fam->create_related('user_roles', { role_id => 2 } );
}
elsif( $elem->{admin_flag} == 0 && $has_admin_flag ) {
$fam->delete_related('user_roles', { role_id => 2 } );
}
}
}
Note 1: This form creates its own 'schema' attribute. You could inherit
from HTML::FormHandler::Model::DBIC, but you won't be using its update
code, so it wouldn't add much.
Note 2: The form will be displayed with a template that uses 'bare'
form input fields, so 'widget_wrapper' is set to 'None' to skip
wrapping the form inputs with divs or table elements.
Note 3: This form consists of an array of elements, so there will be a
single Repeatable form field with subfields. If you wanted to use
automatic rendering, you would also need to create a 'submit' field,
but in this case it will just be done in the template.
Note 4: This field is actually going to be used for display purposes
only, but it's a hidden field because otherwise the information would
be lost when displaying the form from parameters. For this case there
is no real 'validation' so it might not be necessary, but it would be
required if the form needed to be re-displayed with error messages.
Note 5: The 'family_id' is the primary key field, necessary for
updating the correct records.
Note 6: 'init_object' method: This is where the initial object is
created, which takes the place of a database row for form creation.
Note 7: The entries with the admin flag turned on are sorted into the
beginning of the list. This is entirely a user interface choice.
Note 8: 'update_model' method: This is where the database updates are
performed.
The Template Toolkit template for this form:
<h1>Update admin status for members</h1>
<form name="adminroles" method="POST" action="[% c.uri_for('admin_roles') %]">
<input class="submit" name="submit" value="Save" type="submit">
<table border="1">
<th>Family</th><th>Admin</th>
[% FOREACH f IN form.field('admin_roles').sorted_fields %]
<tr>
<td><b>[% f.field('family').fif %]</b>[% f.field('family').render %]
[% f.field('family_id').render %]</td><td> [% f.field('admin_flag').render %]</td>
</tr>
[% END %]
</table>
<input class="submit" name="submit" value="Save" type="submit">
</form
The form is rendered in a simple table, with each field rendered using
the automatically installed rendering widgets with no wrapper
(widget_wrapper => 'None'). There are two hidden fields here, so what
is actually seen is two columns, one with the user (family) name, the
other with a checkbox showing whether the user has admin status. Notice
that the 'family' field information is rendered twice: once as a hidden
field that will allow it to be preserved in params, once as a label.
The Catalyst controller action to execute the form:
sub admin_roles : Local {
my ( $self, $c ) = @_;
my $schema = $c->model('DB')->schema;
my $form = My::Form::AdminRoles->new( schema => $schema );
$form->process( params => $c->req->params );
# re-process if form validated to reload from db and re-sort
$form->process( params => {}) if $form->validated;
$c->stash( form => $form, template => 'admin/admin_roles.tt' );
return;
}
Rather than redirect to some other page after saving the form, the form
is redisplayed. If the form has been validated (i.e. the
'update_model' method has been run), the 'process' call is run again in
order to re-sort the displayed list with admin users at the top. That
could have also been done in the 'update_model' method.
A form that takes a resultset, with custom update_model
For updating a Repeatable field that is filled from a Resultset, and
not a relationship on a single row. Creates a 'resultset' attribute to
pass in a resultset. Massages the data into an array that's pointed to
by an 'employers' hash key, and does the reverse in the 'update_model'
method. Yes, it's a kludge, but it could be worse. If you want to
impement a more general solution, patches welcome.
package Test::Resultset;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Model::DBIC';
has '+item_class' => ( default => 'Employer' );
has 'resultset' => ( isa => 'DBIx::Class::ResultSet', is => 'rw',
trigger => sub { shift->set_resultset(@_) } );
sub set_resultset {
my ( $self, $resultset ) = @_;
$self->schema( $resultset->result_source->schema );
}
sub init_object {
my $self = shift;
my $rows = [$self->resultset->all];
return { employers => $rows };
}
has_field 'employers' => ( type => 'Repeatable' );
has_field 'employers.employer_id' => ( type => 'PrimaryKey' );
has_field 'employers.name';
has_field 'employers.category';
has_field 'employers.country';
sub update_model {
my $self = shift;
my $values = $self->values->{employers};
foreach my $row (@$values) {
delete $row->{employer_id} unless defined $row->{employer_id};
$self->resultset->update_or_create( $row );
}
}
Server-provided dynamic value for field
There are many different ways to provide values for fields. Default
values can be statically provided in the form with the 'default'
attribute on the field, with a default_<field_name> method in the form,
with an init_object/item, and with 'default_over_obj' if you have both
an item/init_object and want to provide a default.
has_field 'foo' => ( default => 'my_default' );
has_field 'foo' => ( default_over_obj => 'my_default' );
sub default_foo { 'my_default' }
..
$form->process( init_object => { foo => 'my_default } );
$form->process( item => <object with $obj->foo method to provide default> );
If you want to change the default for the field at run time, there are
a number of options.
You can set the value in the init_object or item before doing process:
my $foo_value = 'some calculated value';
$form->process( init_object => { foo => $foo_value } );
You can use 'update_field_list' on the 'process' call:
$form->process( update_field_list => { foo => { default => $foo_value } } );
You can set a Moose attribute in the form class, and set the default in
a default_<field_name> method:
package My::Form;
use HTML::FormHandler::Moose;
extends 'HTML::Formhandler';
has 'form_id' => ( isa => 'Str', is => 'rw' );
has_field 'foo';
sub default_foo {
my $self = shift;
return $self->form_id;
}
....
$form->process( form_id => 'my_form', params => $params );
You can set a Moose attribute in the form class and set it in an
update_fields method:
sub update_fields {
my $self = shift;
$self->field('foo')->default('my_form');
}
Static form, dynamic field IDs
The problem: you have a form that will be used in multiple places on a
page, but you don't want to use a static form instead of doing 'new'
for each. You can pass a form name in on the process call and use
'html_prefix' in the form:
$form->process( name => '...', params => {} );
But the field 'id' attribute has already been constructed and doesn't
change.
Solution: apply a role to the base field class to replace the 'id'
getter for the 'id' attribute with a method which constructs the 'id'
dynamically. Since the role is being applied to the base field class,
you can't just use 'sub id', because the 'id' method defined by the
'id' attribute has precedence. So create an 'around' method modifier
that replaces it in the role.
package My::DynamicFieldId;
use Moose::Role;
around 'id' => sub {
my $orig = shift;
my $self = shift;
my $form_name = $self->form->name;
return $form_name . "." . $self->full_name;
};
package My::CustomIdForm;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has '+html_prefix' => ( default => 1 );
has '+field_traits' => ( default => sub { ['My::DynamicFieldId'] } );
has_field 'foo';
has_field 'bar';
AUTHOR
FormHandler Contributors - see HTML::FormHandler
COPYRIGHT AND LICENSE
This software is copyright (c) 2011 by Gerda Shank.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
perl v5.14.12011-1HTML::FormHandler::Manual::Cookbook(3)