HTML::Template Explained, Part One

by Sam Tregar (sam@tregar.com)

Introduction

HTML::Template is a new solution to an old problem - Perl CGI development. In order to explain what makes HTML::Template new I'll briefly describe the history of Perl CGI development. If you want to get straight to the useful stuff, you can skip ahead.

In the beginning, people wrote CGIs by scattering print statements throughout Perl scripts. The HTML that ended up on the user's screen was literally embedded in the Perl script. These scripts were very difficult to maintain. Even a small change, like switching fonts, required a programmer to edit the script. It was almost impossible for a non-programmer to edit the HTML without endangering the script itself! Thus, programmers were required for even the smallest design change. Given the relative shortage of programmers versus HTML designers, this is clearly not desired situation.

Many solutions to this problem have appeared, and many share a common trait - they invert the situation by embedding programming inside HTML. Examples of this approach are HTML::Mason, Embperl, ASP and PHP. Used well these tools can allow programmers and non-programmers to work on the same script/template. However, the Perl programming and the HTML are still in the same file, and opportunities for mishap are constantly present. Maintainence is easier in this situation, but still problematic.

HTML::Template approaches the problem differently. Using HTML::Template, the Perl code and the HTML text are kept totally separate. The HTML is put in a file called a template, and the Perl code is written in separate script files and modules. The HTML is enhanced with a few new tags providing variable substitution, looping and branching. On the Perl side, the programmer can set values for the variables, fill in loops and control branching.

Used well, HTML::Template allows Perl programmers and HTML designers to work totally independently. The HTML designers only need to learn a few new tags. All the programming takes place in Perl scripts and modules using HTML::Template's simple API.

I wrote HTML::Template because I don't enjoy editing HTML, I prefer programming! Call it laziness, but I think that the people who are good at something should be the people doing it. HTML designers are good at editing and maintaining HTML. Anything that stands needlessly in the way of their doing their job should be removed. By keeping the HTML design totally separate from the code, HTML::Template tries to do just that.

HTML-Template-HOWTO

Now that you know what HTML::Template is all about, it's time to show you how to use it! Here's a small example taken from the documentation. This first file is the template file, called "test.tmpl".

test.tmpl:
  <HTML>
    <HEAD>
       <TITLE>Test Template</TITLE>
    </HEAD>
    <BODY>
       My Home Directory is <TMPL_VAR NAME="home">.
       My Path is set to <TMPL_VAR NAME="path">.
    </BODY>
  </HTML>

And here's a small CGI script that uses the above template file.

test.pl:
  use HTML::Template;

  # open the HTML template
  my $template = HTML::Template->new(filename => 'test.tmpl');

  # fill in some parameters in the template
  $template->param(home => $ENV{HOME});
  $template->param(path => $ENV{PATH});

  # send the obligatory Content-Type
  print "Content-Type: text/html\n\n";

  # print the template
  print $template->output;

When run as a CGI by your web server, it should return something like:

output from test.pl:
  My Home Directory is /home/some/directory.
  My Path is set to /bin;/usr/bin.

Let's look at what's going on in the above example. The template file looks a lot like normal HTML except for the <TMPL_VAR> tags. These tags are actually HTML::Template tags - they specify variables that can be filled in from the script with calls to param().

The CGI script is a little more interesting. The first thing it does is call HTML::Template->new(). This returns an HTML::Template object. It requires at least one option/value pair to specify how to open the template file. The most common of these is filename => '/path/to/file.tmpl'. The filename option is used to specify a file to open as the template file.

Next, the script calls $template->param() twice, once for each <TMPL_VAR> in the template. This method takes two parameters, the name of a variable and the value to set it to. In this case I'm populating two variables with values from the %ENV hash (see perlvar for information on %ENV).

Next to last, the script prints out the content header that all CGI scripts need to produce. Finally, it prints the result of calling $template->output(). This is the result of evaluating the <TMPL_VAR> tags in the template and returning the resulting HTML.

That's all there is to using HTML::Template from your CGI scripts. These three methods - new(), param() and output() are all you need to learn to make effective use of HTML::Template. Of course, there are a few more tags to learn, but you can get a remarkable amount done with just <TMPL_VAR>.

Fruit Loops

Beyond simple <TMPL_VAR> tags, the most useful tag is the <TMPL_LOOP>. The <TMPL_LOOP> tag addresses a very common situation in CGI development - displaying lists of data.

Imagine you have some data about fruit that you would like to present to the user of your script. A good way to show it visually would be to put it in an HTML table. Here's a script and accompanying template that uses a <TMPL_LOOP> to show some data about fruit. I'll go through and explain each section following the example:

fruit.tmpl:
   <HTML>
     <HEAD>
       <TITLE>Fruity Data</TITLE>
     </HEAD>
     <BODY>

       <H1>Fruity Data</H1>

       <TABLE BORDER=1>

            <TR>
              <TD><B>Fruit Name</B></TD>
              <TD><B>Color</B></TD>
              <TD><B>Shape</B></TD>
            </TR>

         <TMPL_LOOP NAME="fruit_loop">
            <TR>
               <TD><TMPL_VAR NAME="name"></TD>
               <TD><TMPL_VAR NAME="color"></TD>
               <TD><TMPL_VAR NAME="shape"></TD>
            </TR>
         </TMPL_LOOP>

       </TABLE>

     </BODY>       
   </HTML>

And the script that uses the template above:

fruit.pl:
  use HTML::Template;

  # the fruit data - the keys are the fruit names and the values are
  # pairs of color and shape contained in anonymous arrays
  my %fruit_data = (
                    Apple => ['Red, Green or Yellow', 'Round'],
                    Orange => ['Orange', 'Round'],
                    Pear => ['Green or Red', 'Pear-Shaped'],
                    Banana => ['Yellow', 'Curved'],
                   );

  my $template = HTML::Template->new(filename => 'fruit.tmpl');

  my @loop;  # the loop data will be put in here

  # fill in the loop, sorted by fruit name
  foreach my $name (sort keys %fruit_data) {
    # get the color and shape from the data hash
    my ($color, $shape) = @{$fruit_data{$name}};
    
    # make a new row for this fruit - the keys are <TMPL_VAR> names
    # and the values are the values to fill in the template.
    my %row = ( 
               name => $name,
               color => $color,
               shape => $shape
              );

    # put this row into the loop by reference             
    push(@loop, \%row);
  }

  # call param to fill in the loop with the loop data by reference.
  $template->param(fruit_loop => \@loop);

  # send the obligatory Content-Type
  print "Content-Type: text/html\n\n";

  # print the template
  print $template->output;

This script will produce:

output from fruit.pl:
Fruity Data

Fruit Name Color Shape
Apple Red, Green Or Yellow Round
Banana Yellow Curved
Pear Green or Red Pear-Shaped
Orange Orange Round

Now I'll explain what's going on in this script and template. The <TMPL_LOOP> tag is different from the <TMPL_VAR> tag in that it requires an end tag - </TMPL_LOOP>. If you leave out the </TMPL_LOOP> you'll get an error from HTML::Template telling you to put one in. Inside the <TMPL_LOOP> block you can put anything you can put in the template. <TMPL_VAR> tags used inside the loop need to be populated.

Inside the script we have a more complicated situation. First, the data is included in the form of a hash of arrays. Each key is the name of a fruit and the values are two-item arrays. Each array contains a color value and shape value. This is just an example - any reasonable data structure could have been used.

The loop that takes the fruit data and puts it into @loop comes next. The ultimate goal here is to produce something like:

   [
     { name => 'Apple',   color => 'Red',    shape => 'Round' },
     { name => 'Orange',  color => 'Orange', shape => 'Round' },
   ]

This array-of-hashes format is what HTML::Template needs to fill in a <TMPL_LOOP>. Each row of the array corresponds to a pass through the <TMPL_LOOP>, and each key/value pair is a <TMPL_VAR> assignment to be made. To get to this format the script uses push() to build up the @loop array, adding a hash-ref for each fruit.

Finally, the call to param() fills in the <TMPL_LOOP> with the @loop data in the correct format. It's important to realize that you only call param() once for each <TMPL_LOOP> in the template file. A common mistake is to call param() inside the foreach loop.

<TMPL_LOOP> is a bit complicated to grasp at first, but it is very powerful. With it you can produce very flexible, data-driven, CGI applications.

Conclusion

That's all for this now. I plan to write a second part to this tutorial where I'll give you an introduction to more tags - <TMPL_IF> and <TMPL_INCLUDE>. I'll also introduce some useful options to HTML::Template->new() including information on HTML::Template's various caching modes.

To get more information about HTML::Template, install the module and check out the documentation with the command 'perldoc HTML::Template'.

The easiest way to install HTML::Template is to use CPAN.pm. Type something like:

   perl -MCPAN -e shell
   install HTML::Template

Alternately you can download HTML::Template directly and install it manually. Just untar it and read the README file for further instructions.

If you have questions about HTML::Template, feel free to join the HTML::Template mailing-list. To join, send a blank message to htmltmpl@lists.vm.com. See you there!

Propers

Since I've got the mic, I'd like to thank all the people that have contributed to HTML::Template, including my previous employer, Vanguard Media, which both funded and inspired the early development of HTML::Template. Without all your help HTML::Template would not be.