Mojolicious::Plugin::FormFields

# NAME

Mojolicious::Plugin::FormFields - Lightweight form builder with validation and filtering

# SYNOPSIS

      $self->plugin('FormFields');

      # In your controller
      sub edit
      {
          my $self = shift;
          my $user = $self->users->find($self->param('id'));
          $self->stash(user => $user);
      }

      sub update
      {
          my $self = shift;
          $self->field('user.name')->is_required;
          $self->field('user.password')->is_required->is_equal('user.confirm_password');

          if($self->valid) {
              $self->users->update($self->param('user'));
              $self->redirect_to('/profile');
              return;
          }
      }

      # In your view
      field('user.name')->text
      field('user.name')->error unless field('user.name')->valid

      field('user.password')->password
      field('user.age')->select([10,20,30])
      field('user.password')->password
      field('user.taste')->radio('me_gusta')
      field('user.taste')->radio('estoy_harto_de')
      field('user.orders.0.id')->hidden

      # Fields for a collection
      my $kinfolk = field('user.kinfolk');
      for my $person (@$kinfolk) {
        $person->hidden('id')
        $person->text('name')
      }

      # Or, scope it to the 'user' param
      my $user = fields('user')
      $user->hidden('id')
      $user->text('name')
      $user->error('name') unless $user->valid('name')
      $user->label('admin')
      $user->checkbox('admin')
      $user->password('password')
      $user->select('age', [ [X => 10], [Dub => 20] ])
      $user->file('avatar')
      $user->textarea('bio', size => '10x50')

      my $kinfolk = $user->fields('kinfolk')
      for my $person (@$kinfolk) {
        $person->text('name')
        # ...
      }

# DESCRIPTION

`Mojolicious::Plugin::FormFields` allows you to bind objects and data structures to form fields. It also performs validation and filtering via Validate::Tiny.

# CREATING FIELDS

Fields can be bound to a hash, an array, something blessed, or any combination of the three. They are created by calling the `"field"` helper with a path to the value you want to bind, and then calling the desired HTML input method

      field('user.name')->text

Is the same as

      text_field 'user.name', $user->name, id => 'user-name'

(though `Mojolicious::Plugin::FormFields` sets `type="text"`).

Field names/paths are given in the form `target.accessor1 [ .accessor2 [ .accessorN ] ]` where `target` is an object or data structure and `accessor` is a method, hash key, or array index. The target must be in the stash under the key `target` or provided as an argument to `"field"`.

Some examples:

      field('users.0.name')->text

Is the same as

      text_field 'users.0.name', $users->[0]->name, id => 'users-0-name'

And

      field('item.orders.0.XAJ123.quantity')->text

Is equivalent to

      text_field 'item.orders.0.XAJ123.quantity', $item->orders->[0]->{XAJ123}->quantity, id => 'item-orders-0-XAJ123-quantity'

As you can see DOM IDs are always created.

Here the target key `book` does not exist in the stash so the target is supplied

      field('book.upc', $item)->text

If a value for the flattened representation exists (e.g., from a form submission) it will be used instead of the value pointed at by the field name (desired behavior?). This is the same as Mojolicious' Tag Helpers.

Options can also be provided

      field('user.name')->text(class => 'input-text', 'data-name' => 'xxx')

See "SUPPORTED FIELDS" for the list of HTML input creation methods.

## STRUCTURED REQUEST PARAMETERS

Structured request parameters for the bound object/data structure are available via `Mojolicious::Controller`'s param method. They can not be accessed via `Mojo::Message::Request`.

A request with the parameters `user.name=nameA&user.email=email&id=123` can be accessed in your action like

      my $user = $self->param('user');
      $user->{name};
      $user->{email};

Other parameters can be accessed as usual

      $id = $self->param('id');

The flattened parameter can also be used

      $name = $self->param('user.name');

See Mojolicious::Plugin::ParamExpand for more info.

## SCOPING

Fields can be scoped to a particular object/data structure via the `"fields"` helper

      my $user = fields('user');
      $user->text('name');
      $user->hidden('id');

When using `fields` you must supply the field's name to the HTML input and validation methods, otherwise the calls are the same as they are with `field`.

## COLLECTIONS

You can also create fields scoped to elements in a collection

      my $addresses = field('user.addresses');
      for my $addr (@$addresses) {
        # field('user.addresses.N.id')->hidden
        $addr->hidden('id');

        # field('user.addresses.N.street')->text
        $addr->text('street');

        # field('user.addresses.N.city')->select([qw|OAK PHL LAX|])
        $addr->select('city', [qw|OAK PHL LAX|]);
      }

Or, for fields that are already scoped

      my $user = fields('user')
      $user->hidden('id');

      my $addressess = $user->fields('addresses');
      for my $addr (@$addresses) {
        $addr->hidden('id')
        # ...
      }

You can also access the underlying object and its position within a collection via the `object` and `index` methods.

      <% for my $addr (@$addresses) {  %>
        <div id="<%= dom_id($addr->object) %>">
          <h3>Address #<%= $addr->index + 1 %></h3>
          <%= $addr->hidden('id') %>
          ...
        </div>
      <% } %>

# VALIDATING & FILTERING

Validation rules are created by calling validation and/or filter methods on the field to be validated

      # In your controller
      my $self = shift;
      $self->field('user.name')->is_required;
      $self->field('user.name')->filter('trim');

These methods can be chained

      $self->field('user.name')->is_required->filter('trim');

To perform validation on a field call its `valid` method

      $field = $self->field('user.name');
      $field->is_required;
      $field->valid;
      $field->error;

This will only validate and return the error for the `user.name` field. To validate all fields and retrieve all error messages call the controller's `valid` and `errors` methods

      $self->field('user.name')->is_required;
      $self->field('user.age')->is_like(qr/^\d+$/);
      $self->valid;

      my $errors = $self->errors;
      $errors->{'user.name'}
      # ...

Of course the `error`/`errors` and `valid` methods can be used in your view too

      <% unless(valid()) { %>
        <p>Hey, fix the below errors</p>
      <% } %>

      <%= field('name')->text %>
      <% unless(field('name')->valid) { %>
        <span class="error"><%= field('name')->error %></span>
      <% } %>

When creating validation rules for "fields" you must pass the field name as the first argument

      my $user = fields('user');
      $user->is_required('password');
      $user->is_equal(password => 'confirm_password');
      $user->is_long_at_least(password => 8, 'Mais longo caipira');

## AVAILABLE RULES & FILTERS

`Mojolicious::Plugin::FormFields` uses `Validate::Tiny`, see its docs for a list.

## RENAMING THE VALIDATION METHODS

In the event that the `valid` and/or `errors` methods clash with exiting methods/helpers in your app you can rename them by specifying alternate names when loading the plugin

      $self->plugin('FormFields', methods => { valid => 'form_valid', errors => 'form_errors' });
      # ...

      $self->field('user.name')->is_required;
      $self->form_valid;
      $self->form_errors;

Note that this only changes the methods on the controller and does not change the methods on the object returned by `field`.

# METHODS

## field

      field($name)->text
      field($name, $object)->text

### Arguments

`$name`

The field's name, which can also be the path to its value in the stash. See "CREATING FIELDS".

`$object`

Optional. The object used to retrieve the value specified by `$name`. Must be a reference to a hash, an array, or something blessed. If not given the value will be retrieved from the stash or, for previously submitted forms, the request parameter `$name`.

### Returns

An object than can be used to create HTML form fields, see "SUPPORTED FIELDS".

### Errors

An error will be raised if:

- `$name` is not provided
- `$name` cannot be retrieved from `$object`
- `$object` cannot be found in the stash and no default was given

### Collections

See "COLLECTIONS"

## fields

      $f = fields($name)
      $f->text('address')

      $f = fields($name, $object)
      $f->text('address')

Create form fields scoped to a parameter.

For example

      % $f = fields('user')
      %= $f->select('age', [10,20,30])
      %= $f->textarea('bio')

Is the same as

      %= field('user.age')->select([10,20,30])
      %= field('user.bio')->textarea

### Arguments

Same as "field".

### Returns

An object than can be used to create HTML form fields scoped to the `$name` argument, see "SUPPORTED FIELDS".

### Errors

Same as "field".

### Collections

See "COLLECTIONS"

# SUPPORTED FIELDS

## checkbox

      field('user.admin')->checkbox(%options)
      field('user.admin')->checkbox('yes', %options)

Creates

      <input type="checkbox" name="user.admin" id="user-admin-1" value="1"/>
      <input type="checkbox" name="user.admin" id="user-admin-yes" value="yes"/>

## file

      field('user.avatar')->file(%options);

Creates

      <input id="user-avatar" name="user.avatar" type="file" />

## hidden

      field('user.id')->hidden(%options)

Creates

      <input id="user-id" name="user.id" type="hidden" value="123123" />

## input

      field('user.phone')->input($type, %options)

For example

      field('user.phone')->input('tel', pattern => '\d{3}-\d{4}')

Creates

      <input id="user-phone" name="user.phone" type="tel" pattern="\d{3}-\d{4}" />

## label

      field('user.name')->label
      field('user.name')->label('Nombre', for => "tu_nombre_hyna")

Creates

      <label for="user-name">Name</label>
      <label for="tu_nombre_hyna">Nombre</label>

## password

      field('user.password')->password(%options)

Creates

      <input id="user-password" name="user.password" type="password" />

## select

      field('user.age')->select([10,20,30], %options)
      field('user.age')->select([[Ten => 10], [Dub => 20], [Trenta => 30]], %options)

Creates

      <select id="user-age" name="user.age">
        <option value="10">10</option>
        <option value="20">20</option>
        <option value="30">30</option>
      </select>

      <select id="user-age" name="user.age">
        <option value="10">Ten</option>
        <option value="20">Dub</option>
        <option value="30">Trenta</option>
      </select>

## radio

      field('user.age')->radio('older_than_21', %options)

Creates

      <input id="user-age-older_than_21" name="user.age" type="radio" value="older_than_21" />

## text

      field('user.name')->text(%options)
      field('user.name')->text(size => 10, maxlength => 32)

Creates

      <input id="user-name" name="user.name" value="sshaw" />
      <input id="user-name" name="user.name" value="sshaw" size="10" maxlength="32" />

## textarea

      field('user.bio')->textarea(%options)
      field('user.bio')->textarea(size => '10x50')

Creates

      <textarea id="user-bio" name="user.bio">Proprietary and confidential</textarea>
      <textarea cols="50" id="user-bio" name="user.bio" rows="10">Proprietary and confidential</textarea>

# AUTHOR

Skye Shaw (sshaw [AT] gmail.com)

# SEE ALSO

Mojolicious::Plugin::TagHelpers, Mojolicious::Plugin::ParamExpand, Validate::Tiny, Mojolicious::Plugin::DomIdHelper

# COPYRIGHT

Copyright (c) 2012-2014 Skye Shaw.

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