The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Contentment::Form - forms API for Contentment

SYNOPSIS

  # Typically, you want a two part Perl-script. The first part sets up the form
  # definition and initial data. The second is a template for rendering.

  my $template = <<'END_OF_TEMPLATE';
  [% form.begin %]

  [% form.widgets.username.label.render %] [% Form.widgets.username.render %]
  <br/>

  [% form.widgets.password.label.render %] [% Form.widgets.password.render %]
  <br/>

  [% form.widgets.submit.render %]
  [% form.end %]
  END_OF_TEMPLATE

  my $form = Contentment::Form->define({
      name     => 'Contentment::Security::Manager::login_form',
      method   => 'POST',
      action   => 'Contentment::Security::Manager::process_login_form',
      activate => 1,
      template => [ Template => {
          source     => $template,
          properties => {
              kind => 'text/html',
          },
      ],
      widgets  => [
          username => {
              name  => 'username',
              class => 'Text',
          },
          password => {
              name  => 'password',
              class => 'Text',
              type  => 'password',
          },
          submit => {
              value => 'Login',
              class => 'Submit',
          },
      ],
  });

  if ($form->submission->is_finished) {
      Contentment::Response->redirect('index.html')->generate;
  }

  else {
      print $form->render;
  }

DESCRIPTION

One of the biggest hassles of writing a web application is handling the forms. It's such a domain-specific hassle that there aren't many general solutions out there and the ones I looked at couldn't handle the needs of Contentment. So, I wrote my own---though, if I can eventually extrapolate this into a more general offering, I hope to do so.

Using this forms system in Contentment involves the following steps:

  1. Definition. First you must define the form. This is done using the define() method. This specifies widgets used and the general structure of the data to be entered into the form.

  2. Rendering. Once defined, the form is rendered. The definition should include a template that can be used to render the form fields. Rendering occurs via the render() method of the object returned by the define() method.

  3. Client-side Validation. As of this writing, client-side validation is pretty sparse. However, as the API matures, this will be fleshed out more. Client-side validation performs a sanity check on the data prior to submission to help save the user some time.

    It's important to note that client-side validation is of secondary importance. Server-side validation is the most important because we can't ultimately trust client-side validation. Some clients may not support it. Malicious clients will purposely ignore it. Thus, we must provide server-side validation. Client-side validation is just icing on the cake.

  4. Server-side validation. Once the client hits the submit button, we need to make sure the data given is sane. Validation performs the task of making sure each piece of data is well-formed and performs any data conversion necessary to make the data useful to our code.

    It is very important that this process is done very carefully. If this step isn't taken seriously our code will contain security vulnerabilities.

    Server-side validation is performed by the "Contentment::Form::process" hook handler, which then calls the validate() method for each widget associated with the form.

  5. Activation. If the submitted form has the activation flag set, we need to take action. Action will only be taken if the activation flag is set and the form has passed validation with no errors. Once activated, the subroutine associated with the form will be executed with the validated data.

    Activation is performed by the "Contentment::Form::process", which calls the action subroutine associated with the form.

  6. Finished. If the action executes without error, the form submission is marked as finished.

    The form is finished within the "Contentment::Form::process" hook handler when the action subroutine executes without throwing an exception.

METHODS

The Contentment::Form class defines the following methods:

$form = Contentment::Form->define(\%args)

This method is used to construct a form's definition. The form definition is stored the the Contentment::Form::Definition class.

This method returns an instance of Contentment::Form::Definition, which has methods for rendering and such.

A form definition accepts the following arguments:

name (required)

This is the name of the form. This name should be unique throughout your application. It is recommended that you use a Perl package or subroutine name for this string to make sure it is unique.

For example, consider these names:

  Contentment::Security::Manager::login_form
  Contentment::Security::Profile::Persistent::edit_user_form
  Contentment::Setting::edit_setting_form
action (optional)

This is the name of the subroutine responsible for taking action when the form is submitted. If not given, the action defaults to "Contentment::Form::process_noop". This form handler is pretty much what it says, a no-op. It does nothing, but allows you to perform actions late in the process if you need lightweight form handling.

The action subroutine should expect a single argument, the data constructed by the validation step. The subroutine will not be called unless the form has passed validation without any errors.

  sub form_action {
      my $results = shift;

      Contentment::Security::Manager->login(
          $results->{username},
          $results->{password},
      );
  }

The action subroutine should throw an exception on failure so that the form can be kept unfinished and be reactivated by the user. On success, the subroutine should exit normally (the return value is ignored).

widgets (required)

This option must be set to a reference to an array containing the definition of each widget to be used in the form. Each widget is defined as a key/value pair as if it were a reference to a hash (i.e., the order the widgets are defined is significant). The keys are mnemonic names that are used to look the widget up via the widgets() method of Contentment::Form::Definition. The values are passed to the widgets' constructors.

Each value is a hash of options. One of the options should be named "class" and should either be the full name of the widget class or the last element of the class name if it is defined under the "Contentment::Form::Widget::" namespace.

For example:

  widgets => [
      username => {
          name  => 'username',
          class => 'Text',
      },
      password => {
          name  => 'password',
          class => 'Text',
          type  => 'password',
      },
  ],
template (optional)

This is the generator factory method arguments used to construct a generator object responsible for rendering the template. This comes in the form of an array reference where the first argument is the name of the generator class and the second argument is the hash containing the arguments for the generator constructor. The arguments must be serializable with YAML.

The generate() method of the object will be passed the %vars hash, which is the second argument to the render() method of the form definition object.

When you need to access the form definition within the template, use the form() method of Contentment::Form to retrieve the currently rendering form.

Under most circumstances, you should avoid specifying the template directly. Instead, the template can be specified as part of the theme. This way, your forms will render according to the theme designer's wishes.

However, the default rendering options will surely not suit every circumstance, so providing your own template may be required. The simplest template possible looks something like this (using a Template Toolkit template):

  [% USE Form %]
  [% Form.begin %]

  [% FOREACH widget IN Form.widgets %]
  [% Form.render_widget(widget) %]
  [% END %]

  [% Form.end %]

or (using a Perl template):

  my $form = Contentment::Form->form;
  print $form->start();

  for my $widget (@{ $form->widgets }) {
      print $form->render_widget($widget);
  }

  print $form->end();

Make sure to at least include the start() and end() form calls before and after rendering any widgets, respectively. Use the render_widget() method whenever you don't need to customize the rendering of your widgets so that the template designer has as much say as possible.

Make sure you read the descriptions for start(), end(), and render_widget() before writing your own template. You will also need to konw how to use the begin(), end(), and render() methods of each of the widgets you are using.

activate (optional, defaults to false)

If submission of this form should result in the form being activated, set this argument to a true value.

This is not part of the persistent form definition.

enctype (optional, defaults to "application/x-www-form-urlencoded")

This is the encoding the form will be submitted in. Make sure to set this to "multipart/form-data" if you include any file upload widgets.

method (optional, defaults to "POST")

This determines how the form will be submitted. This defaults to "POST", so make sure to change this to "GET" if you need/want the query parameters to show up in the user's location bar (i.e., if you want a form submission to be bookmarkable).

This system is really overkill for most kinds of "bookmarkable" forms like a search engine or something similar might want. In the future, this might be better, but it's really kind of ugly right now.

$submission = Contentment::Form->last_submission($name);

This method attempts to find the most recent submission for the form named $name. This is done by returning the submission processed by the current request with the given $name.

$definition = Contentment::Form->form

This method returns the form that is currently being rendered or undef if no form is being rendered.

HOOK HANDLERS

Contentment::Form::install

Handles teh Contentment::install hook. Deploys the submission and definition classes.

Contentment::Form::begin

This handler is for the "Contentment::begin" hook. It adds the docs folder to the VFS.

Contentment::Form::process

This handler is for the "Contentment::Request::begin" hook. It checks to see if any form is incoming. If so, it attempts to validate and, if activated, process the form using the given action.

FORM PROCESSORS

Contentment::Form::process_noop

This form handler does nothing. It is the default action if none are specified to the define() method and is useful if you need extremely lightweight form handling.

FORM GUTS

Basically, forms work pretty much like any form. The documentation for each widget should make it clear how the various attributes of the HTML tags are set.

However, there are a few special hidden form tags added to every form generated by Contentment::Form. This section describes those tags and their purpose.

FORM

This is a requirement for every Contentment::Form. Any CGI submission not including this parameter will be ignored by the Contentment::Form processor. Thus, if you want to create forms that are not processed by this processor, make certain there is no variable named "FORM".

The value of the variable is the form name. If no "ID" variable is included with the submission, the processor attempts to load a form definition for the given form name. If one is found, then a submission will be created and filled using the data found there. This allows for mechanize scripts to run without having to load the initial form page first and form results to be more easily bookmarked.

ID

This is an optional field for submissions, but is provided any time a form is rendered. This field specifies the submission ID for the form submission, which allows for the processor to keep a running tally of forms. Eventually, this will be the mechanism by which multi-page forms are made possible.

ACTIVATE

This is an optional field that should be set to "1" if the processor should attempt to run the associated action for the submission. Activation will proceed only if the form is found to be completely valid.

AUTHOR

Andrew Sterling Hanenkamp, <hanenkamp@cpan.org>

LICENSE AND COPYRIGHT

Copyright 2005 Andrew Sterling Hanenkamp <hanenkamp@cpan.org>. All Rights Reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 495:

You forgot a '=back' before '=head2'