Home > PHP > Building table based forms in Zend_Form

Building table based forms in Zend_Form

In web applications it is fairly common to want to edit multiple rows of the same kind of information, formatted into a table, kind of like you can in a spreadsheet. These forms are generally for editing existing information pulled from a database, which is usually already in an array format. Most of the examples I can think of at the moment need explanations of the context to make much sense, so I’ll pick an example that isn’t as likely to be useful but doesn’t need explanation.

Suppose I have a table in my database that lists contacts with title, first name, last name, and so on. When I fetch the data from the database, I’m likely to have an array that looks something like this:

$array[0]['id']    = 1005;
$array[0]['title'] = 'Mr.';
$array[0]['first'] = 'Ted';
$array[0]['last']  = 'Smith';
$array[1]['id']    = 1006;
$array[1]['title'] = 'Mr.';
$array[1]['first'] = 'Mark';
$array[1]['last']  = 'Jones';
$array[2]['id']    = 1007;
$array[2]['title'] = 'Ms.';
$array[2]['first'] = 'Sally';
$array[2]['last']  = 'Adams';

I want to get this into a form structured as a table, that submits the whole thing as one form. So that the posted results like this:

$contacts[1005]['title'] = 'Mr.';
$contacts[1005]['first'] = 'Ted';
$contacts[1005]['last'] = 'Smith';

I want the array to look like this because its much easier to handle the results than if we used the actual field name or id as the variable name.

I couldn't find anything in the Zend Framework documentation, lists, or various tutorials that explained how to do this very clearly, so I thought I'd post at least one way to accomplish this in Zend Framework. This is based on 1.5. I'm going to start by showing the whole thing together. In practice it might not appear this way in the context of your controller class, but hopefully this is clear enough to understand how to apply it. After I show the whole thing I'll walk through it and explain the pieces.

$form = new Zend_Form();
$form->setMethod('post')
     ->setAttrib('id', 'contactForm');
$subForm = new Zend_Form_SubForm();

foreach($array as $rownum => $row){
  $id = $row['id'];
  $rowForm = new Zend_Form_SubForm();
  foreach($row as $key => $value){
    if($key == 'id') continue;
    $rowForm->addElement(
      'text',
      $key,
      array(
        'value' => $value,
      )
    );
  }
  $rowForm->setElementDecorators(array(
    'ViewHelper',
    'Errors',
    array('HtmlTag', array('tag' => 'td')),
  ));
  $subForm->addSubForm($rowForm, $id);
}

$subForm->setSubFormDecorators(array(
  'FormElements',
  array('HtmlTag', array('tag'=>'tr')),
));

$form->addSubForm($subForm, 'contacts');

$form->setSubFormDecorators(array(
  'FormElements',
  array('HtmlTag', array('tag' => 'tbody')),
));

$form->setDecorators(array(
    'FormElements',
    array('HtmlTag', array('tag' => 'table')),
    'Form'
));

$form->addElement(
  'submit', 'submit', array('label' => 'Submit'));

$form->submit->setDecorators(array(
    array(
        'decorator' => 'ViewHelper',
        'options' => array('helper' => 'formSubmit')),
    array(
        'decorator' => array('td' => 'HtmlTag'),
        'options' => array('tag' => 'td', 'colspan' => 4)
        ),
    array(
        'decorator' => array('tr' => 'HtmlTag'),
        'options' => array('tag' => 'tr')),
));

As you can see, this approach uses subforms, which allows us to automatically build the array in the form, as well as having a convenient structure for applying decorators. In order to get the array right, we use two layers of subforms (subForm for $contacts, and rowForm to create each row of the table). I couldn't figure out a good way to not do that and still get the array I wanted in the html. setElementsBelongTo doesn't quite do the trick.

$form = new Zend_Form();
$form->setMethod('post')
     ->setAttrib('id', 'contactForm');
$subForm = new Zend_Form_SubForm();

This just sets up the form, tells the form it should post and have an id of 'contactForm'. You might note that I didn't set an action for the form. Zend_Form does set the action, but leaves the value blank. This conveniently posts the form back to our action (supposing all the browsers handle blank actions as specified in the relevant RFCs). The $subForm line just creates our first subForm for use later.

foreach($array as $rownum => $row){
  $id = $row['id'];
  $rowForm = new Zend_Form_SubForm();
  foreach($row as $key => $value){
    if($key == 'id') continue;
    $rowForm->addElement(
      'text',
      $key,
      array(
        'value' => $value,
      )
    );
  }
  $rowForm->setElementDecorators(array(
    'ViewHelper',
    'Errors',
    array('HtmlTag', array('tag' => 'td')),
  ));
  $subForm->addSubForm($rowForm, $id);
}

For each row in our data we create a subForm, which we populate with an element for each field in our data, setting the value for each to the value from our data. We decorate this subForm in the loop, wrapping each element in a td tag. Note that we don't need to worry about the name of the element. We just set it to our data field name, and the framework's subForm handling deals with the rest. Of course in practice your fields might not all be simple text fields, so the actual code would get more complicated but the process is the same. I also should mention that in practice you almost certainly want to check that $array is actually an array before you do this, or you'll be looking at lots of warnings (at least) in your logs.

$subForm->setSubFormDecorators(array(
  'FormElements',
  array('HtmlTag', array('tag'=>'tr')),
));

$form->addSubForm($subForm, 'contacts');

$form->setSubFormDecorators(array(
  'FormElements',
  array('HtmlTag', array('tag' => 'tbody')),
));

Now that we have a rowForm for each data row added to our subForm, we can decorate each of these with a tr tag, and assign the whole thing back to the parent form. This is where we tell Zend_Form that the parent should be 'contacts', so that gets in our result array. Then we decorate the contacts subForm with the tbody tag to get it nicely fit into the table.

$form->setDecorators(array(
    'FormElements',
    array('HtmlTag', array('tag' => 'table')),
    'Form'
));

We need to decorate the whole form with a table tag. This likely needs a class and id, but we leave it plain in this example.

$form->addElement(
  'submit', 'submit', array('label' => 'Submit'));

$form->submit->setDecorators(array(
    array(
        'decorator' => 'ViewHelper',
        'options' => array('helper' => 'formSubmit')),
    array(
        'decorator' => array('td' => 'HtmlTag'),
        'options' => array('tag' => 'td', 'colspan' => 4)
        ),
    array(
        'decorator' => array('tr' => 'HtmlTag'),
        'options' => array('tag' => 'tr')),
));

Chances are we want a submit button, so here we add it to the bottom, and decorate it with its own row in the table.

The order of a surprising amount of this is arbitrary. You could set the table tag toward the top, for example, if it flows better for you. There are, of course, also completely different ways to do this. Several of the examples in the manual extend Zend_Form rather than do it the way that I have here. To me, this seems more clear.

[2010-02-06 I edited this slightly to fix some formatting issues introduced by a WordPress migration.  After almost two years this post still gets visits, and I figured maybe it should be readable.]

About these ads
Categories: PHP Tags:
  1. August 17, 2008 at 3:10 pm

    Hey! I found your code snippets useful in creating a more generic method for generating tables. Click on the website link to see the code.
    I should note that I subclass Zend_Form to Project_Form and all my forms are created by doing Project_Form_SomeFormName (that subclasses Project_Form).
    So, you’d be calling it in Project_Form_SomeFormName.

  2. August 25, 2008 at 2:43 pm

    Awesome tutorial David.

    I found this post just in time :)

  3. Frank
    October 6, 2008 at 3:32 am

    Thank YOU! — The Zend Framework Documentation on Sub Forms and array Notations Really SUCKS,

  4. October 28, 2008 at 12:41 pm

    I am trying to setup an entry page for addresses and assets. I would like the labels to be in the tablehead and the entry fields to be rows of the table.

    I’ve been trying to wrap my noggin around how to do this with Zend_Form, and just realized that there is nothing requiring us to create forms with Zend_Form, is there?

    I see it as a choice between coding the form as it was before the framework, or bending an unwieldy artificial construct around site requirements. The framework should make it easier to code, not harder. If it’s not cooperative, don’t use it. Granted, it might be more difficult to maintain, but it might be easier than the artificial construct. If the Zend_Form improves in the next couple of releases, It should be easy enough to go back and change it to fit the construct.

  5. November 4, 2008 at 6:33 am

    One of the strengths of the Zend Framework is that you can mix and match. Use the parts that work for you, and use something else or some other approach if it doesn’t.

    Having said that, consistency does mean something. Presumably you are writing something that will stick around for a few months. Somebody (maybe you, maybe someone else) is going to come along after several months and try to figure out how your app works. Sticking with the framework at least gives them some boundaries in figuring out what you’ve done. Of course, clear code and comments are even more important.

    Over the years I have written a lot of code that was a short cut because I couldn’t figure out a quick way to get what I wanted from a framework, then wished later I had taken the time to do it using the framework.

  6. January 6, 2009 at 9:50 am

    good article!
    it’s a pity but there are no good documentation with table form and Zend_Form examples.
    thanks!

  7. January 6, 2009 at 5:45 pm

    How about a customisable view phtml for Zend_Form? Without setDecorator()

    http://www.yu.id.au/2008/12/customisable-zend-form/

  8. niusha
    August 9, 2011 at 8:29 am

    Thanks a lot for your tutorial, it helped me a lot.

    I have just one question, already asked by Jeff :

    Jeff :
    I am trying to setup an entry page for addresses and assets. I would like the labels to be in the tablehead and the entry fields to be rows of the table.

    Is there a simple way to show the labels on the header of the table ?

    Thank you.

    Niusha

    • David
      August 9, 2011 at 8:57 am

      I haven’t actually used the approach outlined in the post in quite a while. I now generally make very simple forms and then decorate them using more css and javascript rather than trying to accomplish as much in the server’s HTML generation. However, to answer your question, the way I’d do it if I were using Zend Form decorators is just to fill row 0 of the array with the labels, and start the data on row 1. Then on line 19 of the larger code example block I’d change it so that where it says

      array(‘HtmlTag’, array(‘tag’ => ‘td’)),

      it would put a th tag if the row == 0.

      I haven’t tested this approach, and there are likely intricacies to implementing it that would need to be be ironed out, but I hope that helps.

      • niusha
        August 10, 2011 at 1:18 am

        Here is the solution I have found on http://paveldubinin.com/2011/04/real-table-based-form-with-zend-form/ :

        - Add a Decorator Class : /library/Form/Decorator/SimpleTable.php

        [lang=php]
        <?php
        
        class Form_Decorator_SimpleTable extends Zend_Form_Decorator_Abstract
        {
        	public function render ($content)
        	{
        		$element = $this->getElement();
        		$view	= $element->getView();
        		if (null === $view) {
        			return $content;
        		}
        		
        		$columns = $this->getOption('columns');
        		$class = $this->getOption('class');
        		$id = $this->getOption('id');
        
        		$columns_html = '';
        		foreach ($columns as $current_column_name) {
        			$columns_html .= '<th>'.$current_column_name.'</th>';
        		}
        
        		$result = '
        			<table class="'.$class.'" id="'.$id.'">
        				<thead>
        					<tr>
        						'.$columns_html.'
        					</tr>
        				</thead>
        				<tbody>
        					'.$content.'
        				</tbody>
        			</table>
        		';
        
        		return $result;
        	}
        }

        Add that line before the line 19 :

        $form->addPrefixPath('Form_Decorator', 'Form/Decorator/', 'Decorator');

        And replace the line 19 by :

        $form->setDecorators(array(
        				'FormElements',
        				array(	'SimpleTable',
        						array('columns' => array(
        							'', 'title', 'firstname', 'lastname'))), // '' do not display the id column name
        				'Form'));
      • David
        August 10, 2011 at 10:06 am

        Great! Thanks for posting that back here. I’m sure others will be looking for that.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: