01.19.08

Symfony 1.1 Forms - Customizing your Form in the View (Part1)

Posted by ryan in symfony


Note: This article is up to date through revision 7136. No promises after that - but I will try to stay up to date.

Note: This article is written as a followup to the series of articles written on this site called "7 Days of Symfony 1.1 - Forms, Widgets and Validators". One of my regrets on that series is not covering topics related to customization and flexibility of your form in the view template.

If you've read my series on the Symfony 1.1 Forms, then you'll be familiar with the look and feel of every template file that I created:

<form action="<?php echo url_for('/articles/editAuthor'); ?>" method="post">
	<table>
		<?php echo $form; ?>
		<tr>
			<td><input type="submit" action="submit form"/></td>
		</tr>
	</table>
</form>

This worked great for a basic form - one whose job is, and only is, to populate the data of one of our model objects. However, life is almost never that clean. Let's introduce a new example today.

schema.yml

propel:
  article:
    _attributes:      {phpName: Article }
    id:
    author:             varchar(255)
    body:               longvarchar
    approved:         boolean

Here's the idea. We have a site where people can come and post articles that they've written. After they've submitted them, an administrator checks them and changes their status to approved if he/she believes the content should be included in the site.

We're not going to concern ourselves with the creation of the form to write or edit an article - that's fairly basic and covered sufficiently in the 7 Days of Symfony Forms series. Instead, we're going to create the form that the administrator will use to approve the articles. So next, let's make a few fake articles. To do this, I created a fixtures directory in my project data directory, and created a new file called articles.yml:

articles.yml

Article:
  article1:
    author:            Ryan Weaver
    body:              "This is the body of my first article. It's a great article"
    approved:        false
  article2:
    author:            Ryan Weaver
    body:              "This is the body of my second article. It's not such a great article"
    approved:        false

Be sure to not only rebuild your model, but load the data into the db via: symfony propel-load-data fo (or whatever your environment is called).

Let's create a module called article where we'll administer our articles. I'm not going to cover security here, but obviously you'd want that on a module such as this.

class articleActions extends sfActions
{
  public function executeApprove()
  {
    $articles = ArticlePeer::doSelect(new Criteria());
    $article = array_shift($articles);
    $this->form = new ArticleForm($article);
    if ($this->getRequest()->isMethod('post'))
    {
    	$this->form->bind($this->getRequestParameter('article'));
    	if ($this->form->isValid())
    	{
    		$values = $this->form->getValues();
    		$this->form->getObject()->setApproved($values['approved']);
    		$this->form->getObject()->save();
    	}
    }
  }
}

There's one major thing that separates this code form the form seen in the Symfony 1.1 Forms article series. Since we're only rendering one field (the approved field), we can't simply do:

	$this->form->updateObject();
	$this->form->save();

Because our name and body fields are missing in our form which would cause them to be set to a blank value. We really only want to update that one field, so we do so by manually updating our object and saving.

Also, I'm temporarily cheating. I've decided to have this page (for now) simply handle the approval of our first article. To do that, I use the array_shift to get the article.

One new thing above is passing the object $article to the ArticleForm constructor. Doing this simply binds the current data to the form. Very simply, you do this when you want to edit an existing object. To create a new object, we would call the new ArticleForm without an object parameter.

approveSuccess.php

<form action="<?php echo url_for('article/approve'); ?>" method="post">
	<table>
		<tr>
			<th>Author</th>
			<th>Body</th>
			<th>Approved?</th>
		</tr>
		<?php include_partial('article_form', array('form'=>$form)); ?>
		<tr>
			<?php echo $form['_csrf_token']; ?>
			<td><input type="submit" value="update status" />
		</tr>
	</table>
</form>
_article_form.php

<tr>
	<td><?php echo $form->getObject()->getAuthor(); ?></td>
	<td><?php echo $form->getObject()->getBody(); ?></td>
	<td><?php echo $form['approved']->render(); ?></td>
</tr>

The result:
forms-template-customization-simple-form.jpg

The form itself is simple. For the most part, we just create the table like we always have. I included the row as a partial because we'll eventually want to handle more than one article on this page. Inside the partial, we see a brand new way of printing out part of our form:

<?php echo $form['approved']->render(); ?>

Very simply, this renders our form element (in this case a checkbox). It still takes care of everything for us, like being checked or not checked in the right situations and having the appropriate name and id. What about the label field that normally comes with our form element. Well, we obviously don't need it in this case since the header on the table aptly explains what this field does. However, if we wanted to, we could print out both the label and the form element.

Different ways to render a form element include:

$form['element_name']->render() 					// renders just the form element itself (e.g. the input tag)
$form['element_name']->renderLabel()			// renders just the label element
$form['element_name']->renderRow()				// renders the full decorated row (which includes both the element and label)

And...
$form['element_name']->renderError()			// renders the error if there is one
$form['element_name']->renderLabelName()	// renders just the name of the label

Other good things to know

$form['element_name']->getValue()					// returns the current value of the field
$form['element_name']->getWidget()				// returns the widget so that you can modify it (e.g. change options or attributes)

So, there's a little reference manual to what you can do. In our case, we simply need the render() method. Incidentally, we don't actually even need the render element, as doing the following calls the render() method automatically:

$form['approved']; //__toString() method calls the render() method automatically

So, with the above code, we get our functional form. You can click and unclick the check box, submit the form, and have it load up with the proper value.

			<?php echo $form['_csrf_token']; ?>

You may have noticed the above code. Since we didn't echo the entire form, we were missing the very importand _csrf_token. We render it here so that our form will validate.

At this point there's one big glaring limitation of our page: It only allows us to approve ONE article! How do we fix this? See part 2 (coming Jan 20) of this article to find out. Also, you'll learn about another new feature: embedForm().

Thanks for the shares!
  • StumbleUpon
  • Sphinn
  • del.icio.us
  • Facebook
  • TwitThis
  • Google
  • Reddit
  • Digg
  • MisterWong