LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Thursday, April 9. 2009

Rendering Zend_Form decorators individually

In the previous installment of this series on Zend_Form decorators, I looked at how you can combine decorators to create complex output. In that write-up, I noted that while you have a ton of flexibility with this approach, it also adds some complexity and overhead. In this article, I will show you how to render decorators individually in order to create custom markup for your form and/or individual elements.

Once you have registered your decorators, you can later retrieve them by name from the element. Let's review our previous example:


$element = new Zend_Form_Element('foo', array(
    'label'      => 'Foo',
    'belongsTo'  => 'bar',
    'value'      => 'test',
    'prefixPath' => array('decorator' => array(
        'My_Decorator' => 'path/to/decorators/',
    )),
    'decorators' => array(
        'SimpleInput'
        array('SimpleLabel', array('placement' => 'append')),
    ),
));
 

If we wanted to pull and render just the "SimpleInput" decorator, we can do so using the getDecorator() method:


$decorator = $element->getDecorator('SimpleInput');
echo $decorator->render('');
 

This is pretty easy, but it can be made even easier; let's do it in a single line:


echo $element->getDecorator('SimpleInput')->render('');
 

Not too bad, but still a little complex. To make this easier, we introduced a shorthand notation into Zend_Form in 1.7: you can now render any registered decorator by calling a method renderDecoratorName(). This will effectively perform what you see above, but makes the $content argument optional and simplifies the usage:


echo $element->renderSimpleInput();
 

This is a neat trick, but how and why would you use it?

Many developers and designers have very precise markup needs for their forms. They would rather have full control over the output than rely on a more automated solution which may or may not conform to their design. In other cases, the form layout may require a lot of specialized markup -- grouping arbitrary elements, making some invisible unless a particular link is selected, etc.

Let's utilize the ability to render individual decorators to create some specialized markup.

First, let's define a form. Our form will capture a user's demographic details. The markup will be highly customized, and in some cases use view helpers directly instead of form elements in order to achieve its goals. Here is the basic form definition:


class My_Form_UserDemographics extends Zend_Form
{
    public function init()
    {
        // Add a path for my own decorators
        $this->addElementPrefixPaths(array(
            'decorator' => array('My_Decorator' => 'My/Decorator'),
        ));

        $this->addElement('text', 'firstName', array(
            'label' => 'First name: ',
        ));
        $this->addElement('text', 'lastName', array(
            'label' => 'Last name: ',
        ));
        $this->addElement('text', 'title', array(
            'label' => 'Title: ',
        ));
        $this->addElement('text', 'dateOfBirth', array(
            'label' => 'Date of Birth (DD/MM/YYYY): ',
        ));
        $this->addElement('text', 'email', array(
            'label' => 'Your email address: ',
        ));
        $this->addElement('password', 'password', array(
            'label' => 'Password: ',
        ));
        $this->addElement('password', 'passwordConfirmation', array(
            'label' => 'Confirm Password: ',
        ));
    }
}
 

Note: I'm not defining any validators or filters at this time, as they are not relevant to the discussion of decoration. In a real-world scenario, you should define them.

With that out of the way, let's consider how we might want to display this form. One common idiom with first/last names is to display them on a single line; when a title is provided, that is often on the same line as well. Dates, when not using a JavaScript date chooser, will often be separated into three fields displayed side by side.

Let's use the ability to render an element's decorators one by one to accomplish this. First, I'll note that I did not set any explicit decorators for the given elements. As a refresher, the default decorators for (most) elements are:

  • ViewHelper: utilize a view helper to render a form input
  • Errors: utilize the FormErrors view helper to render validation errors
  • Description: utilize the FormNote view helper to render the element description (if any)
  • HtmlTag: wrap the above three items in a <dd> tag
  • Label: render the element label using the FormLabel view helper (and wrap it in a <dt> tag)

Also, as a refresher, you can access any element of a form as if it were a class property; simply reference the element by the name you assigned it.

Our view script might then look like this:


<?php
$form = $this->form;
// Remove <dt> from label generation
foreach ($form->getElements() as $element) {
    $element->getDecorator('label')->setOption('tag', null);
}
?>
<form method="<?php echo $form->getMethod() ?>" action="<?php echo
    $form->getAction()?>"
>
    <div class="element">
        <?php echo $form->title->renderLabel() . $form->title->renderViewHelper() ?>
        <?php echo $form->firstName->renderLabel() . $form->firstName->renderViewHelper() ?>
        <?php echo $form->lastName->renderLabel() . $form->lastName->renderViewHelper() ?>
    </div>
    <div class="element">
        <?php echo $form->dateOfBirth->renderLabel() ?>
        <?php echo $this->formText('dateOfBirth['day']', '', array(
            'size' => 2, 'maxlength' => 2)) ?>
        /
        <?php echo $this->formText('dateOfBirth['month']', '', array(
            'size' => 2, 'maxlength' => 2)) ?>
        /
        <?php echo $this->formText('dateOfBirth['year']', '', array(
            'size' => 4, 'maxlength' => 4)) ?>
    </div>
    <div class="element">
        <?php echo $form->password->renderLabel() . $form->password->renderViewHelper() ?>
    </div>
    <div class="element">
        <?php echo $form->passwordConfirmation->renderLabel() . $form->passwordConfirmation->renderViewHelper() ?>
    </div>
    <?php echo $this->formSubmit('submit', 'Save') ?>
</form>
 

If you use the above view script, you'll get approximately the following HTML (approximate, as the HTML below is formatted):


<form method="post" action="">
    <div class="element">
        <label for="title" tag="" class="optional">Title:</label>
        <input type="text" name="title" id="title" value=""/>

        <label for="firstName" tag="" class="optional">First name:</label>
        <input type="text" name="firstName" id="firstName" value=""/>
       
        <label for="lastName" tag="" class="optional">Last name:</label>
        <input type="text" name="lastName" id="lastName" value=""/>
    </div>

    <div class="element">
        <label for="dateOfBirth" tag="" class="optional">Date of Birth
            (DD/MM/YYYY):</label>
        <input type="text" name="dateOfBirth[day]" id="dateOfBirth-day" value=""
            size="2" maxlength="2"/>
        /
        <input type="text" name="dateOfBirth[month]" id="dateOfBirth-month"
            value="" size="2" maxlength="2"/>
        /
        <input type="text" name="dateOfBirth[year]" id="dateOfBirth-year"
            value="" size="4" maxlength="4"/>
    </div>

    <div class="element">
        <label for="password" tag="" class="optional">Password:</label>
        <input type="password" name="password" id="password" value=""/>
    </div>

    <div class="element">
        <label for="passwordConfirmation" tag="" class="optional">Confirm
            Password:</label>
        <input type="password" name="passwordConfirmation"
            id="passwordConfirmation" value=""/>
    </div>

    <input type="submit" name="submit" id="submit" value="Save"/>
</form>

Which looks like the following screenshot:

Demographics form

Maybe not truly pretty, but with some CSS, it could be made to look exactly how you might want to see it. The main point, however, is that this form was generated using almost entirely custom markup, while still leveraging decorators for the most common markup (and to ensure things like escaping with htmlentities and value injection occur).

By this point in the series, you should be getting fairly comfortable with the markup possibilities using Zend_Form's decorators. In the next installment, I'll revisit the date element from above, and demonstrate how to create a custom element and decorator for composite elements.

Also in this series:

  • The simplest Zend_Form decorator
  • From the inside out: How to layer decorators
Posted by Matthew Weier O'Phinney in PHP at 09:28 | Comments (7) | Trackback (1)
Defined tags for this entry: decorators, php, zend framework
Related entries by tags:
Creating Re-Usable Zend_Application Resource Plugins
Quick Start to Zend_Application_Bootstrap
Real-time ZF Monitoring via Zend Server
Building RESTful Services with Zend Framework
Exposing Service APIs via Zend Framework

Trackbacks
Trackback specific URI for this entry

[ZF-8419] _isPartialRendering in Zend_Form_Element can break Zend_Form_Element_File::render()
We upgraded from 1.9.5, and have deployed every Zend upgrade since 1.7, which is the version we started development with. I understand that there is no way that the exception could not be raised if the decorator is missing, provided that Zend_Form_...
Weblog: JIRA: Zend Framework
Tracked: Nov 30, 04:01

Comments
Display comments as (Linear | Threaded)

At first I found Zend_Form confusing and difficult to work with. Now that I have been using it for 6 months I find it quite refreshing to not have to do all of the markup manually. I did not know about rendering each decorator individually, that should help some of the more complex layout I have to create.

Also, your link to the previous installment links to this post, not the previous post :-)
#1 Matt Lorey (Link) on 2009-04-09 10:42 (Reply)
Oops -- forgot to fill in the href. Corrected now, thanks!
#1.1 Matthew Weier O'Phinney (Link) on 2009-04-09 11:28 (Reply)
thanks, this gave me some good ideas on how to let the designers create a form layout and then generalize it for all the forms.
#2 Harro on 2009-04-09 11:09 (Reply)
This is a really handy method and has become my preferred method of building form markup ever since I heard about it. It should be noted, however, that some elements have pre-rendering logic which would be bypassed if the render() method of the element is never called. Examples are the checkbox, captcha and hash elements. One solution is to call $element->render() before calling any $element->renderX() method.
#3 Mon (Link) on 2009-04-10 11:42 (Reply)
Good point about the elements such as checkbox, but doesn't proposing you call render before every single element seem rather wasteful when the bulk of elements don't require pre-rendering logic?

I wonder if there is a more elegant solution to handle this, maybe Matthew has some suggestions or could be a feature suggestion to allow extra conditions in the magic render method to also call the pre-render logic.
#3.1 sck on 2009-04-12 19:38 (Reply)
Matthew, you even can not imagine how grateful I am to you for this article!!!
Many thanks, man!!!

I think that I will translate this article to Russian, if you not against!?

Write more! :-)
#4 Romiz on 2009-05-05 17:24 (Reply)
Matthew, thanks for this article. I am using Zend_Dojo_Form
and i would like to render dojo form attribute (execute) in view script, How could i render the following attribs in view script
/* this script is in my form
$this->addAttribs(array('execute' => 'validateForm(this)','name' => 'empForm')); */
/* this is in view script
execute="*/
if i add like this it is not validating

thanks
#5 dhayalan on 2009-07-24 10:47 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back February '10 Forward
Mon Tue Wed Thu Fri Sat Sun
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Paul M. Jones
  • Mike Naberezny
  • Shahar Evron
  • Planet PHP
  • Zend Where I now work
  • Garden.org Where I once worked

Archives

February 2010
January 2010
December 2009
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml best practices
xml books
xml conferences
xml cw09
xml decorators
xml dojo
xml dpc08
xml file_fortune
xml git
xml linux
xml mvc
xml oop
xml pear
xml perl
xml personal
xml php
xml phpworks08
xml programming
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zendcon09
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net