Abstract
There is more than one way to handle forms in your Catalyst/DBIC application. This paper speaks to the HTML::FormHandler way. Let's start with a simple example of building a registration form to allow new users to register with your website application1. We will look at how to build a registration form object and use it in a Catalyst controller. Furthermore, the form will integrate with a DBIC connected database to persist the registration information submitted via the form.
Build a Form Class
We build a registration form class that uses the DBIC model like so:
package Bracket::Form::Register;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Model::DBIC';
with 'HTML::FormHandler::Render::Table';
has '+item_class' => ( default => 'Player' );
has_field 'first_name' => ( type => 'Text' );
has_field 'last_name' => ( type => 'Text' );
has_field 'email' => (
type => 'Email',
required => 1,
unique => 1,
);
has_field 'password' => ( type => 'Password' );
has_field 'password_confirm' => ( type => 'PasswordConf' );
has_field 'submit' => ( type => 'Submit', value => 'Register' );
has '+unique_messages' =>
( default => sub { { player_email =>
'Email already registered' } } );
no HTML::FormHandler::Moose;
__PACKAGE__->meta->make_immutable;
1
DBIC Model
Notice that we're extending the DBIC model so we can easily create records in our players (think users) table. The '+item_class' attribute is where we specifiy the DBIC Result class that we bind the form to. In our case this is the Player class that corresponds to the player table in our database. Player is synonymous with the more commonly found user table where one has a username and password column.
Form Field Types
FormHandler has a Moose-like attribute declarer has_field. This allows one to declare fields where the type is a HTML::FormHandler::Field such as 'Text', 'Email', 'Password' etc. These type declarations enable cheap and effective validation. For example, a field with type Email will be checked using Email::Valid. In addition we set the email field as required and unique.
Render as a Table
I've asked for the rendering to be in table form with the 'HTML::FormHandler::Render::Table role.
Make the Form Available in a Controller
In order to have an instance of the registration form object available in a Catalyst controller I do:
use Bracket::Form::Register;
has 'register_form' => (
isa => 'Bracket::Form::Register',
is => 'rw',
lazy => 1,
default => sub { Bracket::Form::Register->new },
);
Later in a controller action I will be able to access the form via $self->register_form.
Use the Form in a Controller Action
sub register : Global {
my ( $self, $c ) = @_;
# Stash the form and the template to render it
$c->stash(
template => 'form/auth/register.tt',
form => $self->register_form,
);
# Create an empty row object for the desired table
my $new_player = $c->model('DBIC::Player')->new_result( {} );
# Process the for with the parameters, a schema and a row object
$self->register_form->process(
item => $new_player,
params => $c->request->parameters,
);
# This returns on GET (new form) and a POSTed form that's invalid.
return if !$self->register_form->is_valid;
# At this stage the form has validated
$c->flash->{status_msg} = 'Registration initiated';
$c->response->redirect( $c->uri_for('/login') );
}
Here we have one action that handles both the GET and POST request methods of the form. On GET, HTML::FormHandler will see that there are no parameters to process and just return the form. On POST, it will check that the parameters are valid and then process them accordingly. In our case this means, create a new user record from the form fields where each form field that corresponds to a column in our player table will be used to populate the column.
Stash the Form and Template
We start the action body off by stashing the form and template that will be used to render it. All we need in the template2 is:
[% form.render %]
New Row Object
Since we want to create a new record in our Player class, we create a new row object of that nature with DBIC's new_result() method3.
Arguments to Process
Next we process the form with the following key/value combinations:
- item := which gets our row object, $new_player, where we'll store the form data
- params := the parameters in our context
Note that since we're passing in a DBIC row object as our item the schema is inferred. If one is passing in a primary key this can be done by passing in the primary key as 'item_id' and additionally pass the schema. For example:
$self->form->process(
item_id => 1,
params => $c->request->parameters,
schema => $c->model('DBIC')->schema,
)
Validation
The form is then validated and returned in its orginal form if it's not valid. Otherwise we have a successful registration and proceed to login5.
Conclusion
FormHandler enables one to efficiently define forms and bind them to a DBIC model. In addition, it allows types to be declared on fields which are then used in validation. It also supports other form field properties such as: unique and required. In short, it does all that is needed for basic form handling without having to write a single line of HTML.
Footnotes
1 Instead of the proverbial 'MyApp', I'm using the application name 'Bracket'. This is for a NCAA tournament bracket app. I'm cooking up. Ping me if your interested in it.
2 We're using a Template Toolkit view.
3 The row is not actually inserted yet, just prepared to be.
4 When we pass a row object item to process() we don't need to specify the schema.
5 An upcoming article will discuss how to build a login form backed with authentication in Catalyst using FormHandler and DBIC.




Nice overview. Another slightly more advanced feature of HFH which I like to use is the ability to show/hide fields depending on some prerequisite.
This is done by using a method modifier for set_active and then calling the active method on those fields that have been defined with inactive => 1.
I also use the field_list to change the button text on the submit button:
return [ submit => { type => 'Submit', value => $self->action eq 'edit' ? 'Update' : 'Create' } ];