Stock decorators

2009 March 5
by monzee

I use the magic render* methods a lot nowadays, but I don’t use them exclusively. Elements with the same markup are better off decorated and rendered whole to minimize code duplication. Consistency and predictability of the markup is another advantage of decorators. Write it once, assign it to your elements and you can be sure that all your elements will be marked up the same way. No need to worry about mismatched tags (if you got them right in the first place) and you have fewer stuff to type.

I keep a static class which stores various sets of decorators so I can just pull them out of the class and assign them to my form elements where I see fit. There’s not a lot of them at the moment though.

These decorator sets have the same appearance: labels at the left, input elements at the right and the list of errors below the elements. They only differ in the wrapper tags used and the manner in which they are wrapped. Each set has four types of decorators: checkbox, button, element and form. The checkbox decorators are meant to be used for checkbox, multiCheckbox and radio elements. Button is for button, submit and reset. Element is for the rest of the elements and form is for the form itself. I do not have decorators for display groups because I rarely use them. The labels of the checkbox class of elements are rendered next to the input element itself, instead of being at the left column like the text elements. The description attribute is used instead in the left column for the checkbox elements, although they are still inside label tags to make CSS styling more straightforward.

The same caveats apply here as in the previous post: these decorators might break some elements like File, Captcha and the Dojo/JQuery elements. Don’t blindly apply these to every element. Pick what you need and adapt them to your special elements.

Unordered list form

This set is the simplest of the three. Each row consists of a label, input and ul of errors wrapped with li, then the rows are wrapped with ul and form. It requires a bit of CSS to simulate a columnar structure.

        $elementLi = array(
            'ViewHelper',
            'Label',
            'Errors',
            array('HtmlTag', array('tag' => 'li'))
        );
        $checkboxLi = array(
            'ViewHelper',
            array('Label', array('placement' => 'append')),
            array(array('elemWrap' => 'HtmlTag'), array('tag' => 'div', 'class' => 'checkbox-wrap')),
            array('Description', array('tag' => 'label', 'placement' => 'prepend')),
            'Errors',
            array(array('wrap' => 'HtmlTag'), array('tag' => 'li'))
        );
        $buttonLi = array(
            'ViewHelper',
            array('HtmlTag', array('tag' => 'li', 'class' => 'button'))
        );
        $formUl = array(
            'FormElements',
            array('HtmlTag', array('tag' => 'ul', 'class' => 'list-form')),
            'Form'
        );
.list-form {list-style: none}
.list-form li {clear: both}
.list-form label, .list-form .hint {float: left; display: block; width: 10em}
.list-form .checkbox-wrap {margin-left: 10em}
.list-form .checkbox-wrap label {float: none; display: inline}
.list-form .button {padding-left: 10em}
.list-form .errors {clear: none; margin-left: 10em}

Table form

This one often appears in the ZF mailing list. It requires little styling as the elements automatically align themselves in a column. CSS nazis hate this though. Anything that has to do with tables are automatically uncool to some people. The biggest advantage of this style is that the left column automatically adjusts to the length of the longest label. In the ul and dl forms, the width of the labels or their containers must be explicitly set since there is no notion of columns in those types.

The styling is simple, but the decorators are tricky. If you want additional wrappers somewhere you’d have to be creative with HtmlTag and openOnly/closeOnly options to add them. I suggest reading zomg’s tutorial for an explanation on how to use them.

One thing to note here is the Description decorator for the buttons. An empty td cell will not display borders in IE and in Firefox when using an HTML doctype (if you’re using XHTML, please reconsider if you really want/need it), so you have to put something in the ‘description’ property of the button. If borders aren’t needed, the Description decorator could be ignored or removed.

        $elementTr = array(
            'Label',
            array(array('labelTd' => 'HtmlTag'), array('tag' => 'td', 'class' => 'labelWrap')),
            array(array('elemTdOpen' => 'HtmlTag'), array('tag' => 'td', 'openOnly' => true, 'placement' => 'append')),
            'ViewHelper',
            'Errors',
            array(array('elemTdClose' => 'HtmlTag'), array('tag' => 'td', 'closeOnly' => true, 'placement' => 'append')),
            array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
        );
        $checkboxTr = array(
            array('Description', array('tag' => 'label')),
            array(array('labelTd' => 'HtmlTag'), array('tag' => 'td', 'class' => 'labelWrap')),
            array(array('elemTdOpen' => 'HtmlTag'), array('tag' => 'td', 'openOnly' => true, 'placement' => 'append')),
            'ViewHelper',
            array('Label', array('placement' => 'append')),
            'Errors',
            array(array('elemTdClose' => 'HtmlTag'), array('tag' => 'td', 'closeOnly' => true, 'placement' => 'append')),
            array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
        );
        $buttonTr = array(
            'Description',
            array(array('labelTd' => 'HtmlTag'), array('tag' => 'td', 'class' => 'labelWrap')),
            array(array('elemTdOpen' => 'HtmlTag'), array('tag' => 'td', 'openOnly' => true, 'placement' => 'append')),
            'ViewHelper',
            array(array('elemTdClose' => 'HtmlTag'), array('tag' => 'td', 'closeOnly' => true, 'placement' => 'append')),
            array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
        );
        $formTable = array(
            'FormElements',
            array('HtmlTag', array('tag' => 'table', 'class' => 'table-form')),
            'Form'
        );

Definition list form

This set utilizes the tags dl, dt and dd but in a different way from the default Zend_Form decorators. Here, each row is wrapped in a dl, the dt’s are floated left and dd’s shifted right to simulate columns. The decorators are very similar to the ones above, except there’s no need to add a class to the label wrappers since they are wrapped by a different tag. The rows are then wrapped with a div with a ‘dl-form’ class so that the the rows and its children could be targeted specifically in the CSS. The same could be achieved by adding a class property to the form itself.

        $elementDl = array(
            array('Label', array('tag' => 'dt')),
            array(array('ddOpen' => 'HtmlTag'), array('tag' => 'dd', 'openOnly' => true, 'placement' => 'append')),
            'ViewHelper',
            'Errors',
            array(array('ddClose' => 'HtmlTag'), array('tag' => 'dd', 'closeOnly' => true, 'placement' => 'append')),
            array(array('dl' => 'HtmlTag'), array('tag' => 'dl'))
        );
        $checkboxDl = array(
            array('Description', array('tag' => 'label')),
            array(array('dt' => 'HtmlTag'), array('tag' => 'dt')),
            array(array('ddOpen' => 'HtmlTag'), array('tag' => 'dd', 'openOnly' => true, 'placement' => 'append')),
            'ViewHelper',
            array('Label', array('placement' => 'append')),
            'Errors',
            array(array('ddClose' => 'HtmlTag'), array('tag' => 'dd', 'closeOnly' => true, 'placement' => 'append')),
            array(array('dl' => 'HtmlTag'), array('tag' => 'dl'))
        );
        $buttonDl = array(
            array(array('dt' => 'HtmlTag'), array('tag' => 'dt')),
            array(array('ddOpen' => 'HtmlTag'), array('tag' => 'dd', 'openOnly' => true, 'placement' => 'append')),
            'ViewHelper',
            array(array('ddClose' => 'HtmlTag'), array('tag' => 'dd', 'closeOnly' => true, 'placement' => 'append')),
            array(array('dl' => 'HtmlTag'), array('tag' => 'dl'))
        );
        $formDl = array(
            'FormElements',
            array('HtmlTag', array('tag' => 'div', 'class' => 'dl-form')),
            'Form'
        );
.dl-form dl {clear: both}
.dl-form dt {float: left; width: 10em}
.dl-form dd {margin-left: 10em}

Here is a sample controller that uses all three sets so you can see the resulting markup. This doesn’t use a view script, so you need to add the styles to the layout or create the view script on your own. It’s also stupid long for a controller action, this is the reason why I pull these decorators from a static class.

WordPress decided that it wants to escape random >’s in the code, so I pastebinned the controller instead.

No comments yet

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS