Using Smarty Templates With PEAR HTML_QuickForm
Contents
- 1. Introduction
- 2. Basic forms
- 3. Some theory
- 4. A simple template form
- 5. Intelligent template processing
- 6. Practical considerations
- 7. Conclusions
A note from the author
I hope you find this guide, or tutorial, call it what you will, useful, and I also hope it saves you some time. If you do find it useful, I would be grateful if you could make a donation using the button below (and if it wasn't useful, mail me and tell me why not).
I receive a great deal of mail as a result of this article, much of it asking me to solve various PEAR/HTML_QuickForm/Smarty problems. Please bear in mind that the money that puts food on my family's table comes from the consultancy work that I do, so here's the deal: if you would like some help with any of the points discussed in this article, mail me and tell me what that help is worth to you. Quote any amount you like, and if I'm able to help you out you can make a PayPal donation by way of thanks. Fair enough?
Thank you
Keith Edmunds
1. Introduction
What this is and what it isn't
This page is a simple introduction to using Smarty templates with the PEAR HTML_QuickForm classes. It is by no means exhaustive; in fact, it covers a very small fraction of the total functionality of Smarty templates. It is also not definitive: that role is taken by the source code itself, which is of course always right. However, for the newcomer to Smarty templates, the following should be a useful foundation to build upon.
Pre-requisites
It is assumed that you have installed both PHP and PEAR, and that you are familiar with PHP and HTML. You should also be familiar with using PEAR's HTML_QuickForm classes. Help with PEAR can be found at the PEAR website; help with PHP can be found at the PHP website, and an introduction to using HTML_QuickForm can be found in my PEAR HTML_QuickForm Getting Started Guide.
This page is written from a practical perspective. If you want to get the most from it, you should copy the code examples and run them yourself. Quite apart from anything else, templates are about presenting data, and there is no example output present on this page. If you want to see templates in action, you'll need to run the examples for yourself. Finally, the code samples here are for illustrating the use of Smarty templates only. In other words, features such as validating data have been omitted in order to more clearly demonstrate the template features.
2. Basic forms
A brief review
Let's start with a simple form that does not use templates. Later, we will later we will modify it so that it does use a template.
Create the following file and place it in a directory accessible by your web server, and display it in a browser:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>A simple form</title>
</head>
<body>
<?php
require_once "HTML/QuickForm.php";
$form = new HTML_QuickForm('frmTest', 'get');
$form->addElement('header', 'hdrTesting', 'A simple form');
$form->addElement('text', 'txtFirstName', 'First name?');
$form->addElement('text', 'txtLastName', 'Last name?');
$form->addElement('text', 'txtAge', 'Age?');
$form->addElement('text', 'txtTelephone', 'Telephone number?');
$form->addElement('reset', 'btnClear', 'Clear');
$form->addElement('submit', 'btnSubmit', 'Submit');
if ($form->validate()) {
# If the form validates then freeze the data
$form->freeze();
}
$form->display();
?>
</body>
</html>
You should see the form displayed; filling in details and submitting it should redisplay the form and data in a static format. If this does not work, or if you do not understand why it works, then please review the PEAR HTML_QuickForm Getting Started Guide.
Notice the following about the file shown above:
- It contains both PHP/PEAR code and HTML
- There is no clear way to influence the format of the resulting HTML
3. Some theory
What are templates for?
As we have seen, by default forms devleoped with the PEAR HTML_QuickForm classes are displayed in very simple format, typically a label followed by the form element and with each element placed below the previous one. While perfectly functional, and arguably a good format for development work, the presentation lacks the finishing touches expected of a professional application.
One of the challenges with any kind of program-controlled HTML generation is separating the display elements from the programming code, and this is something that the PEAR classes do well. Templates allow the designer to create far more attractive or complex pages, and to dictate precisely where each element of data will appear. The designer works files which are almost pure HTML; the programmer works with pure PHP code and PEAR classes, and changes to one do not require changes to the other.
How Smarty templates work with HTML_QuickForm
The $form->display(); line above calls the HTML_QuickForms default
renderer. By replacing that line, far more control can be gained over the
resultant HTML. This also allows us to split the source file into two: a
PHP/PEAR code source file, and a template file.
The template file contains HTML with some additional tags. At the simplest level, these tags are just placeholders for data. The role of the renderer is to take the form data and package it in a format that can be used by the template object. The role of the template object is to parse the template file and replace the additional tags with the data from the form.
To create and render a form with a template is a multi-stage process:
- The form is created using PEAR classes in the same way as before
- A template object is created.
- A renderer object is created.
- The renderer generates HTML fragments for each element of the form, and these fragments, together with other infomation about the form, are collated in one large array.
- The array of form data is passed to the template object
- The template object then parses the template file, replacing the tags with the data from the array, and displays the resultant HTML.
4. A simple template form
A simple example
We will now modify the earlier file to use a template. We need a couple more
require_once lines at the top of the file, and we replace the
$form->display(); line with the template code. We can also strip out
all of the HTML; the resultant PHP file is shown below.
<?php
require_once "HTML/QuickForm.php";
require_once 'HTML/QuickForm/Renderer/ArraySmarty.php';
require_once 'Smarty.class.php';
$form = new HTML_QuickForm('frmTest', 'get');
$form->addElement('header', 'hdrTesting', 'Testing Smarty');
$form->addElement('text', 'txtFirstName', 'First name?');
$form->addElement('text', 'txtLastName', 'Last name?');
$form->addElement('text', 'txtAge', 'Age?');
$form->addElement('text', 'txtTelephone', 'Telephone number?');
$form->addElement('reset', 'btnClear', 'Clear');
$form->addElement('submit', 'btnSubmit', 'Submit');
if ($form->validate()) {
# If the form validates then freeze the data
$form->freeze();
}
// Create the template object
$tpl =& new Smarty;
$tpl->template_dir = '.';
$tpl->compile_dir = '/tmp';
// Create the renderer object
$renderer =& new HTML_QuickForm_Renderer_ArraySmarty($tpl);
// build the HTML for the form
$form->accept($renderer);
// assign array with form data
$tpl->assign('form_data', $renderer->toArray());
// parse and display the template
$tpl->display('smarty1.tpl');
?>
We also need a template file, which is shown below. This should be placed in the same directory as the PHP source file above.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- $Id: smarty-guide.tpl,v 1.1 2005/03/03 19:26:54 kae Exp $ -->
<html>
<head>
<title>Smarty template demonstration</title>
<style type="text/css">
{literal}
th {
text-align: right;
}
{/literal}
</style>
</head>
<body>
<h2>
{$form_data.header.hdrTesting}
</h2>
<form {$form_data.attributes}>
<!-- Display the fields -->
<table>
<tr>
<th>{$form_data.txtFirstName.label}</th>
<td>{$form_data.txtFirstName.html}</td>
<th>{$form_data.txtLastName.label}</th>
<td>{$form_data.txtLastName.html}</td>
</tr>
<tr>
<th>{$form_data.txtAge.label}</th>
<td>{$form_data.txtAge.html}</td>
<th>{$form_data.txtTelephone.label}</th>
<td>{$form_data.txtTelephone.html}</td>
</tr>
<!-- Display the buttons -->
<tr>
<td align="center" colspan="4">
{$form_data.btnClear.html} {$form_data.btnSubmit.html}
</td>
</tr>
<!-- Display the copyright -->
<tr>
<td style="font-size:11px; color: navy; text-align: center" colspan="4">
©2004 Tiger Computing Ltd
</td>
</tr>
</table>
</form>
</body>
</html>
The form displayed by the above code, while unlikely to win any design awards, does differ in display somewhat from the previous example. If you have problems making this work then check the following:
- Are the filenames the same as listed here?
- Are both files in the same directory?
- Are both files readable by your web server process?
- Is
/tmpwritable by your web server process?
Step by step: the PHP source
Notice the following differences when compared to the first version:
- The form is presented in two columns rather than one
- There is a static text copyright notice at the foot of the page
- The PHP file contains only PHP code
Let's analyse the files starting with the PHP source code. Other than the
extra require_once lines, the first change is when we instantiate a
Smarty template object:
// Create the template object $tpl =& new Smarty; $tpl->template_dir = '.'; $tpl->compile_dir = '/tmp';
After instantiation, we tell the template object where the template file can
be found (in this case the current directory, represented by a period), and
in which directory to place the compiled version of the template (in this case
/tmp). Any directories may be used so long as the web server process
has read access to the template directory and write access to the compile
directory, and indeed there is some sense in placing the template file in a
directory which is not directly accessible by the web server process.
Next, we instantiate a renderer, passing the template object as a parameter:
// Create the renderer object $renderer =& new HTML_QuickForm_Renderer_ArraySmarty($tpl);
The next step is to use the renderer to create the array containing information about the form, including the HTML fragments for each element. This is done with the following line:
// build the HTML for the form $form->accept($renderer);
The next step is to pass that array data to the template:
// assign array with form data
$tpl->assign('form_data', $renderer->toArray());
This assign statement creates a variable form_data within the
template, and populates it with the previously-defined array data.
Finally, we call the display method of the template object, which
causes it to parse the template file and substitute the placeholders in the
template with the actual values passed in the array. The resultant HTML is
then output:
// render and display the template
$tpl->display('smarty1.tpl');
The data array
Before we consider the template file, let's look in more detail at the array of data created by the renderer. It consists of a number of elements, some of which are themselves arrays. The following pseudo code shows the structure of the array:
array (
['frozen'] // whether the complete form is frozen
['javascript'] // javascript for client-side validation
['attributes'] // attributes for <form> tag
['hidden'] // html of all hidden elements
['requirednote'] // note about the required elements
['errors'] // An array as follows:
(
['1st_element_name'] // Error text for the 1st element
...
['nth_element_name'] // Error text for the nth element
),
['header'] // An array as follows:
(
['1st_header_name'] // Header text for the 1st header
...
['nth_header_name'] // Header text for the nth header
),
['1st_element_name'] // Array for the 1st element
...
['nth_element_name'] // Array for the nth element
);
where an element array has the form:
array(
['name'] // element name
['value'] // element value
['type'] // type of the element
['frozen'] // whether element is frozen
['label'] // label for the element
['required'] // whether element is required
// if element is not a group:
['html'] // HTML for the element
// if element is a group:
['separator'] // separator for group elements
['1st_gitem_name'] // Array for the 1st element in group
...
['nth_gitem_name'] // Array for the nth element in group
);
If we wish to examine this array populated with data, we can add extra line to the PHP file as shown below:
// render and display the template
$tpl->display('smarty1.tpl');
echo "<pre>";var_dump($renderer->toArray());echo "</pre>";
Now that we are familiar with the format of the data array we can examine the template file in detail.
Step by step: the template file
Most of the template file is simple HTML, and does not concern us here. The
various tags and commands for Smarty, however, are placed between braces (for
example, ). Let's examine these.
The first command, {literal}, appears near the top of the
file:
<style type="text/css">
{ldelim}literal{rdelim}
th {
text-align: right;
}
{ldelim}/literal{rdelim}
</style>
The {literal} tag, delimited by
{/literal}, simply means that the enclosed text is to be
taken literally, or in other words, is not to be interpreted by the Smart template.
This is necessary in this case because the text is an in-line style definition,
which requires the use of braces itself. Without the
{literal} tag, Smarty would try to interpret the
text-align: right; string as a Smarty tag.
The next command is {$form_data.header.hdrTesting}, which
forms the text of the level two header. Recall earlier that when we passed the form
data array to the template, we stored it in a variable called form_data
with the following line:
$tpl->assign('form_data', $renderer->toArray());
Within that array is an element called header, which is itself an array
keyed by the header element names, and with a data component comprising the text of
the header. Thus the line {$form_data.header.hdrTesting}
references the text of the header called hdrTesting which was passed to
the template in the form_data array.
The remaining tags in the template file simply access different elements of the
form_data array. Each element of the form, other than headers, has its
own array within the form_data array so, for example, the
txtFirstName element may be represented within the
form_data array as:
["txtFirstName"]=>
array(8) {
["name"]=>
string(12) "txtFirstName"
["value"]=>
string(5) "Keith"
["type"]=>
string(4) "text"
["frozen"]=>
bool(true)
["label"]=>
string(11) "First name?"
["required"]=>
bool(false)
["error"]=>
NULL
["html"]=>
string(62) "Keith"
}
In this example, form_data.txtFirstName.label would be "First name?",
and form_data.txtFirstName.html would be "Keith".
Finally, the template can include static data. In the example above, the copyright statement will always be output verbatim because it is embedded in the template file as HTML.
5. Intelligent template processing
Introduction
Whilst not trying to be a definitive or in-depth guide the use of Smarty templates (that role is best fulfilled by the Smarty website), there are some relatively simple features of Smarty templates which can make templates much more powerful, and this section looks at some of those features.
if-then-else processing
Smarty templates support if-then-else processing. We can modify our original template file as follows:
<h2>
{$form_data.header.hdrTesting}
{if $form_data.frozen}
<font color="red"> (frozen)</font>
{/if}
</h2>
The variable $form_data.frozen is a boolean value that is true when the
form is frozen. When the form is first displayed, the heading will be the
value of hdrTesting as defined in the PHP file; after the form has been
submitted the heading will have " (frozen)" appended in red text.
A more practical example would be to only display the form buttons when the form is not frozen, which we can do as follows:
<!-- Display the buttons --> {if not $form_data.frozen} <tr> <td align="center" colspan="4"> {$form_data.btnClear.html} {$form_data.btnSubmit.html} </td> </tr> {/if}
Notice that the {if} clause is closed with
{/if}. Smarty templates also support
{else} and {elseif}
keywords, which work as expected.
Local variables
It's possible to include local variables in a template. The following example sets the style of the labels according to whether or not the form is frozen. While this may not be a particularly useful thing to do, it does demonstrate how local variables may be used. The template file is the same as before except for the following changes:
<head>
<title>Smarty template demonstration</title>
<style type="text/css">
{literal}
th {
text-align: right;
}
.frozen {
color: red;
}
.thawed {
color: green;
}
{/literal}
</style>
</head>
{if $form_data.frozen}
{assign var="lbl" value="class=frozen"}
{else}
{assign var="lbl" value="class=thawed"}
{/if}
<body>
<h2>
{$form_data.header.hdrTesting}
</h2>
<form {$form_data.attributes}>
<!-- Display the fields -->
<table>
<tr>
<th {$lbl}>{$form_data.txtFirstName.label}</th>
<td>{$form_data.txtFirstName.html}</td>
<th {$lbl}>{$form_data.txtLastName.label}</th>
<td>{$form_data.txtLastName.html}</td>
</tr>
<tr>
<th {$lbl}>{$form_data.txtAge.label}</th>
<td>{$form_data.txtAge.html}</td>
<th {$lbl}>{$form_data.txtTelephone.label}</th>
<td>{$form_data.txtTelephone.html}</td>
</tr>
<!-- Display the buttons -->
Template comments
Comments may be placed in templates by enclosing them in {* and
*}. An example:
{* This is a comment *}
6. Practical considerations
Introduction
The example above has been kept simple in order to clearly illustrate the points being made. However, in reality there are a few extra items we should include. For example, as it stands there is no indication to the user of which fields are required, something we have taken for granted when using the default renderer. There are some other housekeeping items that we need to take care of too.
Hidden fields and Javascript
Hidden fields are often used on forms, perhaps to hold record numbers or other contextual information. Many forms use Javascript to fulfil various fucntions. As it stands, neither hidden fields nor form Javascript will appear on our pages; however, that information is present in the array passed to the template object. We need to make the following changes to use that data:
<body> {if $form_data.javascript} <script language="javascript"> <!-- {$form_data.javascript} //--> </script> {/if} <h2> {$form_data.header.hdrTesting|upper} </h2> <form {$form_data.attributes}> <!-- Output hidden fields --> {$form_data.hidden} <!-- Display the fields -->
Required fields
In the default renderer, required fields are flagged with a red asterisk, and a note is placed at the foot of the form indicating that such fields are required.
When building our own templates, we have to ensure that we include code to
flag and explain required fields (if we want that functionality). Let's make
our Age field mandatory by making the following change to our PHP file:
$form->addElement('submit', 'btnSubmit', 'Submit');
$form->addRule('txtAge', 'Age is required', 'required');
if ($form->validate()) {
# If the form validates then freeze the data
$form->freeze();
}
When we display this form, we see no indication that the Age field is
required, and if we leave the Age field blank the form refuses to submit, but
we are not told why. To overcome these problems, we need to make some changes
to the renderer with the setRequiredTemplate method. This takes a
template as a text string argument, although of course we could read an
external template file by using PHP's file_get_contents() function.
For simplicity, we will specify the template directly.
The template recognises three Smarty variables:
-
{$label}- the label of the element in question -
{$html}- the HTML of the element -
{$required}- this will be true if this element is a required element
An example should make this clear. Modify the PHP source as follows:
$renderer =& new HTML_QuickForm_Renderer_ArraySmarty($tpl);
$renderer->setRequiredTemplate(
'{if $error}
<font color="red">{$label}</font>
{else}
{$label}
{if $required}
<font color="red" size="1">*</font>
{/if}
{/if}'
);
// build the HTML for the form
$form->accept($renderer);
The code says that if there is an error, the element label should be printed in red. If there is not an error then print the label as usual, and if this is a required element then print a small red asterisk after the label.
If we display the form now, we will see that the Age field is flagged with a
red asterisk, and if we submit the form without a value in the Age field then
the label is printed in red. However, we are still not told either what the
red asterisk means nor what the error is if we leave the Age field blank.
Error reporting
Similar to the setRequiredTemplate method, the renderer also has a
setErrorTemplate. In addition to the three Smarty variables supported
by setRequiredTemplate, the setErrorTemplate method also
supports the variable. This holds the error text associated
with the element as defined in the second argument of the addRule
method of the form.
Further modify the PHP source as follows:
$renderer->setRequiredTemplate(
'{if $error}
<font color="red">{$label|upper}</font>
{else}
{$label}
{if $required}
<font color="red" size="1">*</font>
{/if}
{/if}'
);
$renderer->setErrorTemplate(
'{if $error}
<font color="orange" size="1">{$error}</font><br />
{/if}
{$html}'
);
// build the HTML for the form
$form->accept($renderer);
This code says that if there is an error then the error message should be output in a small, orange font followed by a linebreak. The element itself is always output, so the total effect is that any elements with errors have the error message output in orange above them.
Finally, we still need to explain to our user exactly what the red asterisk
(or whatever marker we have chosen) means. We do this by adding the
requirednote field to the template. Modify the template file as
follows:
<!-- Display the buttons -->
{if not $form_data.frozen}
<tr>
<td align="center" colspan="4">
{$form_data.btnClear.html} {$form_data.btnSubmit.html}
</td>
</tr>
{/if}
{if $form_data.requirednote and not $form_data.frozen}
<tr>
<td> </td>
<td valign="top">{$form_data.requirednote}</td>
<td> </td>
<td> </td>
</tr>
{/if}
<!-- Display the copyright -->
This will display the default "requirednote" text, but we can customise it in the PHP file if we wish:
$renderer->setErrorTemplate(
'{if $error}
<font color="orange" size="1">{$error}</font><br />
{/if}{$html}'
);
$form->setRequiredNote(
'<font color="red" size="2">
*
</font>
<font color="blue" size="2">
This field is mandatory
</font>');
// build the HTML for the form
6. Conclusions
This guide has only scratched the surface of what Smarty can do. Often, however, the hardest part is getting started and I hope this guide will get you past that stage. A good next step would be to read through the Smarty documentation to understand what else Smarty can do, and identify those areas of interest to you.
Keith Edmunds