D.4 Template Toolkit Language
The Template Toolkit implements a general-purpose
presentation language rather than a
general-purpose programming language. What that
means is that for general programming tasks, building backend
applications, database access, and so on, you should continue to use
Perl and the many fine modules available for use with it.
The strength of the Template Toolkit language is in building the
frontend—that is, the HTML that presents the output of an
application or displays the content of an XML file, the results of a
database query, the collection of snapshots of your pet camel, or
whatever it is that you're trying to do. It has many
constructs that are familiar in programming languages, such as the
use of variables
(GET, SET,
DEFAULT), conditional clauses
(IF, UNLESS,
ELSIF, ELSE, etc.), loops
(FOREACH, WHILE,
SWITCH, CASE), and exception
handling (TRY,
THROW, CATCH). However, these
are generally intended to be used from the perspective of layout
logic; that is, controlling how the output looks, not what the
underlying application actually does. To compliment these basic
operations, there are also various directives more specifically
oriented to gluing chunks of content together
(PROCESS, INCLUDE,
INSERT, WRAPPER,
BLOCK), for providing useful content-manipulation
tools (FILTER, MACRO), and for
the loading of external modules (USE) by which the
toolkit can easily and quickly be extended.
Although we are focusing on HTML in particular, it is worth pointing
out that the Template Toolkit is actually language-neutral. It
operates on text files (although it can be used to generate binary
files such as images or PDF documents), and as such, it
doesn't really care what kind of text
you're generating, be it HTML, XML, LaTeX,
PostScript, or an Apache httpd.conf
configuration file.
D.4.1 Simple Template Example
So without further ado, let's see
what a typical template looks like:
[% PROCESS header title="Some Interesting Links" %]
<p>
Here are some interesting links:
<ul>
[% FOREACH link = weblinks %]
<li><a href="[% link.url %]">[% link.title %]</a></li>
[% END %]
</ul>
</p>
[% PROCESS footer %]
The first thing to note is that template directives are embedded
within [% and %]. You can
change these values, along with several dozen other configuration
options, but we'll stick with the defaults for now.
The directives within those tags are instructions to the template
processor. They can contain references to variables (e.g.,
[% link.url %]) or language
constructs that typically begin with an uppercase word and may have
additional arguments (e.g., [% PROCESS footer %]).
Anything else outside the tags is plain text and is passed through
unaltered.
The example shows the PROCESS directive
being used to pull in a
header template at the top of the page and a
footer template at the bottom. The
header and footer templates
can have their own directives embedded within them and will be
processed accordingly. You can pass arguments when calling
PROCESS, just as you might when calling a
subroutine in Perl. This is shown in the first line, where we set a
value for the title variable.
By default, variables are global, and if you
change title in one
template, the new value will apply in any other templates that
reference it. The
INCLUDE directive goes a little further to make
arguments more local, giving you better protection from accidentally
changing a variable with global consequences. Separate variable
namespaces can also be used to avoid collisions between variables of
the same name (e.g., page.title versus
book.title).
In the middle of the example, we see the
FOREACH directive. This defines the start of a
repeated block that continues until the END
directive two lines below. Loops, conditionals, and other blocks can
be combined in any way and nested indefinitely. In this case,
we're setting the link variable
to alias each item in the list referenced by the
weblinks variable. We print the
url and title for each item,
with some appropriate HTML markup to display them formatted as an
HTML bullet list.
The dot (.) operator is used
to access data items within data items, and it tries to do the right
thing according to the data type. For example, each item in the list
could be a reference to a hash array, in which case
link.url would be equivalent to the Perl code
$link->{url}, or it could be an object against
which methods can be called, such as $link->url(
). The dotted notation hides the specifics of your backend
code so that you don't have to know or care about
the specifics of the implementation. Thus, you can change your data
from hash arrays to objects at some later date and slot them straight
in without making any changes to the templates.
Let's now go back to our earlier example and see if
we can make sense of it:
<h3>[% users.size %] users currently logged in:</h3>
<ul>
[% FOREACH user = users %]
[%# 'loop' is a reference to the FOREACH iterator -%]
<li>[% loop.count %]/[% loop.size %]:
<a href="[% user.home %]">[% user.name %]</a>
[% IF user.about %]
<p>[% user.about %]</p>
[% END %]
[% INCLUDE userinfo %]
</li>
[% END %]
</ul>
Anything outside a [% ... %]
directive—in this case, various HTML fragments that are
building a list of users currently logged in to our fictional
system—is passed through intact.
The various constructs that we meet inside the directives are:
- users
-
We're assuming here that
the
users variable contains a reference to a list of
users. In fact, it might also be a reference to a subroutine that
generates a list of users on demand, but that's a
backend implementation detail we're quite rightly
not concerned with here. The Template Toolkit does the right thing to
access a list or call a subroutine to return a list, so we
don't have to worry about such things.
The users themselves (i.e., the items in the users
list) can be references to hash arrays, or maybe references to
objects. Again, the Template Toolkit hides the implementation details
and does the right thing when the time comes.
- users.size
-
There are a number of "virtual
methods" you can call on basic Perl data types.
Here, the .size virtual method returns the number
of items in the users list.
- FOREACH user = users
-
The FOREACH directive defines a block of template
code up to the corresponding END directive and
processes it repeatedly for each item in the users
list. For each iteration, the user variable is set
to reference the current item in the list.
- loop
-
The loop variable is set automatically within a
FOREACH block to reference a special object (an
iterator) that controls the loop. You can call various methods in
this object, such as loop.count to return the
current iteration (from 1 to n) and
loop.size to return the size of the list (in this
case, the same as users.size).
- user
-
The user variable references each item in the
users list in turn. This can be a reference to a
hash array or an object, but we don't care which.
Again, these details are sensibly hidden from view. We just want the
home part of user, and
we're not too worried about where it comes from or
what has to be done to fetch it.
- IF user.about
-
The IF directive defines a block that gets
processed if the condition evaluates to some true value. Here
we're simply testing to see if
user.about is defined. As you might expect, you
can combine IF with ELSIF and
ELSE and also use UNLESS.
- INCLUDE userinfo
-
The INCLUDE directive is used here to process and
include the output of an external template called
userinfo. The INCLUDE_PATH
configuration option can be used to specify where external templates
can be found, so you can avoid hardcoding any absolute paths in the
templates. All the variables currently defined are visible within the
userinfo template, allowing it to access
[% user.whatever %] to correctly reference the
current user in the FOREACH
loop.
We've created this separate
userinfo template and can assume it generates a
nice table showing some interesting information about the current
user. When you have simple, self-contained elements like this,
it's often a good idea to move them out into
separate template files. For one thing, the example is easier to read
without large chunks of HTML obstructing the high-level view. A more
important benefit is that we can now reuse this component in any
other template where we need to display the same table of information
about a user.
Now that you're familiar with what templates look
like, let's move on to see how we go about
processing them.
|