Dave Rolsky > Chloro-0.06 > Chloro::Manual::Intro

Download:
Chloro-0.06.tar.gz

Annotate this POD

CPAN RT

Open  0
View/Report Bugs
Source  

NAME ^

Chloro::Manual::Intro - Basic form and field definition

VERSION ^

version 0.06

DEFINING A BASIC FORM ^

A form is defined as a unique class, so you might have MyApp::Form::Login, MyApp::Form::User, etc. To make a form class, just use Moose and then use Chloro.

When you use Chloro, your class is a form. A form class automatically consumes the Chloro::Role::Form role. Since you're still using Moose, you can inherit from other classes, define attributes, consumes other roles, etc.

You can also use Chloro in a role, and then compose those form roles into form classes.

A form consists of one or more fields. A field is a name plus a data type, as well as some other optional parameters.

    package MyApp::Form::User;

    use Moose;
    use Chloro;

    field username => (
        isa      => 'Str',
        required => 1,
    );

    field email_address => (
        isa      => 'EmailAddress',
        required => 1,
    );

    field password => (
        isa    => 'Str',
        secure => 1,
    );

    field password2 => (
        isa    => 'Str',
        secure => 1,
    );

    sub _validate_form {
        my $self   = shift;
        my $params = shift;

        # Use a bare return if form is valid.
        return if ...;

        # Check that passwords are the same. Maybe check that password is
        # present if required. Return a list of error messages.

        return 'The two password fields must match.';
    }

FIELDS ^

A field requires a name and a type. The type is a Moose type constraint, (not an HTML widget type). A field can be a Str, Int, ArrayRef[Int], or a DateTime, or anything else you can define as a Moose type.

Field values are extracted from the user-submitted params during when you call $form->process( params => $params ). By default, the extractor looks for a key matching the field's name, but you can define your own extraction logic. For example, you could define a DateTime field that looked for three separate keys, day, month, year, and used those to construct a DateTime object.

Fields are declared with the field() subroutine exported by Chloro. This subroutine allows the following parameters:

PROCESSING USER INPUT ^

Each form object is immutable. The form processes user input and returns a Chloro::ResultSet object. The resultset in turn contains a set of Chloro::Result::Field objects, one for each field in the form.

    my $resultset = $form->process( params => $params );

The $params value is simple a hash reference of the user submitted input. If you're using Catalyst you could write this:

    my $resultset = $form->process( params => $c->request()->params() );

The first thing you should do with the resultset is check whether it is valid. Valid means that there all of the fields passed their required, type, validator checks. If the form defined any form-level validations, these must also pass for the resultset to be valid.

    if ( $resultset->is_valid() ) {
        ...
    }
    else {
        ...
    }

If the resultset is valid, there a number of ways to retrieve the munged user input. The easiest is to call $resultset->results_as_hash(), which returns a hash reference.

The keys are field names from your form and the results are the value for that field.

    my $user_data = $resultset->results_as_hash();

    $user->update( $user_data );

If the resultset isn't valid you can retrieve the errors from the resultset:

    for my $error ( $resultset->all_errors() ) {
        ...
    }

The errors are objects, and can be either Chloro::Error::Field or Chloro::Error::Form objects. A field error is associated with a specific field, while a form error is not. Both of these objects will have an error message object available from $error->message().

What you do with these error objects is up to you.

What I do is take each field error and display the error near the field in question. I also change the CSS class of the div that holds the field so that it has an orange background.

I display all the form errors at the top of the form in another box with an orange background.

PUTTING IT ALL TOGETHER ^

Here's an example of how you might use Chloro in a Catalyst controller:

    sub login {
        my $self = shift;
        my $c    = shift;

        my $form = MyApp::Form::Login->new();

        my $resultset = $form->process( params => $c->request()->params() );

        if ( $resultset->is_valid() ) {
            # Set authentication cookie or do something with session.
            # Then redirect somewhere useful
        }
        else {
            $c->session()->{errors}    = [ $resultset->all_errors() ];
            $c->session()->{form_data} = $resultset->secure_results_as_hash();

            # redirect back to login form
        }
    }

    sub login_form {
        my $self = shift;
        my $c    = shift;

        # Your view code will look for this data and do something useful with
        # it.
        $c->stash()->{errors}    = $c->session()->{errors}    || [];
        $c->stash()->{form_data} = $c->session()->{form_data} || {};

        $c->stash()->{template} = 'login_form.html';
    }

VALIDATING THE WHOLE FORM ^

Some validations cannot be expressed by validating a single field. For example, when a user changes their password, you generally require them to type it twice. You want to compare the two passwords and make sure they match.

Adding whole form validation logic can be done by adding a _validate_form() method to your form class:

    package MyApp::Form::User;

    use Moose;
    use Chloro;

    use List::AllUtils qw( any );

    field email_address => (
        isa      => 'EmailAddress',
        required => 1,
    );

    field password => (
        isa      => 'Str',
        secure   => 1,
    );

    field password2 => (
        isa      => 'Str',
        secure   => 1,
    );

    sub _validate_form {
        my $self    = shift;
        my $params  = shift;
        my $results = shift;

        my $pw1 = $results->{password}->value();
        my $pw2 = $results->{password2}->value();

        return unless any { defined && length } $pw1, $pw2;

        return if ( $pw1 // q{} ) eq ( $pw1 // q{} );

        return 'The two passwords you provided did not match.';
    }

The _validate_form() method will be called with two arguments. The first is the raw parameters passed to $form->process(). The second is a hash reference where the keys are field and group names and the values are Chloro::Result::Field and Chloro::Result::Group objects.

Generally, it's best to get the data from the result objects, since this is the result of running any custom extraction logic.

The _validate_form() object is expected to return a list of errors if there are any. These can either be strings or Chloro::ErrorMessage objects.

In the example above, our _validate_form() method checks several things. First, if both password fields are empty, we return false, because there's nothing to check. By default, we assume that no input means the user does not want to change their password.

Next, we check whether the passwords match. If they do, we return false. The <( $pw1 // q{} )> construct is there to avoid warnings from uninitialized values.

Finally, if they don't match, we return a string containing the error. This will be turned into an error object that is available from the Chloro::ResultSet object.

REPEATABLE GROUPS ^

Chloro also supports the use of repeatable groups in forms. This is discussed in Chloro::Manual::Groups.

AUTHOR ^

Dave Rolsky <autarch@urth.org>

COPYRIGHT AND LICENSE ^

This software is Copyright (c) 2011 by Dave Rolsky.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)
syntax highlighting: