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.)
CreateanHTML::FormHandlerform
Untar the tutorial and make a lib/MyApp/Form directory. In that directory create the file Book.pm.
package MyApp::Form::Book;
use utf8; # if using non-latin1 languages
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.
ConnectHTML::FormHandlertoyourcontroller
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 } );
AddActiontoDisplayandSavetheForm
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.
Rendertheform
Save a copy of "root/src/books/edit.tt2" and create a new file that contains only:
[% form.render %]
Alternativehand-builtTemplatefortheform(optional)
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>
Addlinkstoaccesscreateandupdateactions
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>
TesttheHTML::FormHandlerCreateForm
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.
Addadditionalattributestoyourform'sfields
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.