The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Valiant::HTML::Util::Form - HTML Form

SYNOPSIS

Given a model like:

    package Person;

    use Moo;
    use Valiant::Validations;

    has first_name => (is=>'ro');
    has last_name => (is=>'ro');

    validates ['first_name', 'last_name'] => (
      length => {
        maximum => 20,
        minimum => 3,
      }
    );

Wrap a formbuilder object around it and generate HTML form field controls:

    use Valiant::HTML::Util::Form;

    my $f = Valiant::HTML::Util::Form->new()
    my $person = Local::Person->new(first_name=>'J', last_name=>'Napiorkowski');
    $person->validate;

    $f->form_for($person, sub($fb, $person) {
      return  $fb->label('first_name'),
              $fb->input('first_name'),
              $fb->errors_for('first_name', +{ class=>'invalid-feedback' }),
              $fb->label('last_name'),
              $fb->input('last_name'),
              $fb->errors_for('last_name'+{ class=>'invalid-feedback' });
    });

Generates something like:

    <form accept-charset="UTF-8" class="new_person" enctype="multipart/form-data" id="new_person" method="post">
      <label for="person_first_name">First Name</label>
      <input id="person_first_name" name="person.first_name" type="text" value="John"/>
      <div class='invalid-feedback'>First Name is too short (minimum is 3 characters).</div>
      <label for="person_last_name">Last Name</label>
      <input id="person_last_name" name="person.last_name" type="text" value="Napiorkowski"/>
    </form>

DESCRIPTION

Builds on Valiant::HTML::Util::TagBuilder and Valiant::HTML::Util::FormTags to provide a wrapper around a model object that can be used to generate HTML form controls via a formbuilder such as Valliant::HTML::FormBuilder. or a subclass thereof.

Like its parent classes, you can provide a view context to the constructor and it will be used to provide attribute values as well as methods used to create safe strings. This is documented extensively in Valiant::HTML::Util::TagBuilder which you should review if you are creating your own view integration. You can see an example view context in Valiant::HTML::Util::View.

INHERITED METHODS

This class inherits all methods from Valiant::HTML::Util::TagBuilder and Valiant::HTML::Util::FormTags.

REQUIRED MODEL API

This (and Valiant::HTML::FormBuilder) wrap an object model that is expected to do the following interface.

to_model

This is an optional method. If this is supported, we call to_model on the wrapped object before using it on the form methods. This allows you to delegate the required API to a secondard object, which can result in a cleaner API depending on your designs and use cases.

in_storage

This is an optional method. If your object has a backing storage solution (such as your object is an instance of a DBIC Result Source) you can provide this method to influence how your object form tags are created. If provided this method should return a boolean when if true means that the object is representing data which is stored in the backing storage solution. Please note that this does not mean that the object is synchronized with the backing storage since its possible that the object has been changed by the user.

is_attribute_changed

    $model->is_attribute_changed($attr); # true or false

Optional method. If provided returns a boolean if the attribute has been changed from its initial state, as defined by either being different from the backing store (if it exists) or being changed from its default value when created as a new model.

human_attribute_name

    $model->human_attribute_name('user'); # User 

Optional. If provided, uses the model to look up a displayable version of the attribute name, for example used in a label for an input control. If not present we use Valiant::HTML::FormTags\_humanize to create a displayable name from the attribute name.

read_attribute_for_html

    $model->read_attribute_for_html('user'); # User 

Optional. If provided must access the string name of the field or attribute and should return the model value for that attribute suitable for HTML form display. You might wish to use this as a way to deflate or otherwise stringify non string values. If not provided we just use the attribute name and call it as an accessor against the model.

errors

Optional. Should return an instances of Valiant::Errors. If present will be used to lookup model and attribute errors.

Please note this currently is tried to behavior expected from Valiant::Errors but in the future we might try to make this tied to a defined interface rather than this concrete class.

has_errors

Optional if you don't use builder methods that are for displaying errors; required otherwise. A boolean that indicates if your model has errors or not. Used to determined if error_classes are merged into class and in a few similar places.

i18n

Optional. If provided should return an instance of Valiant::I18N. Used in a few places to support translation tags.

model_name

Optional. If provide should return an instance of Valiant::Name. Used in a few places where we default a value to a human readable version of the model name. If you don't have this method we fall back to guessing something based on the string name of the model.

primary_columns

Optional. When a model does in_storage and its a nested model, when this method exists we use it to get a list of primary columns for the underlying storage and then add them as hidden fields. This is needed particularly with one - many style relationships so that we find the right record in the storage model to update.

NOTE: This method requirement is subject to change. It feels a bit too tightly bound to the idea of and ORM and to DBIx::Class in particular.

is_marked_for_deletion

Optional, but should be supported if your model supports a storage backend. A boolean that indicates if the model is currently marked for deletion upon successful validation. Used for things like radio and checkbox collections when the state should be 'unchecked' even if the model is still in storage.

INSTANCE METHODS

The following public instance methods are provided by this class.

form_for

    $f->form_for($name, $model, \%options, \&block);
    $f->form_for($model, \%options, \&block);
    $f->form_for($model, \&block);
    $f->form_for($name, \%options, \&block);
    $f->form_for($name, \&block);

Canonical xample. $person is either an object or the name of an attribute on the $view that will supply the object.

    $f->form_for($person, sub($fb, $person) {
      return  $fb->label('name'),
              $fb->input('name');
    });

    # Generates something like:

    <form accept-charset="UTF-8" class="new_person" enctype="multipart/form-data" id="new_person" method="post">
      <label for="person_name">Name</label>
      <input id="person_name" name="person.name" type="text" value="John"/>
    </form>

Given a model as described above, wrap a Valiant::HTML::FormBuilder instance around it which provides methods for generating valid HTML form output. This provides a view logic centered method for creating sensible and reusable form controls which include server generated error output from validation.

See Valiant::HTML::FormBuilder for more on the formbuilder API.

\%options are used to influence the builder creation as well as pass attributes to the generated form tag. Options are as follows:

as

Supplies the name argument for Valiant::HTML::FormBuilder. This is generally used to set the top namespace for your field IDs and name attributes.

method

Sets the form attribute method. Generally defaults to post or patch (when in_storage is supported and the model is marked for updating of an existing model).

action

Should be the URL that the form with post to.

data

a hashref of HTML tag <data> attributes.

class
style

HTML attributes that get merged into the html options (below)

html

a hashref of items that will get rendered as HTML attributes for the form.

namespace

Optional. Can use used to prepend a namespace to your form IDs

index

Optional. When processing a collection model this will be the index of the current model.

builder

The form builder. Defaults to Valiant::HTML::FormBuilder. You can set this if you create your own formbuilder subclass and want to use that. If you don't provide a value we also check the attached view object for a formbuilder_class method and use that if it exists.

csrf_token

Optional. If provided, will be used to generate a hidden field with the name csrf_token. This is useful for CSRF protection. If you don't provide a value we will try to use the csrf_token method on the current view object. If that doesn't exist we will try to use the csrf_token method on the current context object (if one exists). If you are using Catalyst with this you can use Catalyst::Plugin::CSRFToken to generate a token.

The last argument should be a reference to a subroute that will receive the created formbuilder object and should return a string, or array of strings that will be flattened and displayed as your form elements. Any strings returns not marked as safe via Valiant::HTML::SafeString will be encoded and turned safe so be sure to mark any raw strings correctly unless you want double encoding issues.

You can also provide a string as the first argument to this method and it will be used to set the overall scope of the formbuilder. This is useful if you want to use the same formbuilder for multiple models. For example:

    $f->form_for('person', sub($fb, $person) {
      $fb->input('name');
    });

    $f->form_for('address', sub($fb, $address) {
      $fb->input('street');
    });

    # Generates something like:

    <form accept-charset="UTF-8" class="new_person" enctype="multipart/form-data" id="new_person" method="post">
      <input id="person_name" name="person.name" type="text" value="John"/>
    </form>

    <form accept-charset="UTF-8" class="new_address" enctype="multipart/form-data" id="new_address" method="post">
      <input id="address_street" name="address.street" type="text" value="123 Main St"/>
    </form>

If the string name refers to an attribute on the current view object that attribute will be used to provide model data. Lastly you can pass both a string name and a model object and the string name will be used to set the scope of the formbuilder and the model object will be used to provide the model data.

Example:

    $f->form_for('foo', $person, sub($fb, $person) {
      return  $fb->label('name'),
              $fb->input('name');
    });

    # Generates something like:

    <form accept-charset="UTF-8" class="new_foo" enctype="multipart/form-data" id="new_foo" method="post">
      <label for="foo_name">Name</label>
      <input id="foo_name" name="foo.name" type="text" value="John"/>
    </form>

fields_for

    $f->fields_for($name, $model, \%options, \&block);
    $f->fields_for($model, \%options, \&block);
    $f->fields_for($model, \&block);
    $f->fields_for($name, \%options, \&block);
    $f->fields_for($name, \&block);

Create an instance of a formbuilder that represents a model or a namespace in which to build form elements. Its basically <form_for> without the form tag. This is useful for building nested forms or for building forms that are not the top level form.

Examples:

    $f->fields_for($person, sub {
      my ($fb) = @_;
      return $fb->input('first_name'),
    });

    # <input id="person_first_name" name="person.first_name" type="text" value="aa"/>

    # Assume that the view has a $person attribute
    $f->fields_for('person', sub {
      my ($fb) = @_;
      return $fb->input('first_name'),
    });

    # <input id="person_first_name" name="person.first_name" type="text" value="aa"/>

    $f->fields_for('foo', $person, sub {
      my ($fb) = @_;
      return $fb->input('first_name'),
    });

    # <input id="foo_first_name" name="foo.first_name" type="text" value="aa"/>

    # In this case there is no model for the values or errors, we're just using the
    # formbuilder to generate the correct names and ids for an empty form.

    $f->fields_for('foo', sub {
      my ($fb) = @_;
      return $fb->input('first_name'),
    });

    # <input id="foo_first_name" name="foo.first_name" type="text" value=""/>
}

done_testing;

SEE ALSO

Valiant, Valiant::HTML::FormBuilder, Valiant::HTML::Util::FormTags

AUTHOR

See Valiant

COPYRIGHT & LICENSE

See Valiant