Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Advanced HTML::Template: Widgets
by Philipp Janert | Pages: 1, 2

Building Pages Inside Out

This development model still uses a separate, top-level template file for each page. All shared parts of the page are then included in this master template.

The widget approach can go a step further to do away entirely with the notion of having a separate master template for each page, by turning even the main body of the page into a widget or a collection of widgets. At this point, there may be only a single top-level template:

<html>
  <head></head>
  <body>
    <table>
    <tr colspan="2">
      <td><TMPL_INCLUDE NAME=header.tpf></td>
    </tr>
    <tr>
      <td><TMPL_VAR NAME=navbar></td>
      <td><TMPL_VAR NAME=mainbody></td>
    </tr>
    </table>
  </body>
</html>

A central Perl "controller" component dispatches page requests to the appropriate main-body widget (assuming you specify destination pages through a request parameter called action):

use CGI;
use HTML::Template;

my $q = CGI->new;
my $action = $q->param( 'action' );

# Dispatch to the desired main function
my $body_string = '';
if(    $action eq 'act1' ) { $body_string = act1( $q ); }
elsif( $action eq 'act2' ) { $body_string = act2( $q ); }
elsif( $action eq 'act3' ) { $body_string = act3( $q ); }

# Pull the current user from the query object and pass to the navbar
my $navbar_string = navbar( $q->param( 'login' ) );

# Set the rendered navbar and mainbody in the master template
my $tpl = HTML::Template->new( filename => 'tmpl3.tpl' );
$tpl->param( mainbody => $body_string );
$tpl->param( navbar   => $navbar_string );

print $q->header(), $tpl->output;

sub navbar { ... }

sub act1{ ... }
sub act2{ ... }
sub act3{ ... }

A Drop-down Widget

At this point, you may ask why you still need a template for the page fragment at all. Well, you don't--unless you find it convenient, of course.

There are two reasons to use a template: as a more suitable method of generating HTML than having to program a whole bunch of print statements, and to ensure separation of presentation from behavior. By encapsulating the nitty-gritty of HTML generation behind an API, you achieve the latter. How you go about the former depends entirely on the context. If you need to generate a lot of straight up HTML, with lots of <table>, <img>, and <form> tags, a template fragment makes perfect sense. But if it is actually easier to program the print statements yourself, there is nothing wrong with that--by encapsulating the HTML generation in a subroutine, you still achieve separation of presentation and main control flow and business logic.

As a classic example for something that is hard to express as a template, consider a drop-down menu with a default that has to be set programmatically. Attempting to do this using a template leads to a mess of template loops and conditionals. However, doing it in a widget subroutine is clean and easy, in particular if you use the appropriate functions from the standard CGI module:

sub color_select {
  my ( $default_color ) = @_;

  my @colors = qw( red green blue yellow cyan magenta );

  return popup_menu( 'color_select', \@colors, $default_color );
}

Include the string returned by this subroutine in a page template using <TMPL_VAR> tags as discussed previously.

Conclusion

This concludes a brief overview of some useful techniques for using HTML::Template that go beyond straight up variable replacement. I hope you enjoyed the trip.

There remains the question of when all of this is useful and suitable. To me, the beauty of HTML::Template is its utter simplicity. There isn't much in the way of creature comforts or "framework" features (such as forms processing or automated request dispatch). On the other hand, there is virtually no overhead: it's possible to understand the basic ideas of HTML::Template in five minutes or fewer, and it's easy to add to any simple CGI script. You don't need to design the project around the framework (as is often the case with more powerful but inevitably more complex toolsets). In fact, HTML::Template is so trivial to use that I use it in any CGI script that produces more than, say, 10 to 15 lines of HTML output. It's just convenient.

Filters and "widgets" as described in this series are easy ways to add some convenience features that are missing from HTML::Template. By bundling some repetitive code segments into a custom tag or a widget, you can keep both the code and template cleaner and simpler while at the same time continuing to enjoy the low overhead of HTML::Template.

However, there is only so much lipstick you can put on a pig. When you find ourselves building extensive libraries of custom tags or specific widgets, maybe you want to look more deeply into one of the existing frameworks for Perl web development, such as the Template Toolkit.

Of course, as long as you are happy with HTML::Template and it works for you, there is no reason to change. It works for me--and very well indeed.

Sidebar: Three Hidden Gems

HTML::Template has several useful and often overlooked minor features and options, (despite being clearly documented in POD). I want to point out three of the ones that are most commonly useful--all of which, by default, are (unfortunately, I think) turned off.

Permit Unused Parameters
my $tpl = HTML::Template->new( filename => '...', die_on_bad_params => 0 );

HTML::Template will die when it encounters an unused parameter in the template. In other words, if you set a parameter with $tpl->param(), but there is no corresponding <TMPL_VAR> in the template, template processing will fail by default. Setting the option die_on_bad_params to 0 disables this behavior.

Make Global Variables Visible In Loops
my $tpl = HTML::Template->new( filename => '...', global_vars => 1 );

By default, template loops open up a new scope, which makes all template parameters from outside the loop invisible within the loop. In particular, code like this will (by default) not work as expected:

Verbosity level: <TMPL_VAR NAME=isVerbose>
<ul>
<TMPL_LOOP NAME=rows>
  <li><TMPL_VAR NAME=rowitem>
    <TMPL_IF NAME=isVerbose>
      ... <!-- print additional, 'verbose' info -->
    </TMPL_IF>
</TMPL_LOOP>
</ul>

The verbose parameter, defined outside the loop scope will by default not be visible within the loop. Change this by setting the option global_vars to 1.

Special Loop Variables
my $tpl = HTML::Template->new( filename => '...', loop_context_vars => 1 );

Finally, enable a very useful little feature by setting loop_context_vars to 1. This defines several Boolean variables within each loop; they take on the appropriate value for each row:

  • __first__
  • __inner__
  • __last__
  • __odd__
  • __even__

There is also an integer variable __counter__, which is incremented for each row. Note that __counter__ starts at 1, in contrast to Perl arrays!

These variables are extremely useful in a variety of ways. For example, they make it easy to give every other row in a table a different background color to improve legibility. Together with filters (as described in my previous article), this allows for rather elegant template code:

<html>
<head>
  <style type="text/css">
    .odd  { background-color: yellow }
    .even { background-color: cyan }
  </style>
</head>

<body>
  <table>
  <TMPL_LOOP NAME=rows>
    <CSTM_ROW EVEN=even ODD=odd>
      <td> <TMPL_VAR NAME=__counter__> Cell contents... </td>
    </tr>
  </TMPL_LOOP>
  </table>
</body>
</html>

The appropriate filter for the new custom tag is:

sub cstmrow_filter {
  my $text_ref = shift;
  $$text_ref =~ s/<CSTM_ROW\s+EVEN=(.+)\s+ODD=(.*)\s*>
                 /<TMPL_IF NAME=__odd__>
                    <tr class="$1">
                  <TMPL_ELSE>
                    <tr class="$2">
                  <\/TMPL_IF>
                 /gx;
}

Note that, as implemented, the EVEN attribute must precede the ODD attribute in the <CSTM_ROW> tag.