You were an excellent partner in every way. You delivered your commitments on time, you provided clear guidance on what you needed from me, why you needed it, and when my communication was not sufficient you asked for clarification.

800-813-1316

help@ideacode.com

 

Blazing a trail to better facilities management.

Painless Zend Form decorators

Headline, 2008: Zend_Form decoration a pain, says lead Zend developer.  Three years later, googling for "Zend_Form decorator help" produces no shortage of questions, complaints, and outright rants against Zend_Form decorators. Seriously, no other part of Zend seems less loved than form decorators.

All the bitchin' and moanin' seems rooted in two problems: the default use of definition lists and the lack of an easy way to override those defaults.

I've been translating the circa-2003 table layout of our facilities management application (AERES) to one based on A List Apart's "Prettier Accessible Forms" model.  Of course I run aground of the dreaded form decorators, and, low and behold, I joined the chorus of complainers.

But Zend's extremely flexible, so I knew there had to be a way to get it to do what I wanted:

  1. Let me define the global default decoration for the whole form, all elements generically, or specific kinds of elements (eg buttons).
  2. Let me plug-in different global default definitions with minimal fuss.
  3. Use the Zend default decorators if I don't specify otherwise.
  4. Let me override a specific element with a different, unique decorator.
  5. Apply to the form and elements automatically when I init() the form, without a separate call.
  6. Use the same structure for defining decorators that Zend uses.

I couldn't find anything in the Zend documentation or the online discussion with a workable solution, so time to write my own.  Turned out, after a little bit of wrangling, to be straightforward.

Enough talk!  Show me the code!

Here's how to get painless Zend Form decoration:  First, create an intermediary class, from which all of your application forms derive, that will let you easily plug in decorators and do the heavy lifting with Zend.  Second, define your decorations and plug them in.  Third, use.

<?php
class Decorative_Form extends Zend_Form {
    public $decorators = array (); // your decorator definition, keyed by Zend_Form* class; array () uses Zend defaults

    public function __construct($options = null) {
        // plugin form and element decorators
        if (is_array($options) && isset($options['decorators'])) {
            if (is_array($options['decorators'])) {
                $this->decorators = $options['decorators'];
                unset($options['decorators']);
            }
        }

        // build the form
        parent::__construct($options);
    }
    public function addElement($element, $name = null, $options = null) {
        // ask the parent to do the work to add the element
        parent::addElement($element, $name, $options);

        // now if we did not set a decorator on the element, add our default
        if (null === $options || (is_array($options) && ! isset($options['decorators']))) {
            if (! $element instanceof Zend_Form_Element) {
                $element = $this->getElement($name);
            }
            foreach ($this->decorators as $class => $decorators) {
                if ($element instanceof $class) {
                    $element->setDecorators($decorators);
                    return $this;
                }
            }
        }

        return $this;
    }
    public function loadDefaultDecorators() {
        // if we have a form decorator plugged-in, use it
        if (isset($this->decorators['Zend_Form'])) {
            $this->setDecorators($this->decorators['Zend_Form']);

        // otherwise, do the Zend default
        } else {
            parent::loadDefaultDecorators();
        }
    }
}
?>

This class handles the bootstrapping of Zend to take decorator definition and work it into how Zend expects decorators to be set.  If you extend this class, you get the Zend_Form default, because you've not plugged in the decorators yet.

So now, define the decorators to plug-in.  You can do this by hard-coding the definition into classes, injecting from somewhere else, loading from a configuration file, etc.

<?php
// decorates with <ol>
// Based on A List Apart "Prettier Accessible Forms"
// @see http://www.alistapart.com/articles/prettyaccessibleforms
class Accessible_Form extends Decorative_Form {
    public $decorators = array (
                             'Zend_Form_Element_Submit' => array (
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'button')),
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),
                             'Zend_Form_Element_Captcha' => array (
                                 'Label',
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li'))
                             ),
                             'Zend_Form_Element_Checkbox' => array (
                                 'Label',
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'checkbox')),
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),
                             'Zend_Form_Element_Radio' => array (
                                 'Label',
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'radio')),
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),
                             'Zend_Form_Element' => array (
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'element')),
                                 'Label',
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),
                             'Zend_Form' => array (
                                 'FormErrors',
                                 'FormElements',
                                 array ('HtmlTag', array ('tag' => 'ol')),
                                 'Form'
                             ),
                         );

}

// decorates with <table> and <tr><td>Label</td><td>Input</td></tr>
class Table_Form extends Decorative_Form {
    public $decorators = array (            
                             'Zend_Form_Element_Submit' => array (
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'td', 'class' => 'button')),
                                 array (array ('label' => 'HtmlTag'), array ('tag' => 'td', 'placement' => 'prepend')),
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'tr')),
                             ),
                             'Zend_Form_Element' => array (
                                 'ViewHelper',
                                 'Errors',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'td', 'class' => 'element')),
                                 array ('Label', array ('tag' => 'td')),
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'tr')),
                             ),
                             'Zend_Form' => array (
                                 'FormErrors',
                                 'FormElements',
                                 array ('HtmlTag', array ('tag' => 'table')),
                                 'Form'
                             ),
                         );
}
?>

Now, you can create your forms the same way you always do, just extend the decorator implementation that fits your needs.

<?php
class Login_Form extends Table_Form {
    public function init() {
        $this->addElement('text', 'username', array (
            'label'      => 'Username',
            'required'   => true,
            'validators' => array (
                array ('Alnum'),
                array ('StringLength', false, array (1, 8)),
            ),
        ));

	// adjusting the decorator stack still works:
        $this->getElement('username')->removeDecorator('Errors');

        $this->addElement('password', 'password', array (
            'label'      => 'Password',
            'required'   => true,
            // so does ad-hoc decorators
            'decorators' => array ('ViewHelper',),
        ));

        $this->addElement('submit', 'submit', array ('label' => 'Login'));
    }

}
?>

One important point: the order of $decorators is critical.  List them most specific element to least, because the loop in the Decorative_Form class looks for the first class that matches the element.  That's why, in these examples, Zend_Form_Element_Submit comes before Zend_Form_Element, which comes before Zend_Form.

Happy decorating!

Addendum

If you're using Zend_Dojo_Form with Dijit, you need to also set decorators for your Dijit elements.  Fortunately, you can just plug them in.  This class is the same as above, except that I've added a decorator definition for a Zend Dojo form FilteringSelect.  Add a similar block for your ComboBoxes, etc.

<?php
// decorates with <ol>
// Based on A List Apart "Prettier Accessible Forms"
// @see http://www.alistapart.com/articles/prettyaccessibleforms
class Accessible_Form extends Decorative_Form {
    public $decorators = array (
                             // Dijit elements
                             'Zend_Dojo_Form_Element_FilteringSelect' => array (
                                 'DijitElement',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'element')),
                                 'Label',
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),

                             // plain ole Zend_Form elements
                             'Zend_Form_Element_Submit' => array (
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'button')),
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),
                             'Zend_Form_Element_Captcha' => array (
                                 'Label',
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li'))
                             ),
                             'Zend_Form_Element_Checkbox' => array (
                                 'Label',
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'checkbox')),
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),
                             'Zend_Form_Element_Radio' => array (
                                 'Label',
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'radio')),
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),
                             'Zend_Form_Element' => array (
                                 'ViewHelper',
                                 array (array ('data' => 'HtmlTag'), array ('tag' => 'div', 'class' => 'element')),
                                 'Label',
                                 array (array ('row' => 'HtmlTag'), array ('tag' => 'li')),
                             ),
                             'Zend_Form' => array (
                                 'FormErrors',
                                 'FormElements',
                                 array ('HtmlTag', array ('tag' => 'ol')),
                                 'Form'
                             ),
                         );

}
?>

Trackback URL for this post:

http://www.ideacode.com/trackback/49

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.