Spiga

Creating Tables with Zend_Form

March 26, 08 by Andrew Vayanis

I recently published an article trying to shed some light on the Zend_Form component, in particular, when using it with Zend_Config_Ini. In the article I presented a config I developed while trying to learn Zend_Form myself, but unfortunately realized that using generic elementDecorators comes with a price.

Apparently, using elementDecorators overrides individual element level decorators. This leads to some unexpected and annoying results. For instance, in my previous example, I lose the ability to hide or even add individual attributes to specific elements. This leaves me with a form that has labels for each element including the submit button:
Zend Form Table Login with all Labels

The only way I found to correct this problem is to add decorators to each element individually. This is highly redundant and hardly ideal, but it gives the control necessary to fix this problem. It also bloats the config file significantly; previously, the config was 31 lines long, but now it is 47 lines long:

[login]
; General Form Information
login.action = "login/submit"
login.method = "post"
login.id = "login"
 
; Form Decorators
login.decorators.elements.decorator = "FormElements"
login.decorators.table.decorator = "HtmlTag"
login.decorators.table.options.tag = "table"
login.decorators.form.decorator = "Form"
 
; Username Element
login.elements.username.type = "text"
login.elements.username.options.label = "Username:"
login.elements.username.options.required = true
login.elements.username.options.validators.alnum.validator = "alnum"
login.elements.username.options.validators.regex.validator = "regex"
login.elements.username.options.validators.regex.options.pattern = "/^[a-z]/i"
login.elements.username.options.validators.strlen.validator = "StringLength"
login.elements.username.options.validators.strlen.options.min = "5"
 
; Username Decorators
login.elements.username.options.decorators.helper = "ViewHelper"
login.elements.username.options.decorators.tableData.decorator.td = "HtmlTag"
login.elements.username.options.decorators.tableData.options.tag = "td"
login.elements.username.options.decorators.label.decorator = "Label"
login.elements.username.options.decorators.label.options.tag = "td"
login.elements.username.options.decorators.tableRow.decorator.tr = "HtmlTag"
login.elements.username.options.decorators.tableRow.options.tag = "tr"
 
; Password Element
login.elements.password.type = "password"
login.elements.password.options.label = "Password:"
login.elements.password.options.required = true
login.elements.password.options.validators.strlen.validator = "StringLength"
login.elements.password.options.validators.strlen.options.min = "6"
 
; Password Decorators
login.elements.password.options.decorators.helper = "ViewHelper"
login.elements.password.options.decorators.tableData.decorator.td = "HtmlTag"
login.elements.password.options.decorators.tableData.options.tag = "td"
login.elements.password.options.decorators.label.decorator = "Label"
login.elements.password.options.decorators.label.options.tag = "td"
login.elements.password.options.decorators.tableRow.decorator.tr = "HtmlTag"
login.elements.password.options.decorators.tableRow.options.tag = "tr"
 
; Submit Form Element
login.elements.submit.type = "submit"
login.elements.submit.options.label = "Submit"
 
; Submit Decorators
login.elements.submit.options.decorators.helper = "ViewHelper"
login.elements.submit.options.decorators.tableData.decorator = "HtmlTag"
login.elements.submit.options.decorators.tableData.options.tag = "td"
login.elements.submit.options.decorators.label.decorator = "Label"
login.elements.submit.options.decorators.label.options.tag = "td"
login.elements.submit.options.decorators.label.options.class = "submit"
login.elements.submit.options.decorators.tableRow.decorator.tr = "HtmlTag"
login.elements.submit.options.decorators.tableRow.options.tag = "tr"

Personally, I think this is poor design and feel it may even be a bug, but I will attempt to submit it to ZF’s issue tracker and see what they say. Otherwise though, I still feel as though Zend_Form is a great component, and will continue to publish any discoveries I come across.

Similar Posts

Add your comment

20 responses for this post

  1. Gravatar
    Matthew Weier O'Phinney Says:

    Caveat: I’m the author of Zend_Form and Software Architect for Zend Framework. :-)

    I’m always skeptical when somebody says, “I think this is poor design” and then doesn’t back it up… How would you do things differently? To Zend_Form, each element is equal, as is each decorator — it doesn’t know how they differ. For the elements, each decorator is equal… it doesn’t know that one is for the label, and one isn’t. Setting decorators for all elements does exactly that — treats them all equally.

    Internally, it’s actually *faster* if you pass in your decorators at configuration time. Why? because it doesn’t first set the default decorators and then overwrite them. This is exactly the recommendation I have for developers, as well — pass in decorators at configuration time if you’re not using defaults.

    Decorators are meant to allow nested as well as sequential creation of output. In addition, they can draw on *any* metadata in the object they are decorating. Since that metadata is arbitrary, there’s really no extensible way a decorator could hint to an element what it decorates. On the flip side, while decorators can be element aware, doing so sets arbitrary limitations on the decorators and would raise whole new issues.

    All that aside, thanks for posting this, as it gives other developers an example for an alternative form layout.

  2. Gravatar
    Andrew Vayanis Says:

    What I would do differently, and granted, I tested this out prior to the official 1.5 release and have not had a chance to test it since, is take declaration order into account . If I set default decorators earlier in the configuration file and then set individual decorators afterwards, the individual decorators should take precedence. To me at least, that would seem like expected behavior of such a component.

    *Edit: I also would like to point out, that this post was in no way shape or form meant to put down the work you or anyone else has done on the Zend Framework. I think the direction of the project is great and have been using it for as many projects as I can.

  3. Gravatar
    Paris Sánchez Says:

    Hello Andrew Vayanis!

    Cool article, it was a great guide for me. But, it doesn’t manage description and errors messages, that’s why based on this article and Zend_Form_Decorator manual (http://framework.zend.com/manual/en/zend.form.decorators.html) I build a My_Decorator_TableRow class (attached at the end) whit this class the config file pass from:

    ; Username Decorators
    login.elements.username.options.decorators.helper = “ViewHelper”
    login.elements.username.options.decorators.tableData.decorator.td = “HtmlTag”
    login.elements.username.options.decorators.tableData.options.tag = “td”
    login.elements.username.options.decorators.label.decorator = “Label”
    login.elements.username.options.decorators.label.options.tag = “td”
    login.elements.username.options.decorators.tableRow.decorator.tr = “HtmlTag”
    login.elements.username.options.decorators.tableRow.options.tag = “tr”

    To:

    ; Username Decorators
    login.elements.username.options.decorators.tablerow = “TableRow”

    I think this make easier to write a large forms :P The important think fo this is that in the action controler you have to add ElementPrefixPath before load the config to the form, because ElementPrefixPath is an element you can not pass throu config, acording to ZF documentation.

    So the controler and action will look something like this:
    addElementPrefixPath(‘My_Decorator’, ‘My/Decorator/’, ‘decorator’);
    $config = new Zend_Config_Ini(‘./path/to/configFile.ini’, [login]);
    $form->setConfig( $config );
    }

    }
    ?>

    With this I save 6 line per element on confiFile.ini and do not overrides individual element level decorators that has not been defined on My_Decorator_TableRow, but still have to add decorators to each element.

    Hope this is useful to someone, and if my English is horrible sorry =P, pleas correct me so i can learn.

    Paris.

    PS. Here is the complete My_Decorator_TableRow, note: this notation of name use Zend load, and the class shout be in library/My/Decorator/TableRow.php

    getElement();
    $label = $element->getLabel();
    if ($translator = $element->getTranslator()){
    $label = $translator->translate($label);
    }
    if ($element->isRequired()) {
    $label = ‘*’ . $label;
    }/**/
    $label .= ‘:’;
    $label = $element->getView()->formLabel($element->getName(), $label);
    return ” . $label . ”;
    }

    public function buildInput()
    {
    $element = $this->getElement();
    $helper = $element->helper;
    $input = $element->getView()->$helper(
    $element->getName(),
    $element->getValue(),
    $element->getAttribs(),
    $element->options
    );
    return ” . $input . ”;
    }

    public function buildErrors()
    {
    $element = $this->getElement();
    $messages = $element->getMessages();
    if (empty($messages)) {
    return ”;
    }
    return ” . $element->getView()->formErrors($messages) . ”;
    }

    public function buildDescription()
    {
    $element = $this->getElement();
    $desc = $element->getDescription();
    if (empty($messages)) {
    return ”;
    }
    return ” . $desc . ”;
    }

    public function render($content)
    {
    $element = $this->getElement();
    if (!$element instanceof Zend_Form_Element) {
    return $content;
    }
    if (null === $element->getView()) {
    return $content;
    }

    $separator = $this->getSeparator();
    $placement = $this->getPlacement();
    $label = $this->buildLabel();
    $input = $this->buildInput();
    $errors = $this->buildErrors();
    $desc = $this->buildDescription();

    $output = ” .
    $label .
    $input .
    ” .
    $desc .
    $errors .
    ” .
    ”;

    switch ($placement) {
    case (self::PREPEND):
    return $output . $separator . $content;
    case (self::APPEND):
    default:
    return $content . $separator . $output;
    }
    }
    }

  4. Gravatar
    Paris Sánchez Says:

    Oops! the comment doesn’t post the php code as i had planed >..<

  5. Gravatar
    Andrew Vayanis Says:

    Your right, I don’t explain how to add custom validator error messages in this post, and I actually meant to do that in a new post, so I will get around to doing that this week, however if you need some help with it right away, take a look at the comments in my previous post:

    http://www.vayanis.com/2008/03/17/using-zend_form-with-zend_config/

  6. Gravatar
    Marc Shake Says:

    Cool article. What I am still trying to do is set up default-values in my config-path. So when I setup a form I want the fields filled with some values.

    I am not sure, how to archive that… Any Idea?

  7. Gravatar
    Andrew Vayanis Says:

    Hey Marc,

    If you are looking to use Zend_Config_Ini, you can use something like the following:

    elements.username.options.value =”

    Alternatively, if you are doing this with a form element object, you should be able to do something like $element->setValue();

  8. Gravatar
    gerry22 Says:

    There needs to be setButtonDecorators(), setSubmitDecorators() & maybe a setButtonsDecorators() (for setting both together).

    You could then extend custom classes with
    public function setCustomDecorators()
    {
    $this->setDecorators($this->_standardDecorators);
    $this->setElementDecorators($this->_standardElementDecorator);
    $this->setDisplayGroupDecorators($this->_standardGroupDecorator);
    $this->setButtonsDecorators($this->_standardButtonDecorator);
    }

  9. Gravatar
    peter Says:

    What configuration you would propose for this type of form?: (specially about array notation)

    Menu

    ID
    Sysname
    Action

    1

    2

  10. Gravatar
    peter Says:

    Sorry coding issues:(
    What configuration you would propose for this type of form?: (specially about array notation)
    <form id=”menuList” method=”post” action=”listPost”>
    <table>
    <tr class=”rowElement”>
    <td class=”nameElement” colspan=”4″>Menu</td>
    </tr>
    <tr class=”rowElement”>
    <td class=”titleElement”>
    <input type=”hidden” value=”0″ name=”all”/>
    <input id=”all” class=”black” type=”checkbox” value=”1″ name=”all”/>
    </td>
    <td class=”titleElement”>ID</td>
    <td class=”titleElement”>Sysname</td>
    <td class=”titleElement”>Action</td>
    </tr>
    <tr class=”rowElement”>
    <td class=”grey_10″>
    <input type=”hidden” value=”0″ name=”hm_id[1]“/>
    <input id=”id-1″ class=”black” type=”checkbox” value=”1″ name=”id[1]“/>
    </td>
    <td class=”grey_10″>1</td>
    <td class=”grey_10″>
    <input id=”sys_name-1″ type=”text” value=”admin” name=”sys_name[1]“/>
    </td>
    <td class=”grey_10″>
    <input id=”edit[1]” class=”listButton” type=”submit” value=”Edytuj” name=”edit[1]“/>
    <input id=”delete[1]” class=”listButton” type=”submit” value=”Usu?” name=”delete[1]“/>
    </td>
    </tr>
    <tr class=”rowElement”>
    <td class=”grey_10″>
    <input type=”hidden” value=”0″ name=”id[2]“/>
    <input id=”id-2″ class=”black” type=”checkbox” value=”1″ name=”id[2]“/>
    </td>
    <td class=”grey_10″>2</td>
    <td class=”grey_10″>
    <input id=”sys_name-2″ type=”text” value=”front” name=”sys_name[2]“/>
    </td>
    <td class=”grey_10″>
    <input id=”edit[2]” class=”listButton” type=”submit” value=”Edytuj” name=”edit[2]“/>
    <input id=”delete[2]” class=”listButton” type=”submit” value=”Usu?” name=”delete[2]“/>
    </td>
    </tr>
    <tr class=”rowElement”>
    <td class=”barElement” colspan=”4″>
    <input id=”deleteSelected” class=”listButton” type=”submit” value=”Usu? wybrane” name=”deleteSelected”/>
    <input id=”deleteAll” class=”listButton” type=”submit” value=”Usu? wszystkie” name=”deleteAll”/>
    <input id=”saveSelected” class=”listButton” type=”submit” value=”Zapisz wybrane” name=”saveSelected”/>
    <input id=”saveAll” class=”listButton” type=”submit” value=”Zapisz wszystkie” name=”saveAll”/>
    </td>
    </tr>
    <tr class=”rowElement”>
    <td class=”barElement” colspan=”4″> </td>
    </tr>
    </table>
    </form>

  11. Gravatar
    MichelleJD Says:

    In my opinion you are not right. I am assured. I can prove it. Write to me in PM, we will talk.

    31924

  12. Gravatar
    carrepossesseion Says:

    As a Newbie, I am always searching online for articles that can help me. Thank you

  13. Gravatar
    Bart McLeod Says:

    I found your article and I hoped it would help me change the placement option of a decorator, and I tried it using syntax similar to use, but I get an error (using ZF 1.9.6 or very similar).

    elements.nieuwsbrief.options.decorators.label.options.placement = “APPEND”

    I also tried “append” (lowercase) with no luck. Doing this in the form works fine, but not through config. I get this error:

    Fatal error: Uncaught exception ‘Zend_Loader_PluginLoader_Exception’ with message ‘Plugin by name ‘APPEND’ was not found in the registry; used paths: Zend_Form_Decorator_: Zend/Form/Decorator/’ in D:\ZendFramework\library\Zend\Loader\PluginLoader.php:406 Stack trace: #0 D:\ZendFramework\library\Zend\Form\Element.php(1773): Zend_Loader_PluginLoader->load(‘APPEND’) #1 D:\ZendFramework\library\Zend\Form\Element.php(2151): Zend_Form_Element->_getDecorator(‘APPEND’, NULL) #2 D:\ZendFramework\library\Zend\Form\Element.php(1923): Zend_Form_Element->_loadDecorator(Array, ‘placement’) #3 D:\ZendFramework\library\Zend\Form\Element.php(312): Zend_Form_Element->getDecorators() #4 D:\ZendFramework\library\Zend\Form\Element.php(267): Zend_Form_Element->loadDefaultDecorators() #5 D:\ZendFramework\library\Zend\Form.php(1078): Zend_Form_Element->__construct(‘nieuwsbrief’, Array) #6 D:\ZendFramework\library\Zend\Form.php(1009): Zend_Form->createElement(‘checkbox’, ‘nieuwsbrief’, Array) #7 D:\ZendFramework\library\Zend\Form.php(1113): Zend_ in D:\ZendFramework\library\Zend\Loader\PluginLoader.php on line 406

    Apparently ZF is looking for a plugin, while I just want to set an option on the decorator. Any clues what I am doing wrong? I will search for other articles of course, and check the mailing list. But I thought people might benefit if you can come up with an answer here.

    Thank you!

  14. Gravatar
    Andrew Vayanis Says:

    I would have to try it out to confirm, but I think it has to do with you putting the options on the label which doesn’t take any options I don’t think. Have you tried:
    elements.nieuwsbrief.options.decorators.placement = “append”?

  15. Gravatar
    Bart McLeod Says:

    I think I tried this kind of variations, but with no luck. It also makes no sense. The label decorator does take this kind of options, I am postive about that.

    If I take the form as is, created throug .ini and after that I do
    $form->nieuwsbrief->getDecorator(‘Label’)->setOption(‘placement’, ‘APPEND’);

    it works fine.

    So the option can be set for sure.

    I found an entry in the mailing list that is somewhat instructieve, but does yield the same result. This assumes you should talk to the decorators array like this:
    elements.decorators.5.options.placement = “APPEND”

    The label is the fith default decorator if you dump getDecorators() on the nieuwsbrief element. There is however no difference in the result. It would be valuable if you tried, it is possible after all that a bug has entered the system somewhere.

  16. Gravatar
    Andrew Vayanis Says:

    Hey,

    I haven’t played with the placement options too much, but I was able to set a placement option on a label by doing this:

    elements.nieuwsbrief.options.label.options.placement = “append”

    instead of:

    elements.nieuwsbrief.options.decorators.label.options.placement = “APPEND”

    Notice I took out the ‘decorators’ specification between options and label. This makes sense as it is reflected by the non-config code you pasted that does work.

  17. Gravatar
    Bart McLeod Says:

    This helps in so far that I get a different exception AND it tries to set the placement option, but it fails, because it claims to have one already:

    Fatal error: Uncaught exception ‘Zend_Config_Exception’ with message ‘Cannot create sub-key for ‘label’ as key already exists’ in D:\ZendFramework\library\Zend\Config\Ini.php:293 Stack trace: #0 D:\ZendFramework\library\Zend\Config\Ini.php(295): Zend_Config_Ini->_processKey(Array, ‘label.options.p…’, ‘append’) #1 D:\ZendFramework\library\Zend\Config\Ini.php(295): Zend_Config_Ini->_processKey(Array, ‘options.label.o…’, ‘append’) #2 D:\ZendFramework\library\Zend\Config\Ini.php(295): Zend_Config_Ini->_processKey(Array, ‘nieuwsbrief.opt…’, ‘append’) #3 D:\ZendFramework\library\Zend\Config\Ini.php(260): Zend_Config_Ini->_processKey(Array, ‘elements.nieuws…’, ‘append’) #4 D:\ZendFramework\library\Zend\Config\Ini.php(134): Zend_Config_Ini->_processSection(Array, ‘form’) #5 D:\werk\cuddlefish\application\default\controllers\helpers\ContactForm.php(81): Zend_Config_Ini->__construct(‘../config/modul…’) #6 D:\werk\cuddlefish\application\default\controllers\helpers\ContactForm.php(19): Default_Action_Helper_ContactForm->_ge in D:\ZendFramework\library\Zend\Config\Ini.php on line 293

    If I remove the key that set the text on the label, I get no exception, but this output:

    Array

    Note the Array label I have now gotten. It is the result of:
    elements.nieuwsbrief.options.label.options.placement = “append”

    So it did not help, but I appreciate your effort!

  18. Gravatar
    Bart McLeod Says:

    The html did not come out as expected….

  19. Gravatar
    Zend Framework Johny Says:

    Article helped me a lot, thanks.

  20. Gravatar
    Facebook Layouts Says:

    Thanks for the useful info…we are always looking for new blogs to link to.

Leave a Reply