NAME¶
HTML::FormHandler::Manual::Tutorial - how to use FormHandler with Catalyst
VERSION¶
version 0.40013
SYNOPSIS¶
Manual Index
A tutorial for beginners to HTML::FormHandler
This tutorial demonstrates how you can use HTML::FormHandler to manage forms,
validate form input, and interface your forms with the database.
Installation¶
Use CPAN to install HTML::FormHandler
Use the Tutorial application¶
We'll use the files that were created in the Catalyst::Manual::Tutorial, in
order to concentrate on just the bits where HTML::FormHandler is useful. You
can download a tar file of the tutorial files from the Catalyst code
repository. (See Catalyst::Manual::Tutorial::Intro.)
Untar the tutorial and make a lib/MyApp/Form directory. In that directory create
the file Book.pm.
package MyApp::Form::Book;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Model::DBIC';
has '+item_class' => ( default => 'Book' );
has_field 'title' => ( type => 'Text' );
has_field 'rating' => ( type => 'Integer' );
has_field 'authors' => ( type => 'Multiple', label_column => 'last_name' );
has_field 'submit' => ( type => 'Submit', value => 'Submit' );
no HTML::FormHandler::Moose;
1;
This is your Form class. The form initializes the 'item_class' to the source
name of your DBIx::Class result class. The form's fields are defined with the
'has_field' sugar, or in a 'field_list'. The names of the fields should match
a column, relationship, or other accessor in your DBIx::Class result class.
The basic fields have only a 'type', such as 'Text', or 'Integer'. These types
are actually the names of HTML::FormHandler::Field classes. 'Text' and
'Integer' are types that are provided by HTML::FormHandler, in
HTML::FormHandler::Field::Text and HTML::FormHandler::Field::Integer.
The 'Multiple' type will allow you to easily create a multiple select list from
the 'authors' relationship. The 'label_column' attribute must be defined
because the column in the 'authors' table which is used to create the select
list does not have the default column name ('name').
The 'submit' field is necessary if you are going to use FormHandler to render
your form. It wouldn't be necessary for hand-built templates or HTML.
Eventually you will want to create your own field classes, but for this simple
form the default types are adequate.
Edit lib/MyApp/Controller/Books.pm. Add use Moose:
use Moose;
BEGIN { extends 'Catalyst::Controller' }
use MyApp::Form::Book;
Create an attribute to hold your form:
has 'form' => ( isa => 'MyApp::Form::Book', is => 'rw',
lazy => 1, default => sub { MyApp::Form::Book->new } );
In "lib/MyApp/Controller/Books.pm" add the following method:
sub edit : Local {
my ( $self, $c, $book_id ) = @_;
$c->stash( template => 'books/edit.tt2',
form => $self->form );
# Validate and insert/update database
return unless $self->form->process( item_id => $book_id,
params => $c->req->parameters,
schema => $c->model('DB')->schema );
# Form validated, return to the books list
$c->flash->{status_msg} = 'Book saved';
$c->res->redirect($c->uri_for('list'));
}
This will handle both creating new books, and updating old books. If $book_id is
undefined, then HTML::FormHandler will create a new book from your form. If
you pass in a DBIx::Class row object instead of a primary key, you don't need
to specify the schema.
Save a copy of "root/src/books/edit.tt2" and create a new file that
contains only:
[% form.render %]
Although the automatic rendering works well, sometimes it's necessary to hand
build HTML. This section contains an example of a Template Toolkit template
that may be used to display a FormHandler form.
In some cases, you might want to use the rendering for just the field and build
custom divs or tables or whatever around it:
<div class="mycustomclass">
[% form.render_field('book') %]
</div>
If you don't want to play with HTML at this point, you can skip ahead to the
next section.
You could also use TT macros to do pretty sophisticated template generation. But
for now, we'll stick to a straightforward TT template:
Delete the single statement in "root/src/books/edit.tt2", and enter or
copy the following:
[% META title = 'Book Form' %]
[% FOR field IN form.error_fields %]
[% FOR error IN field.errors %]
<p><span class="error" id="error">
[% field.label _ ': ' _ error %] </span></p>
[% END %]
[% END %]
<form name="[% form.name %]"
action="[% c.uri_for('edit', form.item_id) %]"
method="post">
<p>
[% f = form.field('title') %]
<label class="label" for="[% f.name %]">[% f.label %]:</label>
<input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]">
</p>
<p>
[% f = form.field('rating') %]
<label class="label" for="[% f.name %]">[% f.label %]:</label>
<input type="text" name="[% f.name %]" id="[% f.name %]" %] value="[% f.fif %]">
</p>
<p>
[% f = form.field('authors') %]
<label class="label" for="[% f.name %]">[% f.label %]:</label>
<select name="[% f.name %]" multiple="multiple" size="[% f.size %]">
[% FOR option IN f.options %]
<option value="[% option.value %]"
[% FOREACH selval IN f.fif %]
[% IF selval == option.value %]selected="selected"[% END %]
[% END %]>
[% option.label | html %]</option>
[% END %]
</select>
</p>
<input class="button" name="submit" type="submit" value="Submit" />
</form>
<p><a href="[% c.uri_for('list') %]">Return to book list</a></p>
Add links to access create and update actions¶
Add a link to root/src/books/list.tt2 to allow you to edit an existing book, by
changing the last <td> cell in the book list:
<td>
<a href="[% c.uri_for('delete', book.id) %]">Delete</a>|
<a href="[% c.uri_for('edit', book.id) %]">Edit</a>
</td>
Change the link to create a book at the bottom of the file:
<p>
<a href="[% c.uri_for('edit') %]">Create book</a>
</p>
Start up the server for MyApp:
$ script/myapp_server.pl
(You'll need to login with test01/mypass if you're using the packaged tutorial.)
Click the new "Create book" link at the bottom to display the form.
Fill in the fields and click submit. You should be returned to the Book List
page with a "Book saved" message.
Magic! A new book has been created and saved to the database with very little
code in your controller.
Click on the 'edit' links, and edit the existing books. Changes should be saved
and displayed properly. Try to add an alphabetic character to the rating
field. You should get an error message.
We'll add a couple of 'label' attributes to the fields:
has_field 'title' => ( type => 'Text', label => 'Title of a Book' );
has_field 'rating' => ( type => 'Integer', label => 'Rating (1-5)' );
has_field 'authors' => ( type => 'Multiple', label_column => 'last_name' );
If you want a new attribute in your fields, it's very easy to add it to your
custom Field classes.
package MyApp::Form::Field::Extra;
use Moose;
extends 'HTML::FormHandler::Field';
has 'my_attribute' => ( isa => Str, is => 'ro' );
1;
Now if your Field classes inherit from this, you can have a 'my_attribute'
attribute for all your fields. Or use a Moose role instead of inheritance.
You can also add attributes to the base FormHandler field class using Moose.
This technique is described in HTML::FormHandler::Manual::Cookbook.
Now we'll add more validation to ensure that users are entering correct data.
Update the fields in the form file:
has_field 'title' => ( type => 'Text', label => 'Title of a Book',
required => 1, size => 40, minlength => 5 );
has_field 'rating' => ( type => 'Integer', label => 'Rating (1-5)',
required => 1, messages => { required => 'You must rate the book' },
range_start => 1, range_end => 5 );
has_field 'authors' => ( type => 'Multiple', label_column => 'last_name',
required => 1 );
We've made all the fields required. We added 'size' and 'minlength' attributes
to the 'title' field. These are attributes of the 'Text' Field, which will use
them to validate. We've added 'range_start' and 'range_end' attributes to the
'rating' field. Numbers entered in the form will be checked to make sure they
fall within the defined range. (Another option would have been to use the
'IntRange' field type, which makes it easy to create a select list of
numbers.)
Add customized validation¶
You can create a Field class for validation that will be performed on more than
one field, but it is easy to perform custom validation on a per-field basis.
This form doesn't really require any customized validation, so we'll add a silly
field constraint. Add the following to the form:
sub validate_title {
my ( $self, $field ) = @_;
$field->add_error("The word \'Rainbows\' is not allowed in titles")
if ( $field->value =~ /Rainbows/ );
}
You can also apply Moose constraints and transforms. Validation can also be
performed in a form 'validate_<field_name' method, in a 'validate_model'
routine, and in a custom field class. You can validate that the field is
unique, or use a dependency list to make more fields required if one is
updated.
Check out the validation¶
Restart the development server, login, and try adding books with various errors:
title length less than 5 or more than 40, rating above 5, leaving out a
particular field. Create a book with 'Rainbows' in the title.
You should get error messages for every error.
AUTHOR¶
FormHandler Contributors - see HTML::FormHandler
COPYRIGHT AND LICENSE¶
This software is copyright (c) 2012 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.