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.

METHODS

This class defines the following public instance methods.

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.

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>

fields_for

    $fb->fields_for($attribute, sub {
      my $nested_$fb = shift;
    });

    $fb->fields_for($attribute, \%options, sub {
      my $nested_$fb = shift;
    });

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

    $fb->fields_for($attribute, sub {
      my $nested_$fb = shift;
    }, sub {
      my $finally_$fb = shift;
    });

Used to create sub form builders under the current one for nested models (either a collection of models or a single model.)

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)

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. 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.

    $fb->collection_checkbox({roles => 'id'}, $roles_collection, id=>'label'); 
    # <input id="person_roles_0__nop" name="person.roles[0]._nop" type="hidden" value="1"/>
    # <label for="person_roles_1_id">user</label>
    # <input checked id="person_roles_1_id" name="person.roles[1].id" type="checkbox" value="1"/>
    # <label for="person_roles_2_id">admin</label>
    # <input checked id="person_roles_2_id" name="person.roles[2].id" type="checkbox" value="2"/>
    # <label for="person_roles_3_id">guest</label>
    # <input id="person_roles_3_id" name="person.roles[3].id" type="checkbox" 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::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'});
    });

    # <input id="person_roles_4__nop" name="person.roles[4]._nop" type="hidden" value="1"/>
    # <input checked class="form-check-input" id="person_roles_5_id" name="person.roles[5].id" type="checkbox" value="1"/>
    # <label class="form-check-label" for="person_roles_5_id">user</label>
    # <input checked class="form-check-input" id="person_roles_6_id" name="person.roles[6].id" type="checkbox" value="2"/>
    # <label class="form-check-label" for="person_roles_6_id">admin</label>
    # <input class="form-check-input" id="person_roles_7_id" name="person.roles[7].id" type="checkbox" value="3"/>
    # <label class="form-check-label" for="person_roles_7_id">guest</label>

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.

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'});  
    });
    # <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>

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.

SEE ALSO

Valiant

AUTHOR

See Valiant

COPYRIGHT & LICENSE

See Valiant