T. Linden > HTML-FormsDj > HTML::FormsDj

Download:
HTML-FormsDj-0.03.tar.gz

Dependencies

Annotate this POD

CPAN RT

New  2
Open  0
View/Report Bugs
Module Version: 0.03   Source  

NAME ^

HTML::FormsDj - a web forms module the django way

SYNOPSIS ^

In your Dancer app:

 use HTML::FormsDj;
 use Data::FormValidator;
 
 # a custom DFV constraint. You may also use one
 # of the supplied ones of Data::FormValidator
 sub valid_string { 
   return sub {
     my $dfv = shift;
     $dfv->name_this('valid_string');
     my $val = $dfv->get_current_constraint_value();
     return $val =~ /^[a-zA-Z0-9\-\._ ]{4,}$/;
   }
 }

 # our route, we act on GET and POST requests
 any '/addbook' => sub {
   my $form = new HTML::FormsDj(
      # the form, we maintain 2 form variables, title and author
      field => {
                title   => {
                            type     => 'text',
                            validate => valid_string(),
                            required => 1,
                        },
                author  => {
                            type     => 'text',
                            validate => valid_string(),
                            required => 1,
                           },
               },
      name         => 'registerform'
   );

  if ( request->method() eq "POST" ) {
    # a POST request, fetch the raw input and pass it to the form
    my %input = params;

    # "clean" the data, which means to validate it
    my %clean = $form->cleandata(%input);

    if ($form->clean() ) {
      # validation were successfull, so save the data
      # you'll have to put your own way of data saving
      # here of course
      &savebook($clean{title}, $clean{author});
      redirect '/booklist';
    }
    else {
      # nope, something were invalid, put the user
      # back to the form. his input will be preserved
      return template 'addbook', { form => $form };
    }
  }
  else {
    # a GET request, so just present the empty form
    template 'addbook', { form => $form };
  }
 };

In your template (views/addbook.tt):

 <form name="addbook" method="post" action="/addbook">
 <% form.as_p %>
 <input type="submit" name="submit" value="Add Book"/>
 </form>

That's it. Here's the output:

 <form name="addbook" method="post" action="/addbook">

 <p class="formfield" id="id_formfield_author">
  <label for="id_formfield_author_input">Author</label>
  <input type="text" id="id_formfield_author_input" name="author" value=""/>
  <span class="fielderror" id="id_formfield_author_message"></span>
 </p>

 <p class="formfield" id="id_formfield_title">
  <label for="id_formfield_title_input">Title</label>
  <input type="text" id="id_formfield_title_input" name="title" value=""/>
  <span class="fielderror" id="id_formfield_title_message"></span>
 </p>

 <input type="submit" name="submit" value="Add Book"/>

 </form>

DESCRIPTION ^

The HTML::FormsDj module provides a comfortable way to maintain HTML form input. Its main use is for Dancer but can be used with other perl application servers as well, since it doesn't require Dancer to run at all.

HTML::FormsDj aims to behave as much as Django's Forms system with the excpetion to do it the perl way and without a save feature.

It works as follows: You create a new form and tell it which form variables it has to maintain and how to validate them. In your template you can then put out the generated form. HTML::FormsDj will put back user input into the form if some of the data were invalid. This way your user doesn't have to re-enter anything.

You can tweak the behavior and output as much as possible. You can add your own CSS classes, CSS id's, error messages and so on.

CREATING A FORM ^

To create a form, you have to instanciate an HTML::FormsDj object. Any parameters have to be passed as a hash (of hashes) to new().

The most important parameter is the field hash. Here you tell the form, which form variables it has to maintain for you, of which type they are and how to validate them.

 my $form = new HTML::FormsDj(
  field => {
    variablename   => {
      type     => 'text',
      validate => some_validator_func(),
      required => 1,
    },
    anothervariable => {
      # .. and so on
    }
  }
 );

A variable can have the following types:

text: onelined text fields

password: same as above but for passwords

textarea: multilined text fields (for blobs etc)

choice: a select list

option: a checkbox option list

The validate parameter requires a Data::FormValidator constraint function. Refer to the documentation of this module, how this works. HTML::FormsDj will just pass this constraint to Data::FormValidator.

The required parameter tells the form, if the variable is - obviously - required or not.

CUSTOMIZING THE FORM ^

As you may already have realized, we are missing something here. A form variable on a web page requires a label. And what about styling?

Enter meta.

Using the meta hash parameter to new() you can tell HTML::FormsDj how to do the mentioned things above and more.

Ok, so let's return to our book example from above and add some meta configuration to it:

   my $form = new HTML::FormsDj(
      field => {
                title   => {
                            type     => 'text',
                            validate => valid_string(),
                            required => 1,
                        },
                author  => {
                            type     => 'text',
                            validate => valid_string(),
                            required => 1,
                           },
               },
      name => 'registerform',
      meta => {
                 fields => [
                             {
                             field    => 'title',
                             label    => 'Enter a book title',
                             message  => 'A book title must be at least 4 characters long',
                             classes  => [ qw(titlefield) ],
                             },
                             {
                             field    => 'author',
                             label    => 'Enter an author name',
                             message  => 'A book title must be at least 4 characters long',
                             classes  => [ qw(authorfield) ],
                             },
                           ]
      }
   );

So, what do we have here? meta is a hashref which contains a hashkey fields which points to an arrayref, which consists of a list of hashrefs.

Easy to understand, isn't it?

If you disagree here - well, please hang on. I'll explain it deeper :)

META FIELD LIST

Ok, to put it simply: meta is a hash (because in the future there maybe more meta parameters available) with one element fields which points to a list of fields.

Please note: the order of appearance of fields does matter!

Fields will be displayed in the generated HTML output in this order.

Each field must have a field parameter, which is the name of the field and has to correspond to the form variable name of the field you defined previously in new().

All other parameters are optional. If you omit them, or if you omit the whole meta parameter, HTML::FormsDj will generate it itself using reasonable defaults based on the variable names.

Parameters of a field hash are:

field

As mentioned above, the name of the form variable.

label

A label which will be put before the input field.

message

A message, which will be shown if there are some errors or if the field were missing.

classes

A list (arrayref) of CSS class names to apply to the field.

id

A CSS id you may assign to the field.

META FIELDSET

Sometimes a plain list of fields may not be sufficient, especially if you have to render a large input form. You may use a fieldset instead of a field to better organize the display of the form.

Again, using the example used above, you could write:

   my $form = new HTML::FormsDj(
      field => {
                title   => {
                            type     => 'text',
                            validate => valid_string(),
                            required => 1,
                        },
                author  => {
                            type     => 'text',
                            validate => valid_string(),
                            required => 1,
                           },
               },
      name => 'registerform',
      meta => {
                 fieldsets => [
                                {
                                  name        => 'titleset',
                                  description => 'Enter book title data here',
                                  legend      => 'Book Title',
                                  fields      => [
                                                   {
                                                    field    => 'title',
                                                    label    => 'Enter a book title',
                                                    message  => 'A book title must be at least 4 characters long',
                                                    classes  => [ qw(titlefield) ],
                                                   },
                                                  ]
                                },
                                {
                                  name        => 'authorset',
                                  description => 'Enter book author data here',
                                  legend      => 'Book Author',
                                  fields      => [
                                                   {
                                                    field    => 'author',
                                                    label    => 'Enter an author name',
                                                    message  => 'A book title must be at least 4 characters long',
                                                    classes  => [ qw(authorfield) ],
                                                   },
                                                  ]
                                },
                              ]
      }
   );

Ok, this looks a little bit more complicated. Essentially there is just one more level in the definition. A fieldset is just a list of groups of fields. It is defined as a list (an arrayref) which contains hashes, one hash per fieldset.

Each fieldset hash consists of some parameters, like a name or a legend plus a list of fields, which is exactly defined as in the meta parameter fields as seen above.

The output of the form is just devided into fieldsets, which is a HTML tag as well. Each fieldset will have a title, the legend parameter, an (optional) description and a name.

This is the very same as the META subclass in django forms is working.

Please note: you cannot mix a field list and fieldsets!

Only one of the two is possible.

If you omit the meta parameter at all, HTML::FormsDj will always generate a plain field list.

ADDING DEFAULT VALUES

IN some cases you'll need to put some defaults for form variables, eg. for choices or options.

You can do this by adding a default parameter to the field definition in your meta hash.

For text type variables this can just be a scalar. For choices and options you can supply a hash- or an array reference.

An example for a choice:

  # other fields
  ,
  {
    field => 'redirect',
    label => 'Redirect to page',
    default => [
                   {
                    value => 1,
                    label => '/home'
                   },
                   {
                    value => 2,
                    label => '/profile'
                   }
                ],
  }
  ,
  # other fields

In this example we've a choice which contains two values for the generated select form tag. Here we've used an array, which is the preferred way since this preserves order.

However, you might also supply a hash:

  # other fields
  ,
  {
    field => 'redirect',
    label => 'Redirect to page',
    default => {
                 1 => '/home',
                 2 => '/profile'
                }
  }
  ,
  # other fields

DISPLAYING THE FORM ^

To display the form, you have a couple of choices.

as_p

The easiest way is to use the as_p method. Usually you'll call this method from your template.

In the Dancer world you have to do it this way:

Pass the form to the template:

 template 'addbook', { form => $form }

And in your template 'addbook.tt' you call as_p:

 <% form.as_p %>

You have to take care of the HTML form tag yourself. A complete HTML form would look like this:

 <form name="addbook" method="post" action="/addbook">
 <% form.as_p %>
 <input type="submit" name="submit" value="Add Book"/>
 </form>

As you can see, you have to put the submit button yourself as well. This is because some people might add Javascript to the button or don't want to use such a button at all.

as_table

This display method generates a HTML table. Calling it works the very same as as_p:

 <% form.as_table %>

MANUAL RENDERING USING fields and fieldsets

Instead of letting HTML::FormsDj do the rendering of the form, you may render it in your template yourself. You can access the fields (or fieldsets containing fields, if any) from a forms object from a template.

Let's render the form for our book author example manually:

 <form name="addbook" method="post" action="/addbook">
 <% FOREACH field = form.fields %>
  <p id="<% form.id %>">
   <% field.label %>:
   <input type="text" name="<% field.field %>" value="<% field.value %>"/>
   <span style="color: red"><% form.message %></span>
   <br/>
  </p>
 <% END %>
 <input type="submit" name="submit" value="Add Book"/>
 </form>

That's pretty easy. Of course you need to check for the field type in your template, because different field types require different html output. You can check for field.type, eg:

 <% IF field.type == 'textarea' %>
   <textarea name="<% field.field %>"><% field.value %></textarea>
 <% END %>

as_is

This is in fact no display method, it rather just returns the normalized meta hash and NO HTML code. You can use this to generate the HTML yourself, perhaps if the provided methods here are not sufficient for you or if you have to output something different than HTML (e.g. JSON or XML).

The structure returned will look like this (based on our example above with some data filled in by a user):

 {
   'fields' => [
                 {
                   'classes' => [
                                  'formfield'
                                ],
                   'value'   => 'Neal Stephenson',
                   'default' => '',
                   'type'    => 'text',
                   'id'      => 'id_formfield_author',
                   'label'   => 'Author',
                   'field'   => 'author'
                 },
                 {
                   'classes' => [
                                  'formfield'
                                ],
                   'value'   => 'Anathem',
                   'default' => '',
                   'type'    => 'text',
                   'id'      => 'id_formfield_title',
                   'label'   => 'Title',
                   'field'   => 'title'
                 }
               ]
  };

Or, if it contains validation errors:

 {
   'fields' => [
                 {
                   'classes' => [
                                  'formfield'
                                ],
                   'value'   => '',
                   'default' => '',
                   'type'    => 'text',
                   'id'      => 'id_formfield_author',
                   'label'   => 'Author',
                   'field'   => 'author',
                   'message' => 'missing input',
                   'error'   => 'missing input',

                 },
                 {
                   'classes' => [
                                  'formfield'
                                ],
                   'value'   => 'Ana',
                   'default' => '',
                   'type'    => 'text',
                   'id'      => 'id_formfield_title',
                   'label'   => 'Title',
                   'field'   => 'title',
                   'message' => 'invalid input',
                   'error'   => 'valid_string',
                 }
               ]
  };

INPUT DATA VALIDATION ^

To validate the user input just fetch the HTTP POST data and pass them to the form. The Dancer way:

 my %input = params;
 my %clean = $form->cleandata(%input);

cleandata now generates based on your configuration Data::FormValidator and calls its check method to let it validate the input data.

It returns a plain perl hash containing the VALID data. This hash maybe incomplete if there were validation errors or required fields were not filled in by the user.

Therefore, you'll have to check if validation were successfull:

CHECK VALIDATION STATUS

Use the method clean to check if validation had errors. It returns a true value if not.

Example:

 if ($form->clean() ) {
   # save the data and tell the user
 }
 else {
   # put the same form back to the user again
   # so the user has to retry
 }

CUSTOM CLEAN METHOD

Beside the described validation technique you may also supply your own clean() method to the form, which may do additional checks, such as if a user exists in a database or the like.

You can do this by supplying a closure to the clean parameter (not method!) when you instantiate the form.

Example:

 my $form = new HTML::FormsDj(
      ..,
      clean      => sub {
        my (%clean) = @_;
        my $user = $db->resultset('User')->find({login => $clean{user}});
        if($user) {
          return (0, 'user exists');
        }
        else {
          return (1, '');
        }
      },
      ..
 );

In this example we're doing exactly this: we check if a user already exists.

The closure will get the %clean hash as a parameter, which contains the clean validated form data.

Note: This closure will only called if all other validations went successfull!

The closure is expected to return a list with two values: true or false and an error message.

USING Data::FormValidator ATTRIBUTES

The underlying validator module Data::FormValidator supports a couple of attributes which can be used to change its behavior.

You can supply such attributes to the form, which will be handed over to Data::FormValidator, eg:

 my $form = new HTML::FormsDj(
      ..,
      attributes => { filters  => ['trim'] },
      ..
 );

The attributes parameter is just a hashref. Everything inside will be supplied to Data::FormValidator::new(). Refer to its documentation which attributes could be used here.

ADVANCED CONTROL OF Data::FormValidator CONSTRAINTS

Usually HTML::FormsDj generates the DFV Profile used by the Data::FormValidator::check() method. Sometimes you might want to supply your own, for instance if you need multiple validators per variable or ir you want to modify the messages which will be returned on errors and the like.

You can do this by using the dfv parameter:

 my $form = new HTML::FormsDj(
      ..,
      dfv => {}
      ..
 );

Refer to Data::FormValidator#INPUT-PROFILE-SPECIFICATION how to specify/define the dfv profile.

In case you've got supplied a dfv profile, the form will not generate its own and just use the one you supplied and it will not check for errors or if it matches the field hash definition.

This technique is not recommended for the average user.

ERRORS AND DEBUGGING ^

You can use the form method dumpmeta, which dumps out the META hash, in your template to see what happens:

 <% form.dumpmeta %>

Beside errors per field there is also a global error variable which can be put out using the error method:

 <% form.error %>

CROSS SITE REQUEST FORGERY PROTECTION ^

This feature is experimental.

HTML::FormsDj provides CSRF attack protection. Refer to http://www.squarefree.com/securitytips/web-developers.html#CSRF to learn what it is.

To enable CSRF protection, you'll set the csrf parameter to a true value:

 my $form = new HTML::FormsDj(
                 ..,
                 csrf => 1
                 ..
 );

If enabled, the form will generate a unique token for the form based on the field names, some random number and current time.

This token must be set as a COOKIE during the GET request to your form and the very same token has to exist as a HIDDEN VARIABLE in the form.

Since HTML::FormsDj doesn't depend on Dancer (or any other perl app server), you are responsible for setting and retrieving the cookie.

On POST request the value of the cookie must match the value of the hidden variable. If one of them doesn't exist or the two are not the same, clean() returns FALSE. In addition no cleandata will be returned and no validation will be done.

HOW TO USE CSRF PROTECTION IN A DANCER APP

First, enable it using the parameter mentioned above:

 my $form = new HTML::FormsDj(
                 ..,
                 csrf => 1
                 ..
 );

In your route for the GET request set the cookie. You can retrieve the actual cookie value by using the csrfcookie method:

 cookie csrftoken => $form->getcsrf, expires => "15 minutes";
 template 'addbook', { form => $form };

Put this in your code where you're handling the GET request of the form.

In your code for the POST request, you'll have to retrieve the cookie and tell the form about it. This has to be done BEFORE you call clean:

  if ( request->method() eq "POST" ) {
    my %input = params;
    $form->csrfcookie(cookie 'csrftoken');
    my %clean = $form->cleandata(%input);
    if ($form->clean() ) {
    ..

That's it. If you're using as_p or as_table you are done and protected from this kind of attacks.

If you're creating your html form manually, you'll have to put the hidden value into your template this way:

 <% form.csrftoken %>

WHY?

The forms module might not sound as the right place where to do such things. Maybe a Dancer plugin for this would be the better choice to implement such a feature.

However, my idea was, if I am already maintaining forms, why not doing it in a secure way?

TODO ^

add more unit tests

SEE ALSO ^

I recommend you to read the following documents, which are supplied with Perl:

 perlreftut                     Perl references short introduction
 perlref                        Perl references, the rest of the story
 perldsc                        Perl data structures intro
 perllol                        Perl data structures: arrays of arrays

LICENSE AND COPYRIGHT ^

Copyright (c) 2012 T. Linden

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

BUGS AND LIMITATIONS ^

See rt.cpan.org for current bugs, if any.

INCOMPATIBILITIES ^

None known.

DIAGNOSTICS ^

To debug HTML::FormsDj use the Perl debugger, see perldebug.

DEPENDENCIES ^

HTML::FormsDj depends on the module Data::FormValidator. It can be used with Dancer, but this is no requirement.

AUTHOR ^

T. Linden <tlinden |AT| cpan.org>

VERSION ^

0.03

syntax highlighting: