Mojolicious-Plugin-FormFields

 view release on metacpan or  search on metacpan

lib/Mojolicious/Plugin/FormFields.pm  view on Meta::CPAN

sub each
{
    my $self = shift;
    my $block = pop;
    my $fields = $self->_to_fields;

    return $fields unless ref($block) eq 'CODE';

    local $_;
    $block->() for @$fields;

    return;
}

sub check
{
    my $self = shift;
    push @{$self->{checks}}, $self->{name} => shift;
    $self;
}

sub filter
{
    my $self = shift;
    my $data = ref $_[0] eq 'CODE' ? shift : Validate::Tiny::filter(@_);
    push @{$self->{filters}}, $self->{name} => $data;
    $self;
}

# Just a single value
sub error
{
    my $self = shift;
    $self->{result}->{error}->{$self->{name}};
}

sub separator { $SEPARATOR; }

sub valid
{
    my $self = shift;
    return $self->{result}->{success} if defined $self->{result};

    my $result;
    my $name  = $self->{name};
    my $value = $self->{c}->param($name);
    my $field = { $name => $value };
    my $rules = {
	fields  => [ $name ],
	checks  => $self->{checks},
	filters => $self->{filters}
    };

    # A bit of massaging For the is_equal() validation
    my $eq = $self->{eq_to_field};
    if($eq) {
	$field->{$eq} = $self->{c}->param($eq);
	push @{$rules->{fields}}, $eq;
    }

    $result = Validate::Tiny::validate($field, $rules);
    $self->{c}->param($name, $result->{data}->{$name}) if @{$self->{filters}};
    $self->{result} = $result;

    $result->{success};
}

sub is_equal
{
    my $self = shift;
    $self->{eq_to_field} = $_[0];
    push @{$self->{checks}}, $self->{name} => Validate::Tiny::is_equal(@_);
}

# Avoid AUTOLOAD call
sub DESTROY { }

our $AUTOLOAD;
sub AUTOLOAD
{
    my $self   = shift;
   (my $method = $AUTOLOAD) =~ s/[^':]+:://g;

    if($method =~ /^is_/) {
	my $check = Validate::Tiny->can($method);
	die qq|Can't locate object method "$method" via package "${ \__PACKAGE__ }"| unless $check;

	push @{$self->{checks}}, $self->{name} => $check->(@_);
    }
    else {
	# TODO: What's the use case for this?
	# field('name')->trim instead of field('name')->filter('trim')?
	push @{$self->{filters}}, $self->{name} => Validate::Tiny::filter($method);
    }

    $self->{result} = undef;	# reset previous validation
    $self;
}

sub _to_string { shift->_lookup_value; }

sub _to_fields
{
    my $self  = shift;
    my $value = $self->_lookup_value;

    my $fields = [];
    return $fields unless ref($value) eq 'ARRAY';

    my $i = -1;
    while(++$i < @$value) {
	push @$fields, $self->{c}->fields($self->_path($i), $self->{object});
    }

    $fields;
}

sub _dom_id
{
    my @name = @_;
    s/[^\w]+/-/g for @name;

lib/Mojolicious/Plugin/FormFields.pm  view on Meta::CPAN

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

The flattened parameter can also be used

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

See L<Mojolicious::Plugin::ParamExpand> for more info.

=head2 SCOPING

Fields can be scoped to a particular object/data structure via the C<< L</fields> >> helper

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

When using C<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 C<field>.

=head2 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 C<object> and C<index> methods.

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

=head1 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 C<valid> method

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

This will only validate and return the error for the C<user.name> field. To validate all fields and retrieve all error messages call the controller's C<valid> and C<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 C<error>/C<errors> and C<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 L</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');

=head2 AVAILABLE RULES & FILTERS

C<Mojolicious::Plugin::FormFields> uses C<Validate::Tiny>, see L<its docs|Validate::Tiny/filter> for a list.

=head2 RENAMING THE VALIDATION METHODS

In the event that the C<valid> and/or C<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 I<only> changes the methods B<on the controller> and does not change the methods on the object returned by C<field>.

=head1 METHODS

=head2 field

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

=head3 Arguments

C<$name>

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

C<$object>



( run in 3.702 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )