Twitter Bootstrap and the QuickForm2 Callback Renderer

I don’t know about you, but for me building HTML Forms and styling HTML Forms are maybe the most boring things in web development. It’s repetitive and takes a lot of time to do things correctly.

That’s why tools like Twitter’s Bootstrap and PEAR’s HTML_QuickForm2 can help with this part of our job.

Wouldn’t it be nice to have QuickForm2 generate a markup compatible with Bootstrap CSS, so that you could get a nice looking form without to much efforts? Well, that’s what I plan to do here.

Bootstrap is a toolkit from Twitter designed to kickstart development of webapps and sites. It includes base CSS and HTML for typography, forms, buttons, tables, grids, navigation, and more. In this post, I will concentrate on the form stuff.

QuickForm2 on the other hand is a PHP library dedicated to make building HTML forms easier, faster and less error-prone. It is hosted in PEAR and has been extensively tested.

First, I will try to build the same form as shown on Bootstrap’s website, with QuickForm2. Except for the three elements with appended and prepended things, it was easy. I will come back to these elements later, just know it is not difficult to render them neither.

So here is the result :
Step 1 : Bootstrap with QuickForm2 and no renderer

The most obvious problem is that QuickForm2 doesn’t use the same markup to build forms as what Bootstrap expects. To solve this, we can use the Callback renderer that comes with QuickForm2 and define our own callback to render our elements the way Bootstrap likes them.

The Callback renderer associates PHP callbacks with form elements using either their type (text, textarea, …) or their name if you need a more precise control. This means that we can first define how our form object should be rendered, as well as our Checkboxes, Textareas, etc. Adding a Callback renderer and specifying a function to render an element is as simple as that :

$r = HTML_QuickForm2_Renderer::factory('callback');
 
$r->setCallbackForClass('HTML_QuickForm2_Element', function($renderer, $element) {
    $error = $element->getError();
    if ($error) {
        $html[] = '<div class="clearfix error">';
        $element->addClass('error');
    } else {
        $html[] = '<div class="clearfix">';
    }
    $html[] = $renderer->renderLabel($element);
    $html[] = '<div class="input">'.$element;
    if ($error) {
        $html[] = '<span class="help-inline">'.$error.'</span>';
    } else {
        $label = $element->getLabel();
        if (is_array($label) && !empty($label[1])) {
            $html[] = '<span class="help-block">'.$label[1].'</span>';
        }
    }
    $html[] = '</div></div>';
    return implode('', $html);
});
echo $form->render($r);

Just adding the above to our form will already make it look a lot better with Bootstrap. There are some refinements we can add for the rest of our elements, this is shown here :
Step 2 : Defining callbacks to render elements

As you may notice, when I have to deal with special elements or group of elements, as is the case with Bootstrap and its action buttons for example, I usually just create a dumb subclass and define a specific callback to render it. For example, to render the form buttons:

// Callback for action inputs
 
$r->setCallbackForClass('ActionInputs', function($renderer, $group) {
    $html[] = '<div class="actions">';
    $elements = array_pop($renderer->html);
    $html[] = implode(' ', $elements);
    $html[] = '</div>';
    return implode('', $html);
});
 
// Callback for elements inside an action inputs group
 
$r->setElementCallbackForGroupClass('ActionInputs',
    'HTML_QuickForm2_Element', function($renderer, $element) {
    $html[] = (string)$element;
    return implode('', $html);
});

This is a very simple example, you can do a lot more in these callbacks, for example add a contextual error message, a label or an help text, join elements with a separator, etc.

There are, of course, many ways to improve this renderer and expand it to make it even more reusable. I personally find the Bootstrap CSS a little limited, for example it’s missing the following features I often use :

  • Differentiate required elements
  • Allow elements in inline inputs groups to have labels
  • Allow checkboxes and radios to be displayed inline
  • Use a standard text link instead of a button for cancel (better for redirects)
  • Allow elements in inline inputs groups to have contextual errors

For the above, I have made my own QuickForm2 renderer and custom CSS, but if you know of any other good looking and interesting CSS framework for styling forms, please let me know.

This entry was posted in php and tagged , , , . Bookmark the permalink.

6 Responses to Twitter Bootstrap and the QuickForm2 Callback Renderer

  1. Bertrand says:

    Captcha plugin was broken. Now fixed.
    Wordpress’ way to manage plugins sucks.

  2. Jonas Lejon says:

    Thanks for the blog post. I’m currently successfully using your code example. Can you provide a full example code?

  3. David Reagan says:

    Thanks for the post, it was perfect timing since I need to build a simple form and want it to look decent.

    I do have a slightly related question, how would I use AJAX with Quickform2? I’m building a small program that just needs to display and edit rows from a single sql table, it’d be nice if I didn’t have to refresh the page every time I change or add a value.

    I did some searching on Google, but couldn’t find any good tutorials or examples. Do you know of any?

  4. @david I have had great success using the jQuery Form plugin and its ajaxSubmit() function : http://jquery.malsup.com/form/
    You need to know some javascript though.

  5. Pau Gay says:

    So interesting, thanks for sharing.

    I’m in love with Twitter Bootstrap (though you are right in the points that you have made) and the fact that QuickForm output HTML that the Bootstrap can style is cool ;) !

  6. sak says:

    the hierselect doesn’t look good…
    anyway, an update to bootstrap2 would be very much appreciated…
    (I’m trying, without success on the hierselect, the rest is fine)