Navigation

Keeping all parts of a website consistent is one of the most time consuming tasks of a web designer. Spending this time is worth doing, because the more consistent the look and feel of your website is, the more professional it appears to its users.

I will show you today how symfony could help you keeping all of your forms consistent with very little work left for you to do.

The concept presented in this article series is not implemented in symfony. It has been developed together with a few other people in the community. With these articles, I would like to receive feedback about how you like the concept and how it can be improved.

  1. Improving the Forms
  2. Improving the Forms: Field Groups
  3. Improving the Forms: Layouts and Formatters

A Quick Look Back

In my last post I introduced you to the concept of field groups. Field groups are similar to embedded forms in that they group a set of form fields together and enable combined data binding and validation. They differ from embedded forms in that they can combine multiple fields to enter one single value. I showed you the example of a password field with two fields, that is used to enter one single value: The password.

Here is the code for the user profile form that we have developed in the course of this article series:

// lib/form/doctrine/ProfileForm.class.php
class ProfileForm extends sfFormDoctrine
{
  protected $user = null;
 
  public function __construct(sfUser $user)
  {
    $this->user = $user;
  }
 
  public function configure()
  {
    $this->addField('name', new sfTextField(15))
         ->setHelp('Your full name, f.i. "John Travolta"');
    $this->addField('email', new sfEmailField())
         ->setHelp('A valid email address, f.i. "john.travolta@gmail.com"');
    $this->addField('password', new VerifiedPasswordField())
         ->setHelp('Must not be "pulpfiction"');
 
    $this->setFinalValidator(
      new sfValidatorCallback($this, 'comparePasswordAndName')
    );
 
    if (!$this->user->hasCredential('admin'))
    {
      $this->getField('name')->setEditable(false);
    }
  }
 
  public function comparePasswordAndName(sfValidator $validator, array $values)
  {
    if ($values['name'] == $values['password'])
    {
      throw new sfValidatorError('The password must not be the same as the name',
          'password');
    }
  }
}

I invite you to compare this form with the original symfony implementation of the same functionality shown in my first post.

Form Layouts

Today I want to present you another advantage of the field groups. You can use “layouts” on them to automate the generation of HTML code in a very flexible way.

Right now, symfony offers two alternatives to create the HTML code of your forms.

  1. Use one of the built-in form formatters “list” and “table”. These formatters are very unflexible because the whole form will be rendered in the same way. You cannot modify the formatters to render some parts as fieldsets, others as multi-column fields etc.
  2. Code the HTML by hand in your templates. This way gives the most power to web designers. Unfortunately it also leads to a lot of code duplication in your templates that cannot be easily resolved by using partials. Maintaining this duplicate code is quite repetitive, tedious, and error prone™ ;-)

Layouts help you to solve this problem. But what exactly are layouts?

In general, a layout is a set of different classes (the “formatters”) that specify how different parts of a form should be rendered. Look at the following sketch to get an idea of what layouts can do for you:

Layout examples

Usually, layouts render the individual fields in rows with the label to the left and the field to the right. You can change individual fields or field groups to be rendered in a different way: As fieldset (useful for field groups), with the label to the right (useful for checkboxes), with no label at all or with your very own custom formatter.

But layouts can do much more than formatting the individual rows: They also know how the formatted fields should be visually organized. You can, for example, display all fields in a horizontal row or organize them in a grid with multiple columns.

Each field group (the form is also a field group) has exactly one layout assigned that is used to organize the fields in this group. Because field groups can be nested within each other, you can also nest different layouts. Let’s look at a basic example that can be achieved very easily:

Profile form with layouts

The password field and the permissions field group are configured to be formatted as fieldset. The permissions field group itself has a multi-column layout applied. Later in this post I will show you how little PHP code is necessary to achieve this result.

Apart from the visual aspect, layouts specify which HTML code will be used to render the form. sfListLayout renders the form in an ul tag, sfTableLayout renders the form in a … guess what .. right, in a table tag. Of course you can also implement your own layout that renders the whole form in a combination of address and blockquote tags, if that makes sense to you (tag misuse at its best!).

Multi-Column Layouts

Layouts with multiple columns are especially interesting. They help you to display a lot of information in little, well-organized space.

If you use ul-tags to render your form, a multi-column layout can be achieved very easily. You just need to change the CSS of the li-tags to make them floating. By giving them a fixed width, you can control how many form fields are displayed in a row.

There is a big problem with this solution though: Fields are first rendered from left to right, then from top to bottom.

This can be a problem if your fields are sorted in a specific order. A good example are large lists of checkboxes. If the labels of these fields are sorted alphabetically, users expect them to be sorted from top to bottom, then from left to right.

Layouts with multiple columns

Layouts are able to deal with this problem easily. You can configure the number of columns and the order of the fields (“horizontal” or “vertical”) simply by passing these options to the layout’s constructor.

Show Me the Code

Configuring layouts in your form is easy. Every class inheriting from sfFormFieldGroup (thus also sfForm) offers the method setLayout(). To this method you can pass an instance of the layout you want to use.

  public function configure()
  {
    $this->setLayout(new sfListLayout());
  }

The different layout classes allow different options that you can pass to their constructor. Two such options are the number of columns and the order in which the fields are put into these columns.

  public function configure()
  {
    $this->setLayout(new sfListLayout(array(
      'columns' => 3,
      'order'   => sfLayout::VERTICAL,
    )));
  }

With as little code as this you have configured your form to organize all fields in 3 columns in a vertical order using ul and li-tags. You can achieve the same result using an HTML table by passing an instance of sfTableLayout instead of sfListLayout.

The format of the individual form fields can be modified by calling the method setFormat() on the field. To this method you pass the name of the format you want to use. The names of the different formats are declared in the layout class, as you will see later.

  public function configure()
  {
    $this->addField('password', new VerifiedPasswordField())
         ->setFormat('fieldset');
  }

After this simple definition, the password fields are rendered as in the picture above. Depending on the layout, there can be many more formats like “leftlabel” (the default), “nolabel” or “rightlabel”. Look at the first sketch in this post to see what these look like.

A More Complex Example

Now that we have learned about the basics, we can look at how to configure our user profile form to be displayed like in the above picture.

First, we tell our form to be rendered as HTML list.

  public function configure()
  {
    $this->setLayout(new sfListLayout());

Now we add the fields as we did before. Note the format that we have set on the password field to render it as a fieldset.

    $this->addField('name', new sfTextField(15))
         ->setHelp('Your full name, f.i. "John Travolta"');
    $this->addField('email', new sfEmailField())
         ->setHelp('A valid email address, f.i. "john.travolta@gmail.com"');
    $this->addField('password', new VerifiedPasswordField())
         ->setHelp('Must not be "pulpfiction"')
         ->setFormat('fieldset');

Just for fun, we’ll add a few checkboxes to our form to demonstrate how a multigrid layout is integrated.

    $permissions = array(
      'admin', 'blog-editor', 'content-editor',
      'publisher', 'file-editor', 'comment-editor',
    );
    $this->addField('permissions', new sfCheckboxList($permissions))
         ->setHelp('Choose one or more permissions for this user')
         ->setFormat('fieldset')
         ->setLayout(new sfListLayout(array('columns' => 3));

The last thing left is to declare the final validator and the user credential check again, but this code has not changed.

Let’s sum up what we have learned:

  1. A format is the definition of how a single field is rendered
  2. A layout is the definition of how a group of fields is organized

But What About the Web Designers?

I can hear voices screaming that I am violating the MVC separation with this proposal. But honestly, I am not. :-) We still have one specific component that is responsible for generating the output of our form: The sfLayout instance. You can simply modify the way your form is rendered by exchanging its layout, which is what MVC is all about.

Many people misunderstand the MVC concept as it is applied in symfony. Although the view layer consists mostly of templates, it is not restricted to that. MVC simply requires you to program separate decoupled layers that can be easily exchanged. Whether you put the code of these layers in templates, classes or coke cans does not matter.

The good news is that you can do all the configuration from within your templates, if you don’t like to do it in your configure() method. All of the above methods are accessible using sfForm‘s array access syntax. Together with a set of easy convenience methods, even web designers could configure the form’s format.

<?php $form->setLayout('list') ?>
<?php $form['password']->setFormat('fieldset') ?>
<?php $form['permissions']->setFormat('fieldset') ?>
<?php $form['permissions']->setLayout('list', array('columns' => 2)) ?>
 
<?php echo $form ?>

For small projects with one or only very few forms, I recommend you to not use layouts and formatters, if that eases the life of your web designers. You can still output the form the oldfashioned way and write all the HTML by hand.

sfLayout Internals

In the last part of our travel to the magic layout wonderland, I want to explain some of the internals of sfLayout to you.

The information in this section is only relevant when you create your own layout. You hardly ever need this information to simply configure your form.

All layouts must implement sfLayoutInterface.

interface sfLayoutInterface
{
  public function createFieldFormatter($format = null);
  public function createFieldGroupFormatter();
}

Layouts follow the Abstract Factory Pattern. That means that any class implementing sfLayoutInterface simply creates instances of other classes that are connected in some way.

Let’s visualize this very technical definition with a simple example: sfListLayout is able to create new sfFieldFormatter instances as well as sfFieldGroupFormatter instances. Because both instances must know that the form is rendered as ul-list, they structurally belong together. If you use a different layout, you get a different set of formatters that again know about each other. That’s what this pattern is about.

It is the responsibility of sfFieldFormatter and sfFieldGroupFormatter instances to turn the fields/field groups into HTML code.

sfLayoutInterface::createFieldFormatter() has one single parameter “format”. This format is exactly the format string that you have set on the form fields earlier using setFormat(). For each given format, the layout knows which object to return.

$layout = new sfListLayout();
$formatter = $layout->createFieldFormatter('rightlabel');
echo get_class($formatter);
 
// returns "sfRightLabelListFieldFormatter"

The great thing is that you never need to know about these long class names, because the layout you use hides them from you. All returned objects share the same interface: sfFieldFormatterInterface. You only ever need to know about this interface and never about the concrete class you receive.

interface sfFieldFormatterInterface
{
  public function render($id, $label, $value, $help, array $attributes = array());
}

Conclusion

Enough theory for today. I hope I was able to give you an idea of what power layouts can give to you. As you have seen, they can reduce code duplication in large applications with many forms and simplify the presentation of forms. There is much more to tell about layouts, but this post has grown long enough.

How do you like the layouts? Is this idea worth the time following?

Posted Thursday, April 23rd, 2009 at 13:01
Written by: | Filed Under Category: Thinking Ahead
You can leave a response, or trackback from your own site.

13

Responses to “Improving the Forms: Layouts and Formatters”

It definitely is worth the time. Symfony’s current form formatter system is close to being completely useless.

The separation of layout and formatter is a little confusing when seeing it for the first time, but of course it’s necessary.

One other thing became clear to me when reading your posts over again: your current proposal to field groups doesn’t allow just to put fields of an existing form into separated groups because then the values will become multidimensional. While this is of course intentional when dealing with “embedded” groups it does not make sense if you just want to break up a longer form into 2 or 3 parts for clarification (and that was my first intention). I think something in analogy to “mergeForm vs embedForm” should be devised at that point (e.g. defining if the group’s values will be merged with the rest of the form or just added as a sub-array).

This is brilliant. I for myself have to try it out in a real world example. How about releasing your test implementation as a symfony plugin labeld as “alpha release”?

Bernhard

@Klemens: I cannot really release the current implementation because it really is a prototype only. Many things don’t work, there is no exception handling, no documentation etc. I will create a usable implementation once I got the key concepts right.

Rubino

Very nice idea – you should consider implementing a server side version of what’s outlined in CSS3 – there’s some good approaches to layouts defined and some great examples.

There’s even a jQuery lib that implements it now for browsers.

Andrew Allison

Ah Man.. This is exactly what I’ve been looking for I’m working on a project with some very meaty forms and this would be Ideal… Shame….. Oh well back to hand coding 20+ fields D’oh :D

Duane Gran

I’ve really been enjoying this series you have been writing and I believe the proposals will really improve the form system in symfony, which is already quite good. I would be curious if you have ideas on how multiple part forms might be handled best. I posted some questions about it here:

http://forum.symfony-project.org/index.php/m/78775/

Often you need to break a form into segments that aren’t coincident with the schema but you still want validation at each step. I imagine a scenario where you can display a subset of form elements drawing from one or more models but only the subset of displayed form controls are validated. Is such a thing possible in symfony today?

I have read your posts with interest.

The reason i got your blog is this:
When coding an application in symfony 1.2 using the current forms, you often need a view / edit combination of the form data.

I have looked around for such a possibility, but have not been able to find anything that comes close. Please correct me if i’m wrong!

In the current situation you need to code the entire view by hand and in ‘view’ mode and use the forms in ‘edit mode despite the great similarities between two. Not to mention the overhead in template code.

I am thinking of a posibility to call echo $form->renderViewOnly() or something in the template. possibly extending the generator for forms or just extending the renderclasses.

Have you given any thought to this subject in you contemplations on Symfony forms?

Bernhard

@Alex: I did already write about this in my first post of this series:
http://webmozarts.com/2009/04/12/improving-the-forms/

You can find my suggestions by scrolling down to the headline “Editable vs. Non-Editable”.

Matthias

What is the state of this topic? Any response from Fabien about it?

I noticed that it or at least parts of it are implemented in the ullright project.

Well, when thinking ahead you also need to keep the past in mind. That said, I don’t think that the complete ideas from all these posts will never make it into symfony. That’s because they introduce too much (additional) magic. Something that Fabien doesn’t like and, to be honest, I also don’t see a real advantage because you loose the full control over what you are coding.

Apart from that I like the concept of “form field abstraction” very much and I also don’t understand why the concept of plain fields (editable/non-editable) was not introduced, yet.

So.. as a conclusion I think it is important to concentrate on the most important things that should be improved on the form framework and then start another RFC / ticket / or whatever and (most importantly) start a dialogue with Fabien.. otherwise this all will never lead to any useful results.. ;-)

Pete BD

@Alex and @Bernard: RE: non-editable forms. I guess that setting a field group (or a whole form for that matter) to non-editable (I prefer readonly) would filter down to all its child fields?
Then to show a form in a non-editable mode you would just add this in the configure() method of the form itself.

$this->setEditable(false)

Pete

Pete BD

@Duane: RE: Multi-part forms
Since sfForm is just a kind of fieldgroup, how about a form object (perhaps sfMultiForm) to which you add multiple sfForm objects?#

Then you could select which of the subforms to render/validate at any point in the process but still have access to the full submitted data at any point for processing/validation.

Pete BD

@Duane: RE: Multi-part forms
Since sfForm is just a kind of fieldgroup, how about a form object (perhaps sfMultiForm) to which you add multiple sfForm objects?

Then you could select which of the subforms to render/validate at any point in the process but still have access to the full submitted data at any point for processing/validation.

Pete BD

@David: In SilverStripe CMS they have a method on forms to return a flattened data structure for easily managing the values. So while getData() might return a nested array:

array(
‘group1′ => array(
‘name’ => ‘The name’,
‘email’ => ‘thename@gmail.com’,
),
‘password’ => array(
‘first’ => ‘The password’,
‘second’ => ‘The password’,
),
)

getValues() would return a flattened array
array(
‘name’ => ‘The name’,
‘email’ => ‘thename@gmail.com’,
‘password’ => ‘The password’,
)

But you notice that each fieldgroup type is able to say how its values are flattened. The password field only returns one value whereas the generic group returns two.

Leave a Reply

 

Additional Resources