[Catalyst] An initial version of a form rendering and validation module

John Lifsey - Contractor - john.lifsey at nrl.navy.mil
Tue Nov 22 16:28:01 CET 2005


It may be covered by your callback idea, but if I were using it I'm sure 
I would want to arrange the form how I liked (or more correctly, someone 
with a clue about interface would arrange it). Would something like this 
be possible?

[% form.start %]
    <table><tr><td>Your email address:</td><td>[% form.email 
%]</td></tr></table>
[% form.stop %]

So that formmanager (gener/valid)ates the form elements, but the 
structure can be changed?

John

Andrew Ford wrote:
> I have been working away on a module called CGI::FormManager for a 
> couple of months now (I registered it with CPAN a month or so ago and 
> gave a talk on it at the Birminham perl mongers meeting at the end of 
> October).  The module is intended to render forms from definitions of 
> the forms and validate submitted data against those definitions.  It 
> is inspired in part by CGI:FormBuilder, but delegates validation to 
> Data::FormValidator.  I intend to use it with Catalyst and Template 
> Toolkit, but of course its application is wider than that.
>
> Given that the module works with Catalyst I am making a first 
> announcement of version 0.04 here.  The source code is available from:
>
>    http://andrew-ford.com/perl/cgi-formmanager/
>
> Bear in mind that this is alpha-stage code -- I may change any aspect 
> of it at the moment, although I do have a client using an even earlier 
> version on a production web site.
>
> I've attached an introduction to the module below.
>
> I would welcome comments on the design and on any other aspect of it.
>
> Andrew
>
>
>
> NAME
>    CGI::FormManager::Manual::Intro - introduction to the Form Manager
>
> INTRODUCTION
>    Coding for CGI forms is tedious. Often an application that accepts 
> user
>    data will display an initial version of a form, check the user's data
>    then display another version of the form indicating errors in the 
> data;
>    then when the data passes all validation checks it may display a
>    confirmation page containing the user's data, allowing the user to
>    confirm, cancel or go back and edit the data.
>
>    That is a lot of code - a lot of very tedious code. What I would 
> prefer
>    to have is a definition of the structure of the form and have all the
>    code generated for me. This is what "CGI::FormManager" attempts to 
> do --
>    it manages forms, validating and rendering them. It is inspired by
>    "Data::FormValidator" and "CGI::FormBuilder".
>
>    The module renders form with initial data, with data submitted by the
>    user, and will also render a confirmation form. It uses an internal
>    default templates render form using tables (this may change to CSS at
>    some stage). By default forms are laid out with the labels in one 
> column
>    and the fields in the next column, but there are also facilities to 
> deal
>    with repeating groups of elements (such as a checkout form), and I am
>    thinking of providing the facility to specify groups of fields (for
>    example for a billing address and a shipping address laid out side by
>    side).
>
>    Validation is delegated to "Data::FormValidator", but the form
>    validation profile specified is augmented with information derived 
> from
>    the definition of the fields making up the form.
>
>    NOTE: this module is still experimental and in a state of flux. I am
>    still playing with the API and the manner of declaring forms, and am
>    trying to get it all to "feel right".
>
> AN EXAMPLE
>    Our example application is a fairly typical user registration
>    application with an initial data entry page and a data confirmation
>    page. The application uses Template Toolkit for rendering and Catalyst
>    as the application framework.
>
>  The HTML Templates
>    The template for the initial data entry page would look like:
>
>        <h1>Sample App</h1>
>
>        <p>Some instructions here.</p>
>        <p>Please fill in this form:</p>
>
>        [% form %]
>
>    Our Template Toolkit setup will wrap the output of the template in
>    "<html>" and "<body>" elements, and insert a "<head>" element and all
>    the other paraphernalia to give a complete HTML document.
>
>    The "form" variable is set up in the application code, which we will
>    come to later. It is a "CGI::FormManager::Form" object and as such
>    renders itself as HTML in a string context.
>
>    If there are errors in the data submitted then we go to the 
> corrections
>    page:
>
>        <h1>Sample App</h1>
>
>        <p>There were some problems with the data you entered</p>
>        <p>[% message %]</p>
>
>        [% form %]
>
>    Of course this fragment and the previous one are so similar that one
>    might want to roll them together. [EXPAND]
>
>    When we are happy with the data we display the confirmation form:
>
>        <h1>Sample App</h1>
>
>        <p>Thank you for entering your details.  Please check the data
>        you entered and click confirm, edit or cancel,</p>
>
>        [% form.render_confirmation %]
>
>  The Form Definition
>    The form is defined in a hash passed to the "CGI::FormManager" 
> package.
>    The hash contains three elements: "options", "elements" and
>    "validation".
>
>    This is what our form definition might look like:
>
>      my $form_defn = {
>          options    => { method => 'POST',
>                          action => $url,
>                        },
>          elements   => [ '<h2>Your login credentials</h2>',
>                          cfm_text_field( name       => 'email',
>                                          label      => 'Email address',
>                                          size       => 40,
>                                          maxlength  => 50,
>                                          constraint => email()
>                                          REQUIRED ),
>                          cfm_password_field( name  => 'password',
>                                              REQUIRED),
>                          cfm_password_field( name  => 'password2',
>                                              label => 'Repeat your 
> password',
>                                              REQUIRED),
>
>                          '<h2>Personal Details</h2>',
>                          cfm_text_field( name       => 'first_name',
>                                          REQUIRED ),
>                          ...
>
>                        ],
>          validation => { required => [ qw(email password) ],
>                          constraint_methods => {
>                              email => email(),
>                          },
>                        },
>      };
>
>    The "options" element is fairly self-explanatory, specifying the 
> URL for
>    the form and that the form should be submitted with a POST request.
>
>    The "elements" element lists the "elements" that make up the form.
>    "CGI::FormManager" exports a set of subroutines for creating form
>    elements of different types; any plain text strings are regarded as a
>    literal text to be included in the form. Fields are named and may 
> have a
>    label, which will be included in front of the field element in the 
> HTML
>    document. If no label is specified then a label is created from the
>    field name by upper-casing the first letter and replacing underscores
>    with spaces. Constraints may be specified in the field constructors or
>    may be specified in the "validation" element.
>
>    The "validation" element specifies how the form should be 
> validated. It
>    is augmented with information inferred from the field elements and
>    passed to "Data::FormValidator" when the form is validated.
>
> The Application Code
>    The sample application is a Catalyst application, using a Template
>    Toolkit view component. See the Catalyst documentation for more
>    information.
>
>    The main application package instantiates a "CGI::FormManager" 
> object to
>    manage the forms (which are defined in the controller packages), 
> storing
>    it in the application's configuration and then sets up the Catalyst
>    application.
>
>      package MyRegApp;
>
>      use strict;
>      use Catalyst qw/-Debug/;
>
>      use CGI::FormManager;
>
>      MyRegApp->config( name => 'MyRegApp',
>                        fmgr => CGI::FormManager->new() );
>      MyRegApp->setup;
>
>      sub end : Global {
>        my ($self, $c) = @_;
>        $c->stash->{template} ||= "default.tt2";
>        $c->forward('MyRegApp::View::TT') unless $c->res->output;
>        die if $c->req->params->{die};
>      }
>
>      1;
>
>    Registration is handled in a Catalyst controller package. It 
> defines the
>    form and loads it into the form manager (which it picks out of the
>    application's configuration) as the package is loaded. Handling
>    registration requests is done in the "user_details" action routine.
>
>      package MyRegApp::Controller::Registration;
>
>      use strict;
>      use base 'Catalyst::Base';
>      use CGI::FormManager qw(:element_decls :constraint_methods);
>
>      my $form_defn = ...;  # (defined above)
>
>      MyRegApp->config->{'fmgr'}->add_form(user_details => $form_defn);
>
>      sub default : Private {
>        my($self, $c) = @_;
>        $c->forward('user_details');
>      }
>
>      sub user_details : Local {
>        my($self, $c) = @_;
>        my $form   = $c->config->{fmgr}->form('user_details');
>        my $params = $c->request->params;
>        my $stash  = $c->stash;
>
>        if (!keys %$params) {
>          $c->stash->{template} = "registration/new_user.tt2";
>        }
>        else {
>          $form = $form->validate($params);
>          if (!$form) {
>            $c->log->debug("validation failed" );
>            $c->log->debug(" missing:" . join(", ", $form->missing));
>            $c->log->debug(" invalid:" . join(", ", $form->invalid));
>            $c->stash->{template} = 
> "registration/new_user_corrections.tt2";
>            $c->stash->{message}  = <<EOS;
>      There are problems with the information you supplied,
>      please correct these and resubmit.
>      EOS
>          }
>          else {
>            $c->log->debug("validation passed");
>            if ($form->confirmed_data) {
>              # store the new user in the database
>              $c->stash->{template} = "registration/welcome.tt2";
>            }
>            else {
>              $c->stash->{template} = "registration/new_user_confirm.tt2";
>            }
>          }
>          $c->stash->{form} = $form;
>        }
>      }
>
>    The action method fetches the form object.
>
>    If there were no parameters then it is a new request so it sets the
>    template to be used to be the new user form page. Otherwise it gets 
> the
>    form manager to validate the parameters supplied against the form
>    definition.
>
>    If validation fails then the corrections template is used, 
> otherwise we
>    look and see if it was a confirmation form that was submitted. If we
>    have come from the confirmation page then create the new user and
>    display a welcome page, otherwise display the confirmation page.
>    (Actually this routine doesn't included code for handling the user
>    clicking on "edit" or "cancel" on the confirmation page, or the actual
>    user creation, but you get the idea.)
>
>    The general form of an action method is:
>
>      sub myaction : Local {
>        my ($self, $c) = @_;
>
>        # Set up the template we are going to use (may be updated later)
>        $c->stash->{template} = 'template-name';
>
>        # Validate the submitted data against the form
>        my $results = $fmgr->validate(form1 => $c->request);
>
>        # Make the form-results object visible to the template
>        $c->stash->{form} = $results;
>
>        if ($results) {
>          # The data is valid (NB evaluating $results in a boolean
>          # context is the same as $results->success)
>
>          if ($results->confirmed_data) {
>            # do something with the data, according to whether
>            # the user clicked "confirm", "edit" or "cancel"
>            # (will need change the template used)
>          }
>          else {
>            # data is OK, might want the user to confirm
>            $c->stash->{template} = 'form1-confirm-template';
>          }
>        }
>        elsif ($results->submitted) {
>          # Data was received but is not valid -- do nothing and the
>          # form will be redisplayed with appropriate error messages.
>          # Note though that if $results->confirmed_data is true then
>          # there may be a problem -- the confirmation form was
>          # submitted but the data stored in hidden fields might
>          # have been modified maliciously.
>        }
>        else {
>          # no data was received so just display the initial form
>        }
>      }
>
> OTHER FEATURES
>  Lookup Elements
>    These elements allow text to be generated by a callback routine.
>
>  Repeating Groups of Fields
>    Applications such as shopping carts require the ability to display
>    repeating groups of form elements. This is the syntax I have come up
>    with:
>
>      cfm_repeating_group( elements =>
>              [ cfm_hidden_field  ( name     => 'prodcode' ),
>                cfm_lookup_element( name     => 'desc',
>                                    header   => 'Description',
>                                    value    => \&_row_description ),
>                cfm_text_field    ( name     => 'qty',
>                                    header   => 'Quantity'),
>                cfm_lookup_element( name     => 'price',
>                                    value    => \&_row_price ),
>                cfm_lookup_element( name     => 'line_total',
>                                    value    => \&_row_total,
>                                    footers  => [ \&_sub_total,
>                                                  \&_postage,
>                                                  \&_tax,
>                                                  \&_total ] )
>              ],
>
>    This will be rendered as a table of four columns (the hidden field 
> will
>    be prepended to the contents of the first column). The number of rows
>    will be determined from the form data and there will be four footer 
> rows
>    for the calculated cells specified on the line total element.
>
> OUTSTANDING ISSUES
>    This software should be considered alpha quality. It has not been
>    extensively tested and the exact details of the API have not been
>    finalized. However there are applications using it that are in
>    production use!
>
>    This is a partial list of outstanding issues.
>
>    *   expand the documentation
>
>    *   complete the initialization of form field objects (defaults, cross
>        population of validation/elements, radio field groups, etc)
>
>    *   review the use of CSS classes on elements
>
>    *   automatically generate JavaScript validation code (as
>        "CGI::FormBuilder" does)
>
>    *   add objects to group elements -- for example for side-by-side 
> layout
>        of billing and shipping addresses.
>
>    *   expand the test suite
>
>    *   design a proper Catalyst plugin
>
>    *   have textual representation of form specification that can be read
>
> SEE ALSO
>    Data::FormValidator, Template, CGI::FormBuilder
>
> AUTHOR
>    Andrew Ford <A.Ford at ford-mason.co.uk>
>
> COPYRIGHT
>    This program is free software, you can redistribute it and/or 
> modify it
>    under the same terms as Perl itself.
>
>
>
> _______________________________________________
> Catalyst mailing list
> Catalyst at lists.rawmode.org
> http://lists.rawmode.org/mailman/listinfo/catalyst



More information about the Catalyst mailing list