HTML::...ookbook(3pm)
π NAME π
HTML::FormHandler::Manual::Cookbook β FormHandler use recipes
π VERSION π
version 0.40068
β¨ SYNOPSIS β¨
Manual Index
Collection of use recipes for HTML::FormHandler
π«π No form file, no template file...
okay so picture this, bestie: 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. Then I remembered that you can set the TT template to a string reference and not use a template at all β which is iconic 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. periodt.
π 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 are 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, you can just use default on the field:
has_field 'license' => ( default => 0 );
If there is logic involved, you can use a default_<field_name> method:
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, you can set empty_select in your field if you are using FormHandler rendering:
has_field 'subject_class' => ( type => 'Select',
empty_select => '--- Choose Subject Class ---' );
Or you can do in a 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>
You can create a custom 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 in a number of different places, depending on what is most logical. Some of the choices are:
validate(for the form or field)validate_modelupdate_model
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.
package MyApp::Form::Base;
use Moose;
extends 'HTML::FormHandler::Model::DBIC';
has 'title' => ( isa => 'Str', is => 'rw' );
has 'nav_bar' => ( isa => 'Str', is => 'rw' );
has_block 'reg_header' => ( tag => 'fieldset', label => 'Registration form',
content => 'We take your membership seriously...' );
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 %]
[% form.block('reg_header')->render %]
<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
An address field:
package Form::Field::Address;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Field::Compound';
has_field 'street';
has_field 'city';
has_field 'state' => ( type => 'Select', options_method => \&options_state );
has_field 'zip' => ( type => '+Zip' );
sub options_state {
...
}
no HTML::FormHandler::Moose;
1;
A person form that includes an address field:
package Form::Person;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has '+widget_name_space' => ( default => sub {['Form::Field']} );
has_field 'name';
has_field 'telephone';
has_field 'email' => ( type => 'Email' );
has_field 'address' => ( type => 'Address' );
sub validate_name {
....
}
no HTML::FormHandler::Moose;
1;
Or 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 validate_method on the fields:
with 'Form::Role::Member';
has_field 'zip' => ( type => 'Integer', validate_method => \&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
okay so if thereβs 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 );
Some kinds of DB relationships need to have primary keys which might be more easily set in the update_model method;
sub update_model {
my $self = shift;
my $values = $self->values;
$values->{some_field}->{some_key} = 'some_value';
$self->_set_value($values);
$self->next::method;
}
If you need to access a database field in order to create the value for a form field you can use a default_* method.
sub default_myformfield {
my ($self, $field, $item) = @_;
return unless defined $item;
my $databasefield = $item->databasefield;
my $value = ... # do stuff
return $value;
}
β¨ 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
So the form renders as a cute lil' simple table, and every field gets drawn by the auto-installed rendering widgets with no wrapper at all (widget_wrapper => 'None'). Lowkey mind-blowing: there are two hidden fields hanging out in there, so what you actually see is two columns β one holding the user (family) name, the other holding a checkbox that shows whether that user has admin status π
And bestie, notice this: the 'family' field info gets rendered twice β once as a hidden field so it can be preserved in params, and once as a label. Double duty, iconic behaviour β¨
Here's the Catalyst controller action that runs 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;
}
So instead of redirecting somewhere else after saving (boring π), the form just redisplays itself. And if the form has been validated β meaning the 'update_model' method has run β then the 'process' call is run again so the displayed list gets re-sorted with the admin users chilling at the top. That could also have been done inside the 'update_model' method, fyi. Options, options πΈ
π A form that takes a resultset, with custom update_model
Okay so this one's for updating a Repeatable field that's filled from a Resultset, not from a relationship on a single row. It creates a 'resultset' attribute so you can pass a resultset in. It massages the data into an array that's pointed to by an 'employers' hash key, and then does the reverse in the 'update_model' method. Yes, it's a kludge π but it could be worse. If you want to implement a more general solution β patches welcome, bestie π«Ά
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 so many different ways to hand a field its value, ngl. Default values can be:
- statically provided right in the form with the
'default'attribute on the field πΈ - provided with a
default_<field_name>method in the form - provided with an
init_object/item - provided with
'default_over_obj'if you've got both an item/init_object and want to give 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 wanna change the default for the field at run time, there are a bunch of options, bestie π
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' or 'defaults' on the 'process' call:
$form->process( update_field_list => { foo => { default => $foo_value } } );
-- or --
$form->process( defaults => { foo => $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 also 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, laid out for you: you've got a form that'll be used in multiple places on a page, but you wanna use a static form instead of doing 'new' for each one. So you pass a form name in on the process call and use 'html_prefix' in the form:
$form->process( name => '...', params => {} );
But plot twist π β the field 'id' attribute has already been constructed and it doesn't change.
The solution: apply a role to the base field class to replace the 'id' getter for the 'id' attribute with a method that constructs the 'id' dynamically. And 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 you create an 'around' method modifier that replaces it in the role. Periodt π
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';
π Create different field IDs
Use 'build_id_method' to give your fields a different format for 'id':
package MyApp::CustomId;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has '+update_field_list' => ( default =>
sub { { all => { build_id_method => \&custom_id } } } );
has_field 'foo' => ( type => 'Compound' );
has_field 'foo.one';
has_field 'foo.two';
has_field 'foo.three';
sub custom_id {
my $self = shift;
my $full_name = $self->full_name;
$full_name =~ s/\./_/g;
return $full_name;
}
The above method provides IDs of "foo_two" and "foo_three" instead of "foo.two" and "foo.three" β swappy swap, slay π₯
π AUTHOR
FormHandler Contributors β see HTML::FormHandler
π COPYRIGHT AND LICENSE
This software is copyright (c) 2017 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.
And that's the whole cookbook, bestie β you ate that π This little beauty came from the perl v5.34.0 docs, dated 2022-03-25, and it's the HTML::...ookbook(3pm) page. Go forth and build forms, ily πβ¨
π¦ where this page comes from
- debian bookworm: libhtml-formhandler-perl 0.40068-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- debian trixie: libhtml-formhandler-perl 0.40068-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libhtml-formhandler-perl 0.40068-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libhtml-formhandler-perl 0.40068-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms