7 Days of Symfony 1.1 - Forms, Widgets and Validators (Day7)
Formatting your forms with custom Symfony decorators
posted on Dec 24th
Note: This article is up to date through revision 7136. No promises after that - but I will try to stay up to date.
After 6 days, we've made several forms, handled their processing, and investigated nearly all the widgets and validators. Today we turn our focus to a silent, but important, aspect of the forms: the decorator.
The job of the decorator is simple: To format the labels and form elements. Let's imagine our form without them. Imagine we have 3 input text fields: first_name, last_name and address. Imagine that we echo our form exactly how we have been throughout this seven day tutorial:
When this is called, our form iterates through its widgets and renders their html. Without decorators, the output would like something like this:
The truth is, I'm being too kind, I included line breaks after every input element, which wouldn't necessarily even be there without decorators. Now, there's nothing wrong with the above output. With the proper wrapping div and css, we could make this work quite nicely. Of course our job would become much more difficult when you picture field-specific error messages sandwiched inside the code. Thus, the need to "decorate" our forms is born.
Symfony's new form system comes standard with 2 decorators (known as form formatters inside Symfony) out of the box. The first, and default decorator, is called 'table' and (you guessed it) centers around the use of tables. The second, called list, uses lists to present the table. The former is the sfWidgetFormSchemaFormatterTable class and the latter the sfWidgetFormSchemaFormatterList class. Each of these inherit from the base "form formatter" class called sfWidgetFormSchemaFormatter. Let's take a look at how we would change our existing forms to use either the table (already used by default) or the list decorators.
Only one of the two above bottom lines are necessary. I simply added the second line to illustrate that the decorator is simply an option (remember widget options?) on the widget schema (which inherits from the sfFormWidget class).
So, probably like many of you, I have a slightly different idea of how forms should be formatted. In particular, there are 4 parts of each widget that we need to decorate:
- the label
- the form element itself
- the validation error (when it shows)
- the optional helper text (which, to this point, I've neglected to mention)
The "creation" of a decorator is as simple as defining the following variables (printed here with the default values for the table decorator":
%help%', $errorRowFormat = "
- \n%errors%
Now, there are two true ways to create a custom formatter. The first would be to use setter methods in the following way.
So on and so forth. Each of the above properties have a setter with which you can modify your decorator right alongside your form modifications. However, since we want to define a new type of decorator, and we don't want to have to remodify it for each form we create, we're going to a new decorator class that we can easily apply to any form. Now, since I haven't seen anyone else do this yet, I'm going to have to guess at exactly where Fabien intends this class to be located. First, let's suppose I have a field called "first_name." Let's define what we'll want the ending form row to look like:
-
Required
You get the idea. Next, let's set everything up. To start, let's simply mirror the table form formatter as our custom decorator, and get our author form to being using it. Since I haven't seen a custom validator created yet, I don't know exactly where Fabien intends for the custom class to be located. I'll follow the Symfony library example, but it may not be perfect. Create a new directory in your project lib/widget and new file in that directory called myWidgetFormSchemaFormatterCustom.class.php. In that file:
%help%', $errorRowFormat = "
- \n%errors%
Let's next hook up our author form from previous days to use our new decorator. Notice above that I've placed the word "custom" inside the
Make sure you've cleared your cache, or our new form formatter class won't be autoloaded. What you should see is the same form, except with the word "custom" starting every row. That's proof that our custom decorator is being used.
All decorators are essentially the same thing - simple overrides of the above 7 protected fields that define how a row of a form is printed. Using our custom decorator is just as simple. First, we construct our new $decorator object by calling the constructor of our new decorator class. This takes no arguments, and serves no purpose. Next, we use the addFormFormatter function of our widget_schema to make the widget_schema notice our new decorator. This method's first argument is a name for the decorator, so that we can refer to it later. The final step is to use the setFormFormatterName method to set the name of the decorator to use to the name we just gave our custom form formatter. In this example, the name is "custom". The placement and naming of our class is unimportant - I just tried to stay consistent with the Symfony library example.
The only thing left to do is actually configure our class properties so that our output matches our target output listed above.
- \n%errors%
Hopefully the above descriptions helped out. The truly important fields are $rowFormat, $helpFormat, $errorListFormInARow, and $errorRowFormatInARow. The others are important too, but don't always come in to play (they're not used in our examples). If you don't really need to override a value, simply omit it to use the parent's value. In our example, we could've omitted $errorListFormatInARow, $errorRowFormatInARow, and $namedErrorRowFormatInARow, as the above values are default.
Now, to pretty up our form a bit, let's add in some css:
That's it! Of course, none of the above css is required - but it gives you an idea of what I was going for. You can now take your custom decorator and modify the format to fit the form formatting to which you're accustomed.
Well, that's all I have for you. I hope the previous seven days have been informative. I tried to cover everything important, without weighing down with things that are more advanced (and will come later!). Two things I DID neglect to cover in a timely fashion included the csrf token (which prevents cross-site request forgery attacks) and the helper text that you can apply to each element. These are both covered throughout the tutorial, but were given less emphasis than I originally intended.
So what other advanced things will we be doing in the future. These things might include the aforementioned embedding of forms or the re-ordering of widgets inside our widget schema (so that they display in a different order). There are also a few schema validators which I chose not to cover. We'll also probably see many different attempts at sufficiently replacing the form_tag helper so that the form tag is once again a function (and not hand-written). This will take advantage of the form's isMultipart() function and vary greatly depending on the need of ajax and the current javascript library. By the way, that's another big change for Symfony - no more javascript helpers. They've been purposely dropped by Symfony so that Symfony is no longer prototype/scriptaculous - dependent. No worries though, where's there is a void, the Symfony community will fill it. I imagine that several groups - Prototype, jQuery etc - will be creating a library (widgets?) to handle the job that javascript helpers once held.
Thanks for reading!
Recent Articles
- Still writing old school php? At least hide your .php extension
- Adding Files to Subversion without Ignoring svn:ignore
- Symfony 1.1 Forms - The Evolution of Functionality
The Articles
- PHP (1)
- Symfony (11)
- Linux (4)
- CFA (0)
- Websites for People (1)