Zend_Form decorator tips

Tags:

It seems a lot of people are struggling with Zend_Form decorators. They want to know how to modify the markup, but they can’t figure out how to do it with the bundled decorators or how to write their own.

Since I’ve done some decorator-stuff myself, I thought I’d share some tips on making the output a bit nicer. For example, how to make Zend_Form use <ul> and <li> tags instead of <dd>, <dt> and <dl>. I also wanted some inputs appear side by side (date from and date to in my case).

General tips

Learn what the bundled decorators do and which are required. You can do a lot of stuff with the decorators that are shipped with Zend Framework, for example the HtmlTag decorator can be used in most scenarios.

Also, learn the vital decorators: For forms, you’ll usually need Form and FormElements, for DisplayGroups FormElements and FieldSet and for elements, ViewHelper, Label and Errors. Without these decorators, the form won’t work very well – you could always make your own, but most of the time these basic ones should suffice.

With the decorators I listed above, you will get a very basic form; basically just the form element, the labels and the inputs – no dd, dt or such. From there, it’s easy to start building your own markup, which I will show later in this post.

The HtmlTag decorator

Let’s take a quick look at the HtmlTag decorator, since it’s very useful.

You can use it to wrap any element, or form, in a specific HTML tag, such as a div. You can also use it to wrap multiple elements inside a single element with some smart usage of the parameters.

If you have two elements that you wish to put inside a div, you can do the following:

//add two elements
$form->addElement('text', 'one');
$form->addElement('text', 'two');
 
//Prepend an opening div tag before "one" element:
$form->one->addDecorator('HtmlTag', array(
    'tag' => 'div',
    'openOnly' => true,
    'placement' => Zend_Form_Decorator_Abstract::PREPEND
));
 
//Append a closing div tag after "two" element:
$form->two->addDecorator('HtmlTag', array(
    'tag' => 'div',
    'closeOnly' => true,
    'placement' => Zend_Form_Decorator_Abstract::APPEND
));
 
//The markup for one and two would now appear inside a <div> </div> block

As you can see, it’s actually quite simple to customize the markup a bit with the HtmlTag.

Custom decorators

Creating custom decorators is also quite simple. Just create a new class which extends Zend_Form_Decorator_Abstract, or implements Zend_Form_Decorator_Interface. Generally speaking, extending Decorator_Abstract is easier since you’ll have less things to code.

When extending Zend_Form_Decorator_Abstract, all you need to do is to define a method called render:

/**
 * This example decorator wraps things in div blocks
 */
class MyDecorator extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        return '<div>' . $content . '</div>';
    }
}

Adding this decorator to a form element would achieve the same effect as adding a HtmlTag decorator with div tag, so it’s perhaps a bit pointless, but serves as an example.

Next, we’ll look at changing the form output from dl/dd/dt to ul/li, and we’ll also use one decorator with DisplayGroups: CU_Form_Decorator_AllErrors, which grabs all child element errors and displays them.

Adding it all together: Modifying form output

Now that we know how decorators work and all that, let’s apply it to reach the ultimate goal: Changing Zend_Form’s default dd/dt/dl based markup to ul/li based one which is not only lighter but easier to style (in my opinion).

First, we’ll need a form:

  • Your name

    Errors for both name fields would be here


<style type="text/css">
#exampleform { list-style: none; margin: 0; padding: 0; }
#exampleform fieldset { border: none; margin: 0; padding: 0; }
#exampleform fieldset div { width: 100px; float: left; }
#exampleform label {display: block; }
#exampleform input { width: 100px; }
#exampleform p { clear: both }
</style>
<ul id="exampleform">
 <li>
  <fieldset>
   <legend>Your name</legend>
   <div>
    <input type="text" />
   </div>
   <div>
    <input type="text" />
   </div>
  </fieldset>
 </li>
 <li>
  <label for="phone">Phone</label>
  <input type="text" id="phone" />
 </li>
 <li>
  <input type="submit" value="ok" />
 </li>
</ul>

So the end result we want is something like the form above, with the markup looking somewhat like that as well, but not with that style-block obviously. I just included it for completeness’ sake, in case you were wondering how to make the markup look like that.

Let’s create a form class that we can use:

class ExampleForm extends Zend_Form
{
    public function init()
    {
        //We don't want the default decorators
        $this->setDisableLoadDefaultDecorators(true);
 
        $this->addDecorator('FormElements')
             ->addDecorator('HtmlTag', array('tag' => 'ul') //this adds a <ul> inside the <form>
             ->addDecorator('Form');
 
        //Create the name elements
        $this->addElement('text', 'firstname', array(
            'required' => true
        ));
 
        $this->addElement('text', 'lastname', array(
            'required' => true
        ));
 
        //Put the elements inside a displaygroup to get the fieldset and all
        $this->addDisplayGroup(array('firstname', 'lastname'), 'name', array(
            'legend' => 'Your name'
        ));
 
        $this->addElement('text', 'phone', array(
            'label' => 'Phone'
        ));
 
        $this->addElement('submit', 'ok', array(
            'label' => 'OK'
        ));
 
        //Set the decorators we need:
        $this->setElementDecorators(array(
            'ViewHelper',
            'Label',
            'Errors',
            new Zend_Form_Decorator_HtmlTag(array('tag' => 'li')) //wrap elements in <li>'s
        ));
 
        //Set decorators for the name fields so they appear side by side
        $this->setElementDecorators(array(
            'ViewHelper',
            'Label',
            new Zend_Form_Decorator_HtmlTag(array('tag' => 'div')) //wrap names in <div> for the float
        ), array('firstname', 'lastname'));
 
        //Set decorators for the displaygroup:
        $this->setDisplayGroupDecorators(array(
            'FormElements',
            'Fieldset',
            new CU_Form_Decorator_AllErrors(),
            new Zend_Form_Decorator_HtmlTag(array('tag' => 'li')) //wrap groups in <li>'s too
        ));
 
        //Remove label from submit button:
        $this->ok->removeDecorator('Label');
    }
}

And there it is. If you render the above form, you should get very similar markup to what I used to create the example. Apply the CSS, and it should look quite the same as well.

There’s one thing in the above code that may need a bit explaining: You can see we add the Errors decorator to the elements, but not on firstname and lastname. This is because the CU_Form_Decorator_AllErrors decorator we add to the DisplayGroups will take care of rending the errors.

And in case you missed it, you can get CU_Form_Decorator_AllErrors from the usual place.

In closing

Despite seeming overly complex and confusing, the decorators in Zend_Form are very powerful and offer lots of flexibility. They’re also quite simple to use after you understand the concept.

More on Zend_Form:
Complex custom elements in Zend_Form
Autogenerating forms from Doctrine models
Client side validation with Zend_Form