Hubble, Bubble, Toil and Trouble: Catalyst, Bootstrap and HTML::FormFu

I thought I would share a little trick I use to get these three complex and idiosyncratic frameworks to play nice with each other.

Catalyst and HTML::FormFu are a powerful combination that allows you to tie the form displayed by your view to the form processed by your controller. This direct link means: 1) your field names in your generated HTML will always match the field names used in form processing, 2) default and redisplay field values set by your controller will always match up with the values displayed by the view, and 3) any constraint or validation issues detected by the form processing logic in your controller can be directly associated with the fields displayed by your view. Without this link, keeping a form defined in our view in sync with the form defined by our controller is a large source of potential bugs.

The problematic part of our potion is the combination of HTML::FormFu and Bootstrap. HTML::FormFu is a very powerful and elegant framework for managing forms. It is also very complex and can take some real time and effort to master. It can be a real challenge to get the forms generated by HTML::FormFu to be marked up exactly the way you want. This is problematic when combined with display frameworks like Bootstrap, which can be very fussy about their required markup.

The trick to getting this all to work is best show by example.

Lets start with the following HTML::FormFu form definition:

elements => [
    {
        name => 'min_amount',
        type => 'Text',
    },
    {
        name        => 'max_amount',
        type        => 'DollarAmount',
        constraints => [
            {
                type    => 'GreaterThan',
                others  => 'min_amount',
                message => 'the maximum amount must be greater than the minimum',
            },
        ],
    },
    {
        name  => 'search',
        type  => 'Submit',
    },
]

The corresponding form can be constructed and processed by a Catalyst controller in a number of ways. Ultimately we end up with a form object on the stash which stringifies to an HTML form. This can be use directly in our Template Toolkit view:

[% form %]

The resulting HTML, although lean, is going to be a challenge to style and layout with Bootstrap:

<form action="/my_form" method="post">
    <div class="text">
        <input name="min_amount" type="text" />
    </div>
    <div class="dollaramount">
        <input name="max_amount" type="text" />
    </div>
    <div class="submit">
        <input name="search" type="submit" />
    </div>
</form>

To be fair HTML::FormFu does have a large number of facilities for modifying the generated markup. However, in my experience, they add a lot of complexity to an already complex framework soup. They can also require embedding a lot of display related information in the controller which is not ideal.

An alternative is to construct the form display piece-wise.

First, we strip most of the formating logic from our HTML::FormFu form using the new ‘layout’ feature, and add some Bootstrap classes:

default_args => {
    elements => {
        Field => {
            layout => [ 'field' ],
            attrs  => { class => 'form-control' },
        },
        Submit => {
            attrs => { class => 'form-control btn btn-primary' },
        },
    },
}

(See HTML::FormFu::Role::Element::Field for the gory details of layout)

Now we can markup our form so that it works with Bootstrap:

<form class="form" action=[% form.action %] >
    <div class="row">
        <div class="col-md-2">Lead Amount</div>
        <div class="form-group col-md-4">
            <label for="min_date" class="sr-only">Minimum</label>
            <div class="input-group">
                <div class="input-group-addon">$</div>

                [% form.get_element('min_amount').placeholder('Minimum') %]

            </div>
        </div>
        <div class="form-group col-md-4">
            <label for="max_date" class="sr-only">Maximum</label>
            <div class="input-group">
                <div class="input-group-addon">$</div>

                [% form.get_element('max_amount').placeholder('Maximum') %]

            </div>
        </div>

        <div class="form-group col-md-2">

            [% form.get_element('search').value('Search Leads') %]

        </div>
    </div>

    <div class="row">
        <div class="col col-md-10 col-md-offset-2 text-danger">

        [% FOR error IN form.get_errors %]
            [% error.message | html_line_break %]
        [% END %]

        </div>
    </div>

</form>

The trick here is to note that the form object stringifies recursively. If you pick out an element of the form object, it will stringify to the corresponding HTML field.

[% form.get_element('search') %]

You can also modify the element in place before the stringification takes place:

[% form.get_element('search').value('Search') %]

The result looks something like this:

form-display.png

Magic!

This gives us a much better separation of display and processing concerns. It is now much easier for me to pass off my template to a designer who can easily tweak language and worry about the finer points of styling and layout.

Happy Hacking!

Kal

Leave a comment

About Kahlil (Kal) Hodgson

user-pic Leaking memory all over the place.