It doesn't matter what language you do your server-side web
development in, presentation templates just make so much sense. Here are
some reasons why:
- Templates (usually) let you re-use commonly used blocks of code.
- Templates (usually) make it easy (or easier) for a web designer
(e.g. not a developer) to work on the presentation layout of your
application.
- Templates allow you to separate presentation from business logic
and will help you separate them in how you think of your application as
well.
By templates, I mean files that contain HTML data along with
some special coded method of interpolating dynamic data into the HTML.
By this definition, PHP and ASP code are template languages themselves.
That's one of the more substantial reasons I've come to dislike
these languages! You'll come to understand as you read more below.
Here's a very simple template example:
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>Welcome to the $SITE_NAME website!</h1>
</body>
</html>
The above example uses the syntax defined by the
CGI::FastTemplate Perl module. I used this module
for a couple of projects in 2000 or so and while it does make it easy to
design a page's look and feel around the dynamic content your web
application is going to drop into the page, it works best when your content
doesn't need to be nested inside HTML. For example, dynamics data in
a table does not work well with CGI::FastTemplate. Neither do
lists of data.
Some templating systems, like CGI::FastTemplate to some extent,
can handicap you too much. Others, like
HTML::Embperl or
ePerl, in my opinion, provide too much capability at the template
level.
You don't want to be able to write your entire application in a
template. You might as well be writing your application in PHP, JSP, or ASP
(or
Apache::ASP).
I think a templating system should provide you with just enough logic so
that you can affect how data is presented and not much more logic than
that. I'm not alone in thinking these things. See Wikipedia topics:
Web template system,
Model-view-controller, and
Separation of concerns.
So, as a result of study of templating systems for Perl, I
recommend Template (Template Toolkit for Perl).
Note: Template Toolkit does allow some things I don't think have
any place in templates, but it doesn't do so by default. You have to
make a conscious choice to do those things, read the documentation, etc. and
that should (hopefully) discourage you from doing so.
How to use Template Toolkit
Let's take a look at how Template Toolkit would be used to mimic
the CGI::FastTemplate example above.
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>[% title %]</title>
</head>
<body>
<h1>Welcome to the [% site_name %] website!</h1>
</body>
</html>
It's not a whole lot different, is it? And, actually, it's a
little more typing to key in those beginning and ending brackets instead of
just a preceding dollar sign.
Now, here's an example of how this template file (we'll call
it page1.tt) would be used in Perl code, say, in a CGI script.
#!/usr/bin/perl
use CGI qw/:standard/;
use Template;
my $q = CGI;
my $tt = Template->new({
INCLUDE_PATH => '/var/www/templates/'});
my $vars = {};
$vars->{'title'} = 'Joe Schmoe Shoe Repair - Home';
$vars->{'site_name'} = 'Joe Schmoe Shoe Repair';
print $q->header('text/html');
print $tt->process('page1.tt', $vars) ||
die $tt->error();
Great! Now we know Template is just as good as
CGI::FastTemplate! What else can it do?!
Wrapping an application
One thing I often do with Template Toolkit is use it to wrap
applications so that every page has the same look and feel. You do this by
defining a wrapper template in the hash reference you pass to the
Template constructor:
my $tt = Template->new({
INCLUDE_PATH => '/var/www/templates/',
WRAPPER => 'sitewrapper.tt', });
Now every page generated by the process() function will include
the contents of sitewrapper.tt around it.
The wrapper template needs to contain a special template directive in
it: [% content %]. This is where the contents of your specified
templates are placed.
Here is an example of a wrapper template:
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>[% template.title %]</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<div class="sidebar">[% INCLUDE sidebar.tt %]</div>
<div class="body">[% content %]</div>
<div class="footer">[% INCLUDE footer.tt %]</div>
</body>
</html>
Notice the use of the [% content %] directive in the wrapper
template.
There are a couple other interesting uses of Template Toolkit directives
in this example, notably the INCLUDE directive (for inserting the
parsed output of other templates) and a special directive within the HTML
title tags.
The [% template.title %] directive should make sense once we
see how a normal template might look when using a wrapper:
[% META title = "Contact us" %]
<h1>Contacting Joe Schmoe Shoe Repair!</h1>
<p>See the list below for our telephone numbers:</p>
<ul>
[% FOREACH phone = phone_numbers %]
<li>[% phone.name %]: [% phone.number %]</li>
[% END %]
The META directive let's us pass data into the wrapper
template.
Flow control
The above template example also introduces to one of the reasons I love
Template Toolkit: flow control. In this case, a for-each loop. The variable
phone_numbers could be a list of hashes we get from a database and
pass to the template. Each hash contains a name-value pair named
name and one named number. The FOREACH directive
allows us to iterate through the phone_numbers list and assign a
reference the current hash to a variable we've named phone.
Inside the loop, we can reference the individual name-value pairs by name.
This is only one of the flow control structures Template Toolkit offers.
There are also IF-THEN-ELSIF-ELSE structures, CASE/SWITCH structures, and
WHILE loops.
These should give you just a taste of what Template Toolkit can do. With
the Template Perl module installed on your system, you get all the
documentation you could hope for as POD and man files. The Template Toolkit
website also has all this documentation online as well.
Below is a bit of a more complex template I created years ago for the utahisps.com website.
<h3>[% company.name %]</h3>
<table cellpadding="6">
<tr>
<td>Services</td>
<td>
[% has_services = 0 %]
[% FOREACH service_name = services.keys %]
[% IF services.$service_name %]
<a href="/isp/[% company.company_id %]/[% service_name %]">
[ % service_name %]</a><br/>
[% has_services = 1 %]
[% END %]
[% END %]
[% IF has_services == 0 %]
N/A
[% END %]
<!-- None listed -->
</td>
</tr>
<tr>
<td>Address</td>
<td>
[% company.addr1 %]<br/>
[% IF company.addr2.length %]
[% company.addr2 %]<br/>
[% END %]
[% company.city %], [% company.state %] [% company.zip %]
</td>
</tr>
<tr>
<td>Phones</td>
<td>
[% IF company.phone1.length %]
[% company.phone1 %] ([% company.phone1_desc %])
[% END %]
[% IF company.phone2.length %]
<br/>
[% company.phone2 %] ([% company.phone2_desc %])
[% END %]
</td>
</tr>
[% IF company.fax.length %]
<tr>
<td>Fax</td>
<td>[% company.fax %]</td>
</tr>
[% END %]
<tr>
<td>Website</td>
<td>< <a href="[% company.www_url %]"
target="_new">[% company.www_url %]
</a> ></td>
</tr>
[% IF company.info_email.length %]
<tr>
<td>Info E-mail</td>
<td>< <a href="mailto:[% company.info_email %]"
target="_new">
[% company.info_email %]</a> ></td>
</tr>
[% END %]
<tr>
<td>Payment</td>
<td>
[% IF company.credit_cards.length %]
[% company.credit_cards %]
[% ELSE %]
Cash/check only
[% END %]
</tr>
[% IF company.oper_since %]
<tr>
<td>Oper since</td>
<td>[% company.oper_since %]</td>
</tr>
[% END %]
<tr>
<td><a href="http://www.angio.net/rep/"
target="_new">Utah
REP</a><br/>member</td>
<td>[% IF company.ut_rep %]Yes[% ELSE %]No[% END %]</td>
</tr>
[% IF company.os.length %]
<tr>
<td>Primary OS</td>
<td>[% company.os %]</td>
</tr>
[% END %]
[% IF company.upstreams %]
<tr>
<td>Upstream feeds</td>
<td>[% company.upstreams %]</td>
</tr>
[% END %]
[% IF company.notes.length %]
<tr>
<td>Notes</td>
<td><tt>[% company.notes %]</tt></td>
</tr>
[% END %]
</table>