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>
Alternatively you can create a form object and then render it later:
my $form = $f->form_for($person);
$form->render(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' });
});
Would return the same output.
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 secondary 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.
read_attribute_errors_for
$model->read_attribute_errors_for('user');
Optional. If provided should return an array of validation errors on a field. Allows you to do some final tuning of the error messages. If not provides we expect and errors object as documented in Valiant
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.
VIEW, CONTROLLER, CONTEXT API
Since you will have access potentially to a view, controller or context object you can use the following API in those classes in order to influence the form generation.
formbuilder_class
Optional. If provided should return a string that is the name of a class that will be used to instantiate the formbuilder. If not provided we default to Valiant::HTML::FormBuilder.
Generating a URL for the action attribute
NOTE: This is a work in progress and subject to change.
The following methods are used to generate the URL for the action
attribute of the form tag. They are tried in the following order:
$self->create_uri_for
$controller->create_uri
$self->update_uri_for
$controller->update_uri
?? Should there be a $c->uri_for_model($path, $model) ??
The 'create' methods are checked when the model is not persistent (ie not in storage). The 'update' methods are checked when the model is persistent (ie in storage).
We first look for these methods on the view object, then the controller object. If you don't provide any of these objects we will just return undef
which will result in no action
attribute being generated (You will have to supply it yourself).
All the 'update_*' methods are passed the full model path as an argument, which is an arrayref whose last member is the model object and any preceding members are the parent objects or strings used to create a path. For example if you have a deeply nested model you might need both its ID and the ID of its parent to form the correct URL to its update action.
All the 'create_*' methods are passed only the 'path' parts of model path, that is the models or strings that are used to create the path. However the create_uri_for
(and the update_uri_for
) methods always get the full path as an argument. You might use those if you create a central registry mapping models to controllers (perhaps this is a possible good idea for a Catalyst plugin or similar?)
This allows you to move more of the request and application bound logic and information out of the view and back to the controller. For an example of how this works with Catalyst you can review the example application in the example
directory of this distribution.
If you prefer a different behavior you can override the methods _edit_action_for_model
and _new_action_for_model
in your subclass. Or just set the url directly in the form and ignore this feature
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 andname
attributes. - method
-
Sets the form attribute
method
. Generally defaults topost
orpatch
(whenin_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. This is a string and used exactly as typed.
- 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 thecsrf_token
method on the current view object. If that doesn't exist we will try to use thecsrf_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. - url
-
The url to use for the form action (unless 'action' is defined). Can be a string or a coderef. if a coderef will recieve the view object and model_path as argument.
url => sub ($self, $model_path) { $model_path->[-1]->persisted ? path('update') : path('create') }
- edit_url
- new_url
-
A string or coderef that is the url for the form action when the model is persisted (edit_url) or not persisted (new_url). If you provide these we will ignore the
url
option.edit_url => path('update'), new_url => path('create'),
If using the coderef form you get the view and model_path as arguments:
edit_url => sub ($self, $model_path) { path('update') }
For
edit_url
the model_path will be the full path to the model, fornew_url
the model_path will be the path to the model minus the last element (which is the model itself). - controller
-
The controller object that is associated with the form, if one exists. Defaults to the controller associated with the view if it exists.
- allow_method_names_outside_object
-
By default you can indicate field attribute names in your formbuilder methods that have no map to anything on the model to provide a value. This is useful when you are adding fields that you are making part of the form but are not part of the model to be updated. However this could lead to hard to debug errors in your code if you make a typo in the field attribute name or if you change the model. Setting this to 1 (true) will instead result in an error when you try to use a field name that isn't part of the model.
This defaults to false for 'form_for', 'fields_for' and true for 'form_with', 'fields'.
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