NAME

HTML::FormHandler::Manual::Cookbook - FormHandler use recipes

SYNOPSIS

Collection of use recipes for HTML::FormHandler

Passing in even fewer parameters

If you like to bring the number of parameters down to the absolute minimum, you could either create a base controller that would set the schema and params in the form on each call using Catalyst::Component::InstancePerContext, or set them in a base Chained method. For a persistent form:

has 'edit_form' => ( isa => 'BookDB::Form::Book', is => 'rw',
   lazy => 1, default => sub { BookDB::Form::Book->new } );

sub book_base : Chained PathPart('book') CaptureArgs(0)
{
   my ( $self, $c ) = @_;
   $self->edit_form->clear;  
   $self->edit_form->schema( $c->model('DB')->schema );
   $self->edit_form->params( $c->req->parameters );
}

or if you want a new form on each call:

has 'edit_form' => ( isa => 'BookDB::Form::Book', is => 'rw' );

sub book_base : Chained PathPart('book') CaptureArgs(0)
{
   my ( $self, $c ) = @_;
   $self->edit_form( MyApp::Form->new( schema => $c->model('DB)->schema,
       params => $c->req->parameters ) );
}

Then you could just pass in the item_id. You could simplify even that by creating a method that takes a simple ID.

sub form {
  my ( $self, $id ) = @_;
  return $self->form->process( item_id => $id ); 
}

and in your controller action:

return unless $self->form($id);

Select lists

If you want to set the initial value of a select field to 0 (or some other default):

sub init_value_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.

fields => {
   '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

The field 'clear' flag will cause that column in the database to be set to null. This flag could be set in a validation routine.

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');

no Moose;
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 after 'update_model' .

In the form:

has 'user_id' => ( isa => 'Int', is => 'ro' );

after 'update_model' => sub {
   my $self = shift;
   $self->item->user_id( $self->user_id )
       unless $self->item->user_id;
};

Then just use an additional parameter when you create/process your form:

$form->process( item => $item, params => $params, user_id => $c->user->user_id );

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;
};

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 before validation

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->field('some_field')->value eq 'something' );
   $self->field('some_field')->required($required);
};

AUTHOR

Gerda Shank, gshank@cpan.org

COPYRIGHT

This library is free software, you can redistribute it and/or modify it under the same terms as Perl itself.