Thursday, June 24, 2010

Zend Form: display group and custom decorator

I keep meaning to write a proper review of Zend Framework, but I am waiting to finish a big project that uses it. It is running late, and I think ZF can take some of the blame. So don't hold your breath waiting for a positive review.

Today's topic: I want to have part of my form hidden initially, and instead show a button saying: "toggle more questions".

So, we make it a display group (where ex1 and ex2 are the form elements to hide):
$form->addDisplayGroup(array('ex1','ex2'),'extras');

By default this wraps it in a fieldset, with no place I could see to slip in my CSS, javascript and the toggle link. What I need is a decorator, I said to myself.

Oh, gasp, does Zend make this difficult or what! In another part of this project custom decorators had been used for a minor layout change. 7 classes in 3 directories, almost all of it boilerplate. The real work was being done in CSS; those 7 classes were just to give a way to name the items as far as I could tell.

The problem is the Zend Framework Philosophy of making things complex. Did you realize there is no addDecorator($myclass) function! You have to keep decorators in a special directory and then tell Zend where to get them. Then addDecorator('part_of_my_class_name').

ZF's saving grace is that it is open source. So I poked around, and here is my solution. First, I'll define an alias for readability (optional):
$displayGroup=$form->getDisplayGroup('extras');

Remove all the default stuff I don't need (this step is optional too):
$displayGroup->removeDecorator('Fieldset');
  $displayGroup->removeDecorator('HtmlTag');
  $displayGroup->removeDecorator('DtDdWrapper');

Now insert my decorator (these 4 lines are in lieu of addDecorator($myclass)):
$decorators=$displayGroup->getDecorators();
  $decorators['MyTest']=new MyTestDecorator();
  $displayGroup->clearDecorators();
  $displayGroup->addDecorators($decorators);

(I.e. get the current decorators, add mine, then replace the existing decorators with the new set.)


Finally we get to the meat. All you need is to define a render() function that takes a string (the existing content) and returns that string, optionally modified. Here is the minimal version that does nothing.
class MyTestDecorator extends Zend_Form_Decorator_Abstract
{
  public function render($content){ return $content; }
}

And here is the full version: it hides the elements in the group and uses JQuery to show/hide it. The CSS is inline.
class MyTestDecorator extends Zend_Form_Decorator_Abstract
{
  public function render($content)
  {
    $js="$('#extra_questions').toggle();return false;";
    return '<a href="" onclick="'.$js.'">Toggle Tags Visibility</a>'.
      '<div id="extra_questions" style="display: none;">'.$content.'</div>';
  }
}

Yep, it's that simple. Oooh, the architects of ZF must be turning red with rage ;-)

UPDATE: The best article I've found so far on Zend Form Decorators. As many of the comments say, the length of the article is also a very good argument against using Zend Form. And it still didn't answer my questions, so it really needed to be 2-3 times as long. But if you need to format a form, it is a far more useful resource than the utterly inadequate Zend Form manual.

2 comments:

Anonymous said...

Thanks that was very useful. I'm currently using Zend for a rather big project and I had no idea of the amount of unhappiness it was going to cause me with things like this!

Unknown said...

A helpful article on using DisplayGroups:
http://zendgeek.blogspot.com/2009/07/zend-form-display-groups-decorators.html