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

NAME

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

VERSION

version 0.07

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 MooseX::Types::Email qw/EmailAddress/;
    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:

  • isa

    This must be a Moose type constraint. This can be passed as a string, a type constraint object, or a MooseX::Types type.

    This type will be used to validate the field when it is submitted.

    This is required.

  • default

    The default value for the field. This can either be a non-reference scalar, or a subroutine reference.

    If this is a subroutine reference, it will be called as a method on the field object. It will also receive the parameters being processed and the field prefix as arguments.

    Field prefixes only matter for field groups, which are documented later.

    This is optional.

  • required

    A field can be made required. If a required field is missing, the form submission is not valid.

  • secure

    If a field is marked as secure, then it is excluded from the data returned by the Chloro::ResultSet class's secure_results_as_hash() method.

    The primary use of this is to avoid putting sensitive data like passwords or credit cards numbers in a session or query parameter.

  • extractor

    This is a subroutine reference that defines how the field's value is extracted from the hash reference of parameters that a form processes.

    This subroutine will be called as a method on the form object. It will receive three additional parameters.

    The first is the hash reference of parameters that was passed to the $form->process() method.

    The second is the prefix for the group, if there is one. For fields outside a group, this will be undefined.

    The third parameter is the Chloro::Field object for the field.

    By default, the extractor simply looks for a key in the user-submitted parameters that matches the field's name (with a group prefix, if needed). You can override this to implement a more complex extraction strategy. For example, you might extract a date from three separate field (year, month, day).

    The extractor is expected to return a two element list. The first should be the name of the field in the form, the second is the value.

    If the field does not have a correspondence to any one field you can return undef for the name. If you provide a name, it will be passed to the Chloro::Result::Field object as the name_in_form parameter.

  • validator

    This is a subroutine reference that defines how the field's value is validated.

    This subroutine will be called as a method on the Chloro::Field object itself. It will receive four additional parameters.

    The first is the value being validated. The second parameter is the hash reference of data submitted to the form.

    The third is the prefix for the group, if there is one. For fields outside a group, this will be undefined.

    The fourth is the Chloro::Field object for the field.

    Note that the validator is called in addition to validating the field's type. If the type validation fails, then the validator will not be called at all.

    If the validator returns nothing, the field is valid. If the field is invalid, you can either return a string or a Chloro::ErrorMessage object.

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 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 MooseX::Types::Email qw/EmailAddress/;
    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 ( $pw2 // 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.

SUPPORT

Bugs may be submitted at http://rt.cpan.org/Public/Dist/Display.html?Name=Chloro or via email to bug-chloro@rt.cpan.org.

I am also usually active on IRC as 'autarch' on irc://irc.perl.org.

SOURCE

The source code repository for Chloro can be found at https://github.com/autarch/Chloro.

AUTHOR

Dave Rolsky <autarch@urth.org>

COPYRIGHT AND LICENSE

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

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

The full text of the license can be found in the LICENSE file included with this distribution.