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::FormBuilder - General HTML Forms

SYNOPSIS

Given a model with the correct API such as:

    package Local::Person;

    use Moo;
    use Valiant::Validations;

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

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

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

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

    my $fb = Valiant::HTML::FormBuilder->new(
      model => $person,
      name => 'person'
    );

    print $fb->input('first_name');
    # <input id="person_first_name" name="person.first_name" type="text" value="J"/> 

    print $fb->errors_for('first_name');
    # <div>First Name is too short (minimum is 3 characters)</div> 

Although you can create a formbuilder instance directly as in the above example you might find it easier to use the export helper method "form_for" in Valiant::HTML::Form which encapsulates the display logic needed for creating the form tags. This builder creates form tag elements but not the actual form open and close tags.

DESCRIPTION

This class wraps an underlying data model and makes it easy to build HTML form elements based on the state of that model. Inspiration for this design come from Ruby on Rails Formbuilder as well as similar designs in the Phoenix Framework.

You can subclass this to future customize how your form elements display as well as to add more complex form elements for your templates.

Documentation here is basically API level, a more detailed tutorial will follow eventually but for now you'll need to review the source, test cases and example application bundled with this distribution for for hand holding.

Currently this is designed to work mostly with the Valiant model validation framework as well as the glue for DBIx:Class, DBIx:Class::Valiant, although I did take pains to try and make the API agnostic many of the test cases are assuming that stack and getting that integration working well is the primary use case for me. Thoughts and code to make this more stand alone are very welcomed.

ATTRIBUTES

This class defines the following attributes used in creating an instance.

model

This is the data model that the formbuilder inspects for field state and error conditions. This should be a model that does the API described here: "'REQUIRED MODEL API'" in Valiant::HTML::Form. Required but the API is pretty flexible (see docs).

Please note that my initial use case for this is using Valiant for validation and DBIx::Class as the model (via DBIx:Class::Valiant) so that combination has the most testing and examples. If you are using a different storage or validation setup you need to complete the API described. Please send test cases and pull requests to improve interoperability!

name

This is a string which is the internal name given to the model. This is used to set a namespace for form field name attributes and the default namespace for id attributes. Required.

options

A optional hashref of options used in form field generation. Some of these might become attributes in the future. Here's a list of the current options

index

The index of the formbuilder when it is a sub formbuilder with a parent and we are iterating over a collection.

child_index

When creating a sub formbuilder that is an element in a collection, this is used to pass the index value

builder

The package name of the current builder

parent_builder

The parent formbuilder instance to a sub builder.

include_id

Used to indicated that a sub formbuilder should add hidden fields indicating the storage ID for the current model.

namespace

The ID namespace; used to populate the namespace attribute.

as

Used to override how the class and ids are made for your forms.

index

The current index of a collection for which the current formbuilder is one item in.

namespace

Used to add a prefix to the ID for your form elements.

allow_method_names_outside_model

Default is false. Generally we expect method_name to be an actual method on the model and if its not we expect an exception. This helps to prevent typos from leading to unexpected results. However sometimes you may wish create a form field that has a name that isn't on the model but still respects the current namespace and index. These names would appear in the POST request body and could be used for things other than updating or creating a model.

skip_default_ids

Defaults to false. Generally we create an html id attribute for the field based on a convention which includes the model name, index and method name. Setting this to true prevents that so you should set id manually unless you don't want them. Please note that even if this is false, you can always override the id on a per field basis by setting it manually.

view

Optional. The view or template object that is using the formbuilder. If available can be used to influence how the HTML for controls are created.

Generally used to provide HTML escaping and safe string tagging methods that are compatible with your template system. For example Mojo::Template provides its own system for marking strings safe for template display. I you don't provide a view then we will use Valiant::HTML::SafeString for automatic HTML escaping. If you are not using a view or template system (for example like Template::Toolkit ) that does automatic escaping then the built in escaping features are probably fine.

If you provide a view it should provide the following API methods:

raw

given a string return a single tagged object which is marked as safe for display. Do not do any HTML escaping on the string. This is used when you want to pass strings straight to display and that you know is safe. Be careful with this to avoid HTML injection attacks.

safe

given a string return a single tagged object which is marked as safe for display. First HTML escape the string as safe unless its already been done (no double escaping).

safe_concat

Same as safe but instead works an an array of strings (or mix of strings and safe string objects) and concatenates them all into one big safe marked string.

html_escape

Given a string return string that has been HTML escaped.

read_attribute_for_view

Given an attribute name return the value that the view has defined for it.

attribute_for_view_exists

Given an attribute name return true if the view has defined a value for it.

Both raw, safe and safe_concat should return a 'tagged' object which is specific to your view or template system. However this object must 'stringify' to the safe version of the string to be displayed. See Valiant::HTML::SafeString for example API. We use <Valiant::HTML::SafeString> internally to provide safe escaping if you're view doesn't do automatic escaping, as many older template systems like Template Toolkit.

NOTE: In the future the view API might change so keep an eye on this spot.

METHODS

This class defines the following public instance methods.

form_has_errors

Display a message if the form has errors. This is a convenience method that you can use in your template to display a message if there are any errors in the form. This is useful if you want to display a message at the top of the form. You can also use this method to display a message at the top of a sub formbuilder if you are using sub formbuilders.

A form has errors if any of the fields has an error or if the model has errors.

    $fb->form_has_errors();
    $fb->form_has_errors(\%attrs);
    $fb->form_has_errors(\%attrs, $content);
    $fb->form_has_errors(\$content);
    $fb->form_has_errors(\&template);

Examples:

    # with default content
    $fb->form_has_errors;

    # with simple content
    $fb->form_has_errors('There were errors in the form. Please correct them.');

    # Simple content with attributes
    $fb->form_has_errors({ class=>'alert alert-danger', role=>'alert' },
      'There were errors in the form. Please correct them and try again.');

    # with complex content
    $fb->form_has_errors(sub ($self, $fb, $contact) {
      div +{ class=>'alert alert-danger', role=>'alert' }, [
        'There were errors in the form. Please correct them and try again.',
      ]
    });

If you pass a scalar as content, the scalar can be a string or a translation tag.

Note Please note this method doesn't show any of the model or field errors, it just shows a generic 'form has errors' message. You can either call the field or "model_errors" methods, or you can loop over errors in the custom complex content callback. Alternatively if you have model level errors for this object you might prefer to use the "model_errors" method with the show_message_on_field_errors option.

model_errors

    $fb->model_errors();
    $fb->model_errors(\%attrs);
    $fb->model_errors(\%attrs, \&template); # %attrs limited to 'max_errors' and 'show_message_on_field_errors'
    $fb->model_errors(\&template);

Display model level errors, either with a default or custom template. 'Model' errors are errors that are not associated with a model attribute in particular, but rather the model as a whole.

Arguments to this method are optional. "\%attrs" is a hashref which is passed to the tag builder to create any needed HTML attributes (such as class and style). "\&template" is a coderef that gets the @errors as an argument and you can use it to customize how the errors are displayed. Otherwise we use a default template that lists the errors with an HTML ordered list, or a div if there's only one error.

"\%attrs" can also contain two options that gives you some additional control over the display

max_errors

Don't display more than a certain number of errors

show_message_on_field_errors

Sometimes you want a global message displayed when there are field errors. Valiant doesn't add a model error if there's field errors (although it would be easy for you to add this yourself with a model validation) so this makes it easy to display such a message. If a string or translation tag then show that, if its a '1' the show the default message, which is "Form has errors" unless you overide it.

This can be a useful option when you have a long form and you want a user to know there's errors possibly off the browser screen.

Examples. Assume two model level errors "Trouble 1" and "Trouble 2":

    $fb->model_errors;
    # <ol><li>Trouble 1</li><li>Trouble 2</li></ol>

    $fb->model_errors({class=>'foo'});
    # <ol class="foo"><li>Trouble 1</li><li>Trouble 2</li></ol>

    $fb->model_errors({max_errors=>1});
    # <div>Trouble 1</div>

    $fb->model_errors({max_errors=>1, class=>'foo'})
    # <div class="foo">Trouble 1</div>

    $fb->model_errors({show_message_on_field_errors=>1})
    # <ol><li>Form has errors</li><li>Trouble 1</li><li>Trouble 2</li></ol>

    $fb->model_errors({show_message_on_field_errors=>"Bad!"})
    # <ol><li>Bad!</li><li>Trouble 1</li><li>Trouble 2</li></ol>

    $fb->model_errors(sub {
      my (@errors) = @_;
      join " | ", @errors;
    });
    # Trouble 1 | Trouble 2

label

    $fb->label($attribute)
    $fb->label($attribute, \%options)
    $fb->label($attribute, $content)
    $fb->label($attribute, \%options, $content) 
    $fb->label($attribute, \&content);   sub content { my ($translated_attribute) = @_;  ... }
    $fb->label($attribute, \%options, \&content);   sub content { my ( $translated_attribute) = @_;  ... }

Creates a HTML form element label with the given "\%options" passed to the tag builder to create HTML attributes and an optional "$content". If "$content" is not provided we use the human, translated (if available) version of the "$attribute" for the label content. Alternatively you can provide a template which is a subroutine reference which recieves the translated attribute as an argument. Examples:

    $fb->label('first_name');
    # <label for="person_first_name">First Name</label>

    $fb->label('first_name', {class=>'foo'});
    # <label class="foo" for="person_first_name">First Name</label>

    $fb->label('first_name', 'Your First Name');
    # <label for="person_first_name">Your First Name</label>

    $fb->label('first_name', {class=>'foo'}, 'Your First Name');
    # <label class="foo" for="person_first_name">Your First Name</label>

    $fb->label('first_name', sub {
      my $translated_attribute = shift;
      return "$translated_attribute ",
        $fb->input('first_name');
    });
    # <label for="person_first_name">
    #   First Name 
    #   <input id="person_first_name" name="person.first_name" type="text" value="John"/>
    # </label>

    $fb->label('first_name', +{class=>'foo'}, sub {
      my $translated_attribute = shift;
      return "$translated_attribute ",
        $fb->input('first_name');
    });
    # <label class="foo" for="person_first_name">
    #   First Name
    #   <input id="person_first_name" name="person.first_name" type="text" value="John"/>
    # </label>

errors_for

    $fb->errors_for($attribute)
    $fb->errors_for($attribute, \%options)
    $fb->errors_for($attribute, \%options, \&template)
    $fb->errors_for($attribute, \&template)

Similar to "model_errors" but for errors associated with an attribute of a model. Accepts the $attribute name, a hashref of \%options (used to set options controling the display of errors as well as used by the tag builder to create HTML attributes for the containing tag) and lastly an optional \&template which is a subroutine reference that received an array of the translated errors for when you need very custom error display. If omitted we use a default template displaying errors in an ordered list (if more than one) or wrapped in a div tag (if only one error).

\%options used for error display and which are not passed to the tag builder as HTML attributes:

max_errors

Don't display more than a certain number of errors

Assume the attribute 'last_name' has the following two errors in the given examples: "first Name is too short", "First Name contains non alphabetic characters".

    $fb->errors_for('first_name');
    # <ol><li>First Name is too short (minimum is 3 characters)</li><li>First Name contains non alphabetic characters</li></ol>

    $fb->errors_for('first_name', {class=>'foo'});
    # <ol class="foo"><li>First Name is too short (minimum is 3 characters)</li><li>First Name contains non alphabetic characters</li></ol>

    $fb->errors_for('first_name', {class=>'foo', max_errors=>1});
    # <div class="foo">First Name is too short (minimum is 3 characters)</div>

    $fb->errors_for('first_name', sub {
      my (@errors) = @_;
      join " | ", @errors;
    });
    # First Name is too short (minimum is 3 characters) | First Name contains non alphabetic characters

input

    $fb->input($attribute, \%options)
    $fb->input($attribute)

Create an input form tag using the $attribute's value (if any) and optionally passing a hashref of \%options which are passed to the tag builder to create HTML attributes for the input tag. Optionally add errors_classes which is a string that is appended to the class attribute when the $attribute has errors. Examples:

    $fb->input('first_name');
    # <input id="person_first_name" name="person.first_name" type="text" value="J"/>

    $fb->input('first_name', {class=>'foo'});
    # <input class="foo" id="person_first_name" name="person.first_name" type="text" value="J"/>

    $fb->input('first_name', {errors_classes=>'error'});
    # <input class="error" id="person_first_name" name="person.first_name" type="text" value="J"/>

    $fb->input('first_name', {class=>'foo', errors_classes=>'error'});
    # <input class="foo error" id="person_first_name" name="person.first_name" type="text" value="J"/>

Special \%options:

errors_classes

A string that is appended to the class attribute if the $attribute has errors (as defined by the model API)

password

    $fb->password($attribute, \%options)
    $fb->password($attribute)

Create a password HTML form field. Similar to "input" but sets the type to 'password' and also sets value to '' since generally you don't want to show the current password (and if you are doing the right thing and saving a 1 way hash not the plain text you don't even have it to show anyway).

Example:

    $fb->password('password');
    # <input id="person_password" name="person.password" type="password" value=""/>

    $fb->password('password', {class='foo'});
    # <input class="foo" id="person_password" name="person.password" type="password" value=""/>

    $fb->password('password', {class='foo', errors_classes=>'error'});
    # <input class="foo error" id="person_password" name="person.password" type="password" value=""/>

hidden

    $fb->hidden($attribute, \%options)
    $fb->hidden($attribute)

Create a hidden HTML form field. Similar to "input" but sets the type to 'hidden'.

    $fb->hidden('id');
    # <input id="person_id name="person.id" type="hidden" value="101"/>

    $fb->hidden('id', {class='foo'});
    # <input class="foo" id="person_id name="person.id" type="hidden" value="101"/>

text_area

    $fb->text_area($attribute);
    $fb->text_area($attribute, \%options);

Create an HTML text_area tag based on the attribute value and with optional \%options which is a a hashref passed to the tag builder for generating HTML attributes. Can also set errors_classes that will append a string of additional CSS classes when the $attribute has errors. Examples:

    $fb->text_area('comments');
    # <textarea id="person_comments" name="person.comments">J</textarea>

    $fb->text_area('comments', {class=>'foo'});
    # <textarea class="foo" id="person_comments" name="person.comments">J</textarea>

    $fb->text_area('comments', {class=>'foo', errors_classes=>'error'});
    # <textarea class="foo error" id="person_comments" name="person.comments">J</textarea>

Special \%options:

errors_classes

A string that is appended to the class attribute if the $attribute has errors (as defined by the model API)

checkbox

    $fb->checkbox($attribute);
    $fb->checkbox($attribute, \%options);
    $fb->checkbox($attribute, $checked_value, $unchecked_value);
    $fb->checkbox($attribute, \%options, $checked_value, $unchecked_value);

Generate an HTML form checkbox element with its state based on evaluating the value of $attribute in a boolean context. If $attribute is true then the checkbox will be checked. May also pass a hashref of \%options, which contain render instructions and HTML attributes used by the tag builder. "$checked_value" and "$unchecked_value" specify the values when the checkbox is checked or not (defaults to 1 for checked and 0 for unchecked, but $unchecked is ignored if option include_hidden is set to false; see below).

Special \%options:

errors_classes

A string that is appended to the class attribute if the $attribute has errors (as defined by the model API)

include_hidden

Defaults to true. Since the rules for an HTML form checkbox specify that if the checkbox is 'unchecked' then nothing is submitted. This can cause issues if you are expecting a submission that somehow indicates 'unchecked' For example you might have a status field boolean where unchecked should indicate 'false'. So by default we add a hidden field with the same name as the checkbox, with a value set to $unchecked_value (defaults to 0). In the case where the field is checked then you'll get two values for the same field name so you should have logic that in the case that field name is an array then take the last one (if you are using Plack::Request this is using Hash::MultiValue which does this by default; if you are using Catalyst you can use the use_hash_multivalue_in_request option or you can use something like Catalyst::TraitFor::Request::StructuredParameters which has options to help with this. If you are using Mojolicious then the param method works this way as well.

checked

A boolean value to indicate if the checkbox field is 'checked' when generated. By default a checkbox state is determined by the value of $attribute for the underlying model. You can use this to override (for example you might wish a checkbox default state to be checked when creating a new entry when it would otherwise be false).

Examples:

    $fb->checkbox('status');
    # <input name="person.status" type="hidden" value="0"/>
    # <input id="person_status" name="person.status" type="checkbox" value="1"/>

    $fb->checkbox('status', {class=>'foo'});
    # <input name="person.status" type="hidden" value="0"/>
    # <input class="foo" id="person_status" name="person.status" type="checkbox" value="1"/>

    $fb->checkbox('status', 'active', 'deactive');
    # <input name="person.status" type="hidden" value="deactive"/>
    # <input id="person_status" name="person.status" type="checkbox" value="active"/>

    $fb->checkbox('status', {include_hidden=>0});
    # <input id="person_status" name="person.status" type="checkbox" value="1"/>

    $person->status(1);
    $fb->checkbox('status', {include_hidden=>0});
    # <input checked id="person_status" name="person.status" type="checkbox" value="1"/>

    $person->status(0);
    $fb->checkbox('status', {include_hidden=>0, checked=>1});
    # <input checked id="person_status" name="person.status" type="checkbox" value="1"/>

    $fb->checkbox('status', {include_hidden=>0, errors_classes=>'err'});
    # <input class="err" id="person_status" name="person.status" type="checkbox" value="1"/>

radio_button

    $fb->radio_button($attribute, $value);
    $fb->radio_button($attribute, $value, \%options);

Generate an HTML input type 'radio', typically part of a group including 2 or more controls. Generated value attributes uses $value, the control is marked 'checked' when $value matches the value of $attribute (or you can override, see below). \%options are HTML attributes which are passed to the tag builder unless special as described below.

Special \%options:

errors_classes

A string that is appended to the class attribute if the $attribute has errors (as defined by the model API)

checked

A boolean which determines if the input radio control is marked 'checked'. Used if you want to override the default.

Examples:

    # Example radio group

    $person->type('admin');

    $fb->radio_button('type', 'admin');
    $fb->radio_button('type', 'user');
    $fb->radio_button('type', 'guest');

    #<input checked id="person_type_admin" name="person.type" type="radio" value="admin"/>
    #<input id="person_type_user" name="person.type" type="radio" value="user"/>
    #<input id="person_type_guest" name="person.type" type="radio" value="guest"/>
    
    # Example \%options

    $fb->radio_button('type', 'guest', {class=>'foo', errors_classes=>'err'});
    # <input class="foo err" id="person_type_guest" name="person.type" type="radio" value="guest"/>

    $fb->radio_button('type', 'guest', {checked=>1});
    # <input checked id="person_type_guest" name="person.type" type="radio" value="guest"/>

date_field

    $fb->date_field($attribute);
    $fb->date_field($attribute, \%options);

Generates a 'type' date HTML input control. Used when your $attribute value is a DateTime object to get proper string formatting. Although the date type is considered HTML5 you can use this for older HTML versions as well when you need to get the object formatting (you just don't get the date HTML controls).

When the $attribute value is a DateTime object (or actually is any object) we call '->ymd' to stringify the object to the expected format. '\%options' as in the input control are passed to the tag builder to create HTML attributes on the input tag with the exception of the specials ones already documented (such as errors_classes) and the following special \%options

min
max

When these are DateTime objects we stringify using ->ymd to get the expected format; otherwise they are passed as is to the tag builder.

Examples:

    $person->birthday(DateTime->new(year=>1969, month=>2, day=>13));

    $fb->date_field('birthday');
    # <input id="person_birthday" name="person.birthday" type="date" value="1969-02-13"/>

    $fb->date_field('birthday', {class=>'foo', errors_classes=>'err'});
    # <input class="foo err" id="person_birthday" name="person.birthday" type="date" value="1969-02-13"/>

    $fb->date_field('birthday', +{
      min => DateTime->new(year=>1900, month=>1, day=>1),
      max => DateTime->new(year=>2030, month=>1, day=>1),
    });
    #<input id="person_birthday" max="2030-01-01" min="1900-01-01" name="person.birthday" type="date" value="1969-02-13"/>

datetime_local_field

time_field

Like "date_field" but sets the input type to datetime-local or time respectively and formats any <DateTime> values with "->strftime('%Y-%m-%dT%T')" (for datetime-local) or either "->strftime('%H:%M')" or "->strftime('%T.%3N')" for time depending on the \%options include_seconds, which defaults to true).

Examples:

    $person->due(DateTime->new(year=>1969, month=>2, day=>13, hour=>10, minute=>45, second=>11, nanosecond=> 500000000));

    $fb->datetime_local_field('due');
    # <input id="person_due" name="person.due" type="datetime-local" value="1969-02-13T10:45:11"/>

    $fb->time_field('due');
    # <input id="person_due" name="person.due" type="time" value="10:45:11.500"/>

    $fb->time_field('due', +{include_seconds=>0});
    # <input id="person_due" name="person.due" type="time" value="10:45"/>

submit

    $fb->submit;
    $fb->submit(\%options);
    $fb->submit($value);
    $fb->submit($value, \%options);

Create an HTML submit input tag with a meaningful default value based on the model name and its state in storage (if supported by the model). Will also look up the following two translation tag keys:

    "formbuilder.submit.@{[ $self->name ]}.${key}"
    "formbuilder.submit.${key}"

Where $key is by default 'submit' and if the model supports 'in_storage' its either 'update' or 'create' depending on if the model is new or already existing.

Examples:

    $fb->submit;
    # <input id="commit" name="commit" type="submit" value="Submit Person"/>

    $fb->submit('Login', {class=>'foo'});
    # <input class="foo" id="commit" name="commit" type="submit" value="Login"/>

button

    $fb->button($name, \%attrs, \&block)
    $fb->button($name, \%attrs, $content)
    $fb->button($name, \&block)
    $fb->button($name, $content)

Create a button tag with custom attibutes and content. Content be be a string or a coderef if you need to do complex layout.

Useful to create submit buttons with fancy formatting or when you need a button that submits in the form namespace.

Examples:

    $person->type('admin');

    $fb->button('type');
    # <button id="person_type" name="person.type" type="submit" value="admin">Button</button>

    $fb->button('type', {class=>'foo'});
    # <button class="foo" id="person_type" name="person.type" type="submit" value="admin">Button</button>

    $fb->button('type', "Press Me")
    # <button id="person_type" name="person.type" type="submit" value="admin">Press Me</button>

    $fb->button('type', sub { "Press Me" })
    # <button id="person_type" name="person.type" type="submit" value="admin">Press Me</button>

legend

    $fb->legend;
    $fb->legend(\%options);
    $fb->legend($content);
    $fb->legend($content, \%options);
    $fb->legend(\&template);
    $fb->legend(\%options, \&template);

Create an HTML Form legend element with default content that is based on the model name. Accepts \%options which are passed to the tag builder and used to create HTML element attributes. You can override the content with either a $content string or a \&template coderef (which will receive the default content translation as its first argument).

The default content will be based on the model name and can be influenced by its storage status if in_storage is supplied by the model. We attempt to lookup the content string via the following translation tags (if the body supports ->i18n):

    "formbuilder.legend.@{[ $self->name ]}.${key}"
    "formbuilder.legend.${key}"

Where $key is 'new' if the model doesn't support in_storage else it's either 'update' or 'create' based on if the current model is already in storage (update) or its new and needs to be created.

Examples:

    $fb->legend;
    # <legend>New Person</legend>

    $fb->legend({class=>'foo'});
    # <legend class="foo">New Person</legend>

    $fb->legend("Person");
    # <legend>Person</legend>

    $fb->legend("Persons", {class=>'foo'});
    # <legend class="foo">Persons</legend>

    $fb->legend(sub { shift . " Info"});
    # <legend>New Person Info</legend>

    $fb->legend({class=>'foo'}, sub {"Person"});
    # <legend class="foo">Person</legend>

legend_for

    $fb->legend_for($attr);
    $fb->legend_for($attr, \%options);

Creates an HTML legend tags with it's content set to the human translated name of the given $attribute. Allows you to pass some additional HTML attributes to the legend tag. Examples:

    $fb->legend_for('status')
    # <legend id="status_legend" >Status</legend>
    
    $fb->legend_for('status', {class=>'foo'})
    # <legend id="status_legend" class="foo" >Status</legend>

fields_for

    $fb->fields_for($attribute, sub {
      my ($nested_fb, $model) = @_;
    });

    $fb->fields_for($attribute, \%options, sub {
      my ($nested_fb, $model) = @_;
    });

    # With a 'finally' block when $attribute is a collection

    $fb->fields_for($attribute, sub {
      my ($nested_fb, $model) = @_;
    }, sub {
      my ($nested_fb, $new_model) = @_;
    });

Used to create sub form builders under the current one for nested models (either a collection of models or a single model.) This sub form builder will be passed as the first argument to the enclosing subref and will encapsulate any indexing or namespacing; its model will be set to the sub model. You also get a second argument which is the sub model for ease of access. Note that if the $attribute refers to a collection then $model will be set to the current item model of that collection.

When the $attribute refers to a collection the collection object must provide a next method which should iterate thru the collection in the order desired and return undef to indicate all records have been rolled thru. This collection object may also implement a reset method to return the index to the start of the collection (which will be called after the final record is processed) and a build method which should return a new empty record (required if you want a finally block as described below).

Please see Valiant::HTML::Util::Collection for example. NOTE: If you supply an arrayref instead of a collection object, we will build one using Valiant::HTML::Util::Collection automatically. This behavior might change in the future so it would be ideal to not rely on it.

If the $attribute is a collection you may optionally add a second coderef template which is called after the collect has been fully iterated thru and it recieves a sub formbuilder with a new blank model as an argument. This finally block is always called, even if the collection is empty so it can he used to generate a blank entry for adding new items to the collection (for example) or for any extra code or field controls that you want under the sub model namespace.

Available \%options:

builder

The class name of the formbuilder. Defaults to Valiant::HTML::FormBuilder or whatever the current builder is (if overridden in the parent).

namespace

The ID namespace. Will default to the parent formbuilder namespace if there is one.

child_index

The index of the sub object. Can be a coderef. Used if you need explicit control over the index generated

include_id

Defaults to true. If the sub model does in_storage and primary_columns then add hidden form fields with those IDs to the sub model namespace. Often needed to properly match a record to its existing state in storage (such as a database). Not sure why you'd want to turn this off but the option is a carry over from Rails so I presume there is a use case.

id

Override the ID namespace for th sub model.

index

Explicitly override the index of the sub model.

Example of an attribute that refers to a nested object.

    $person->profile(Local::Profile->new(zip=>'78621', address=>'ab'));

    $fb->fields_for('profile', sub {
      my $fb_profile = shift;
      return  $fb_profile->input('address'),
              $fb_profile->errors_for('address'),
              $fb_profile->input('zip');
    });

    # <input id="person_profile_address" name="person.profile.address" type="text" value="ab"/>
    # <div>Address is too short (minimum is 3 characters)</div>
    # <input id="person_profile_zip" name="person.profile.zip" type="text" value="78621"/>

Example of an attribute that refers to a nested collection object (and with a "finally block")

    $person->credit_cards([
      Local::CreditCard->new(number=>'234234223444', expiration=>DateTime->now->add(months=>11)),
      Local::CreditCard->new(number=>'342342342322', expiration=>DateTime->now->add(months=>11)),
      Local::CreditCard->new(number=>'111112222233', expiration=>DateTime->now->subtract(months=>11)),  # An expired card
    ]);

    $fb->fields_for('credit_cards', sub {
      my $fb_cc = shift;
      return  $fb_cc->input('number'),
              $fb_cc->date_field('expiration'),
              $fb_cc->errors_for('expiration');
    }, sub {
      my $fb_finally = shift;
      return  $fb_finally->button('add', +{value=>1}, 'Add a New Credit Card');
    });

    # <input id="person_credit_cards_0_number" name="person.credit_cards[0].number" type="text" value="234234223444"/>
    # <input id="person_credit_cards_0_expiration" name="person.credit_cards[0].expiration" type="date" value="2023-01-23"/>
    # <input id="person_credit_cards_1_number" name="person.credit_cards[1].number" type="text" value="342342342322"/>
    # <input id="person_credit_cards_1_expiration" name="person.credit_cards[1].expiration" type="date" value="2023-01-23"/>
    # <input id="person_credit_cards_2_number" name="person.credit_cards[2].number" type="text" value="111112222233"/>
    # <input id="person_credit_cards_2_expiration" name="person.credit_cards[2].expiration" type="date" value="2021-03-23"/>
    # <div>Expiration chosen date can&#39;t be earlier than 2022-02-23</div>
    # <button id="person_credit_cards_3_add" name="person.credit_cards[3].add" type="submit" value="1">Add a New Credit Card</button>

select

    $fb->select($attribute_proto, \@options, \%options)
    $fb->select($attribute_proto, \@options)
    $fb->select($attribute_proto, \%options, \&template)
    $fb->select($attribute_proto, \&template)

Where $attribute_proto is one of:

    $attribute                # A scalar value which is an attribute on the underlying $model
    { $attribute => $method } # A hashref composed of an $attribute on the underlying $model
                              # which returns a sub model or a collection of sub models
                              # and a $method to be called on the value of that sub model (
                              # or on each item sub model if the $attribute is a collection).

Used to create a select tag group with option tags. \@options can be anything that can be accepted by "options_for_select" in Valiant::HTML::FormTags. The value(s) of $attribute_proto are automatically marked as selected.

Since this is built on top if select_tag \%options can be anything supported by that method. See "select_tag" in Valiant::HTML::FormTags for more. In addition we have the following special handling for \%options:

selected
disabled

Mark C\<option> tags as selected or disabled. If you manual set selected then we ignore the value of $attribute (or @values when $attribute is a collection)

unselected_value

The value to set the hidden 'unselected' field to. No default value. See 'include_hidden' for details.

include_hidden

Defaults to true for 'multiple' and false for single type selects.

The rules for an HTML form select field specify that if the no option is 'selected' then nothing is submitted. This can cause issues if you are expecting a submission that somehow indicates 'nothing selected' means to unset some settings. So we do one of two things when 'include_hidden' is true. When the select is a simple 'single value' select (not multiple) we add a hidden field with the same name as the select name but indexed to 0 so its always the first value; its value is whatever you set 'unselected_value' to. If you don't set 'unselected_value' this hidden field is NOT created. If you are using Plack::Request or Mojolicious (or using Catalyst with use_hash_multivalue_in_request option set to true, or something like Catalyst::TraitFor::Request::StructuredParameters) then the last value of an array body parameter will be returned which will let you choose between a default value or an actual returned value. Example:

    # $fb->select('state_ids', [map { [$_->label, $_->id] } $roles_collection->all], +{include_hidden=>1, unselected_value=>-1} );
    # <input id="person_state_ids_hidden" name="person.state_ids[0]" type="hidden" value="-1"/>
    # <select id="person_state_ids" multiple name="person.state_ids[]">
    #   <option selected value="1">user</option>
    #   <option value="2">admin</option>
    #   <option selected value="3">guest</option>
    # </select>

If you've set the 'multiple' attribute to true, or we detect that multiple values are intended (either when the form value of the field is an arrayref or a collection) indicting your select drop list allows one to choose more than one option, we add a hidden field '_nop' at index 0 which you will need to treat at the signal for 'this means unset. If you are using this with DBIx:Class::Valiant then that code will automatically handle this for you. Otherwise you'll need to handle it manually or add code to detect that there is no form submission value under that name. If you don't want this behavior you can manually turn it off be explicitly setting 'include_hidden' to false.

Optionally you can provide a \&template which should return option tags. This coderef will recieve the $model, $attribute and an array of @selected values based on the $attribute.

Examples:

    $fb->select('state_id', [1,2,3], +{class=>'foo'} );
    # <select class="foo" id="person_state_id" name="person.state_id">
    #   <option selected value="1">1</option>
    #   <option value="2">2</option>
    #   <option value="3">3</option>
    # </select>

    $fb->select('state_id', [1,2,3], +{selected=>[3], disabled=>[1]} );
    # <select id="person_state_id" name="person.state_id">
    #   <option disabled value="1">1</option>
    #   <option value="2">2</option>
    #   <option selected value="3">3</option>
    # </select>

    $fb->select('state_id', [map { [$_->name, $_->id] } $states_collection->all], +{include_blank=>1} );
    # <select id="person_state_id" name="person.state_id">
    #   <option label=" " value=""></option>
    #   <option selected value="1">TX</option>
    #   <option value="2">NY</option>
    #   <option value="3">CA</option>
    # </select>

    $fb->select('state_id', sub {
      my ($model, $attribute, $value) = @_;
      return map {
        my $selected = $_->id eq $value ? 1:0;
        option_tag($_->name, +{class=>'foo', selected=>$selected, value=>$_->id}); 
      } $states_collection->all;
    });
    # <select id="person_state_id" name="person.state_id">
    #   <option class="foo" selected value="1">TX</option>
    #   <option class="foo" value="2">NY</option>
    #   <option class="foo" value="3">CA</option>
    # </select>

Examples when $attribute is a collection:

    $fb->select({roles => 'id'}, [map { [$_->label, $_->id] } $roles_collection->all]), 
    # <input id="person_roles_id_hidden" name="person.roles[0]._nop" type="hidden" value="1"/>
    # <select id="person_roles_id" multiple name="person.roles[].id">
    #   <option selected value="1">user</option>
    #   <option selected value="2">admin</option>
    #   <option value="3">guest</option>
    # </select>

Please note when the $attribute is a collection we add a hidden field to cope with case when no items are selected, you'll need to write form processing code to mark and notice the _nop field.

collection_select

    $fb->collection_select($attribute_proto, $collection, $value_method, $text_method, \%options);
    $fb->collection_select($attribute_proto, $collection, $value_method, $text_method);
    $fb->collection_select($attribute_proto, $collection);

Where $attribute_proto is one of:

    $attribute                # A string which is an attribute on the underlying $model
                              # that returns a scalar value.
    { $attribute => $method } # A hashref composed of an $attribute on the underlying $model
                              # which returns a sub model or a collection of sub models
                              # and a $method to be called on the value of that sub model (
                              # or on each item sub model if the $attribute is a collection).

Similar to "select" but works with a $collection instead of delineated options. The collection can be an actual collection object, or the string name of a method on the model which provides the actual collection objection. Its a type of shortcut to reduce boilerplate at the expense of some flexibility ( if you need that you'll need to use "select"). Examples:

    $fb->collection_select('state_id', $states_collection, id=>'name');
    # <select id="person.state_id" name="person.state_id">
    #   <option selected value="1">TX</option>
    #   <option value="2">NY</option>
    #   <option value="3">CA</option>
    # </select>

    $fb->collection_select('state_id', $states_collection, id=>'name', {class=>'foo', include_blank=>1});
    # <select class="foo" id="person.state_id" name="person.state_id">
    #   <option label=" " value=""></option>
    #   <option selected value="1">TX</option>
    #   <option value="2">NY</option>
    #   <option value="3">CA</option>
    # </select>

    $fb->collection_select('state_id', $states_collection, id=>'name', {selected=>[3], disabled=>[1]});
    # <select id="person.state_id" name="person.state_id">
    #   <option disabled value="1">TX</option>
    #   <option value="2">NY</option>
    #   <option selected value="3">CA</option>
    # </select>

    is $fb->collection_select({roles => 'id'}, $roles_collection, id=>'label');
    # <input id="person_roles_id_hidden" name="person.roles[0]._nop" type="hidden" value="1"/>
    # <select id="person_roles_id" multiple name="person.roles[].id">
    #   <option selected value="1">user</option>
    #   <option selected value="2">admin</option>
    #   <option value="3">guest</option>
    # </select>

Please note when the $attribute is a collection value we add a hidden field to allow you to send a signal to the form processor that this namespace contains no records. Otherwise the form will just send nothing. If you have a custom way to handle this you can disable the behavior if you wish by explicitly setting include_hidden=>0

collection_checkbox

    $fb->collection_checkbox({$attribute=>$value_method}, $collection, $value_method, $text_method, \%options);
    $fb->collection_checkbox({$attribute=>$value_method}, $collection, $value_method, $text_method);
    $fb->collection_checkbox({$attribute=>$value_method}, $collection);
    $fb->collection_checkbox({$attribute=>$value_method}, $collection, $value_method, $text_method, \%options, \&template);
    $fb->collection_checkbox({$attribute=>$value_method}, $collection, $value_method, $text_method, \&template);
    $fb->collection_checkbox({$attribute=>$value_method}, $collection, \&template);

Create a checkbox group for a collection attribute

Examples:

Where the $attribute roles refers to a collection of sub models, each of which provides a method id which is used to fetch a matching value and $roles_collection refers to the full set of available roles which can be added or removed from the parent model.

In these examples $collection and $roles_collection can be either a collection object or a string which is the method name on the current model which provides the collection.

    $fb->collection_checkbox({roles => 'id'}, $roles_collection, id=>'label'); 
    # <div id="person_roles">
    #   <input id="person_roles_hidden" name="person.roles" type="hidden" value="{'_nop':1}"/>
    #   <label for="person_roles_1">user</label>
    #   <input checked id="person_roles_1" name="person.roles" type="checkbox" value="{'id':1}"/>
    #   <label for="person_roles_2">admin</label>
    #   <input checked id="person_roles_2" name="person.roles" type="checkbox" value="{'id':2}"/>
    #   <label for="person_roles_3">guest</label>
    #   <input id="person_roles_3" name="person.roles" type="checkbox" value="{'id':3}"/>
    # </div>

Please note when the $attribute is a collection value we add a hidden field to allow you to send a signal to the form processor that this namespace contains no records. Otherwise the form will just send nothing. If you have a custom way to handle this you can disable the behavior if you wish by explicitly setting include_hidden=>0

If you have special needs for formatting or layout you can override the default template with a coderef that will receive a special type of formbuilder localized to the current value (an instance of Valiant::HTML::FormBuilder::Checkbox):

    $fb->collection_checkbox({roles => 'id'}, $roles_collection, id=>'label', sub {
      my $fb_roles = shift;
      return  $fb_roles->checkbox({class=>'form-check-input'}),
              $fb_roles->label({class=>'form-check-label'});
    });

    # <div id="person_roles">
    #   <input id="person_roles_hidden" name="person.roles" type="hidden" value="{'_nop':1}"/>
    #   <label for="person_roles_1" class="form-check-label">user</label>
    #   <input checked class="form-check-input" id="person_roles_1" name="person.roles" type="checkbox" value="{'id':1}"/>
    #   <label for="person_roles_2" class="form-check-label">admin</label>
    #   <input checked class="form-check-input" id="person_roles_2" name="person.roles" type="checkbox" value="{'id':2}"/>
    #   <label for="person_roles_3" class="form-check-label">guest</label>
    #   <input  class="form-check-input" id="person_roles_3" name="person.roles" type="checkbox" value="{'id':3}"/>
    # </div>

In addition to overriding checkbox and label to already contain value and state (if its checked or not) information. This special builder contains some additional methods of possible use, you should see the documentation of Valiant::HTML::FormBuilder::Checkbox for more.

If provided %options is a hashref of the following optional values

include_hidden

Defaults to whatever the method default_collection_checkbox_include_hidden returns. In the core code this returns true. If true will include a hidden field set to the name of the collection, which is uses to indicate 'no checked values' since HTML will send nothing by default if there's no checked values. It will add this hidden field for each checkbox item to represent the 'none checked' value.

builder

Defaults to the values of DEFAULT_COLLECTION_CHECKBOX_BUILDER method. In core code this is Valiant::HTML::FormBuilder::Checkbox. Overide if you need to make a custom builder (tricky work).

collection_radio_buttons

    $fb->collection_radio_buttons($attribute, $collection, $value_method, $text_method, \%options);
    $fb->collection_radio_buttons($attribute, $collection, $value_method, $text_method);
    $fb->collection_radio_buttons($attribute, $collection);
    $fb->collection_radio_buttons($attribute, $collection, $value_method, $text_method, \%options, \&template);
    $fb->collection_radio_buttons($attribute, $collection, $value_method, $text_method, \&template);
    $fb->collection_radio_buttons($attribute, $collection, \&template);

A collection of radio buttons. Similar to \collection_checkbox but used one only one value is permitted. Example:

    $fb->collection_radio_buttons('state_id', $states_collection, id=>'name');
    # <input id="person_state_id_hidden" name="person.state_id" type="hidden" value=""/>
    # <label for="person_state_id_1">TX</label>
    # <input checked id="person_state_id_1_1" name="person.state_id" type="radio" value="1"/>
    # <label for="person_state_id_2">NY</label>
    # <input id="person_state_id_2_2" name="person.state_id" type="radio" value="2"/>
    # <label for="person_state_id_3">CA</label>
    # <input id="person_state_id_3_3" name="person.state_id" type="radio" value="3"/>

Please note when the $attribute is a collection value we add a hidden field to allow you to send a signal to the form processor that this namespace contains no records. Otherwise the form will just send nothing. If you have a custom way to handle this you can disable the behavior if you wish by explicitly setting include_hidden=>0

If you have special needs for formatting or layout you can override the default template with a coderef that will receive a special type of formbuilder localized to the current value (an instance of Valiant::HTML::FormBuilder::RadioButton):

    $fb->collection_radio_buttons('state_id', $states_collection, id=>'name', sub {
      my $fb_states = shift;
      return  $fb_states->radio_button({class=>'form-check-input'}),
              $fb_states->label({class=>'form-check-label'});  
    });
    # <div id='person_state_id'>
    #   <input id="person_state_id_hidden" name="person.state_id" type="hidden" value=""/>
    #   <input checked class="form-check-input" id="person_state_id_1_1" name="person.state_id" type="radio" value="1"/>
    #   <label class="form-check-label" for="person_state_id_1">TX</label>
    #   <input class="form-check-input" id="person_state_id_2_2" name="person.state_id" type="radio" value="2"/>
    #   <label class="form-check-label" for="person_state_id_2">NY</label>
    #   <input class="form-check-input" id="person_state_id_3_3" name="person.state_id" type="radio" value="3"/>
    #   <label class="form-check-label" for="person_state_id_3">CA</label>
    # </div>

In addition to overriding radio_button and label to already contain value and state (if its checked or not) information. This special builder contains some additional methods of possible use, you should see the documentation of Valiant::HTML::FormBuilder::RadioButton for more.

Please note that the generated radio inputs will be wrapped in a containing div tag. You can change this tag using the container_tag option. For example:

    $fb->collection_radio_buttons('state_id', $states_collection, id=>'name', +{container_tag=>'span'}, sub {
      my $fb_states = shift;
      return  $fb_states->radio_button({class=>'form-check-input'}),
              $fb_states->label({class=>'form-check-label'});  
    });

Here's all the values for the '%options' argument. Any options that are not one of these will be passed to the container tag as html attributes:

checked_value

This is the current value of the attribute. By default its the attribute value (via \tag_value_for_attribute) but you can override as needed.

include_hidden.

Defaults to true. The value returned if you don't check one of the radio buttons.

container_tag

The tag that contains the generated radio buttons.

builder

The builder subclass used in the radio input generator.

radio_buttons

    $fb->radio_buttons($attribute, \@options, \%options);
    $fb->radio_buttons($attribute, \@options, \%options, \&template);
    $fb->radio_buttons($attribute, \@options);
    $fb->radio_buttons($attribute, \@options, \&template);

Similar to "collection_radio_buttons" but takes an arrayref of label / values instead of a collection. Useful when you have a list of radio buttons (like from an ENUM) but you don't want to list each radio separately. Example:

    $fb_profile->radio_buttons('status', [[Pending=>'pending'],[Active=>'active'],[Inactive=>'inactive']]);

    # <input id="person_profile_status_hidden" name="person.profile.status" type="hidden" value="">
    # <input id="person_profile_status_pending_pending" name="person.profile.status" type="radio" value="pending">
    # <label for="person_profile_status_pending">Pending</label>
    # <input checked="" id="person_profile_status_active_actie" name="person.profile.status" type="radio" value="active">
    # <label for="person_profile_status_active">Active</label>
    # <input id="person_profile_status_inactive_inactive" name="person.profile.status" type="radio" value="inactive">
    # <label for="person_profile_status_inactive">Inactive</label>

Supports using a template subroutine reference (like "collection_radio_buttons") when you need to be fussy about style and positioning.

THEMING

You can add a method called default_theme to your custom form builder sub class to return a hashref of default attributes for the various form elements. For example:

    package Example::FormBuilder;

    use Moo;
    use Example::Syntax;

    extends 'Valiant::HTML::FormBuilder';

    sub default_theme($self) {
      return +{ 
        errors_for => +{ class=>'invalid-feedback' },
        label => +{ class=>'form-label' },
        input => +{ class=>'form-control', errors_classes=>'is-invalid' },
        date_field => +{ class=>'form-control', errors_classes=>'is-invalid' },
        password => +{ class=>'form-control', errors_classes=>'is-invalid' },
        submit => +{ class=>'btn btn-lg btn-success btn-block' },
        button => +{ class=>'btn btn-lg btn-primary btn-block' },
        text_area => +{ class=>'form-control' },
        checkbox => +{ class=>'form-check-input', errors_classes=>'is-invalid' },
        collection_radio_buttons => +{errors_classes=>'is-invalid'},
        collection_checkbox => +{errors_classes=>'is-invalid'},
        collection_select => +{class=>'form-control', errors_classes=>'is-invalid'},
        select => +{class=>'form-control', errors_classes=>'is-invalid'},
        radio_buttons => +{errors_classes=>'is-invalid'},
        radio_button => +{class=>'custom-control-input', errors_classes=>'is-invalid'},
        model_errors => +{ class=>'alert alert-danger', role=>'alert' },
        form_has_errors => +{ class=>'alert alert-danger', role=>'alert' },
        attributes => {
          password => {
            password => { autocomplete=>'new-password' }
          }
        },
      };
    }

In the above example you set class defaults for most of the form elements based on the method name. In addition you can set specific attributes for specific model attributes (as in the 'password' attribute at the end of the last example.

Then you you call a method like text_field it will automatically add the default attributes for that element. For example:

    $fb->input('name');
    # <input class="form-control" id="person_name" name="person.name" type="text" value="">

If you are using a CSS framework you can use this to setup default (but overridable) classes for the various form elements. For example:

    $fb->input('name', {class=>'form-control form-control-lg'});
    # <input class="form-control form-control-lg" id="person_name" name="person.name" type="text" value="">

You can instead add a method called formbuilder_theme to your view class which does the same thing but allows you to make local view specific customatizations. If you use both the formbuilder default theme is added first and the view theme would override it.

Please note that you are not limited to passing HTML attributes here, you an pass anything that is a valid attribute for the form field you are generating.

NOTE: I'm still working out some of the rules around how we merge or override the various themes so if you go wild here you will need to follow the release notes for following versions carefully. I consider theming a beta feature subject to breaking changes if that's what I need to do to fix bugs or make it more flexible.

1;

SEE ALSO

Valiant

AUTHOR

See Valiant

COPYRIGHT & LICENSE

See Valiant

2 POD Errors

The following errors were encountered while parsing the POD:

Around line 2218:

Unknown directive: =over4

Around line 2220:

'=item' outside of any '=over'