[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