Listen Print Discuss

Advanced HTML::Template: Widgets

by Philipp Janert
February 01, 2007

My previous article, looked at extending HTML::Template through custom tags and filters. This article looks at ways to manage large, more complex pages, by bundling HTML::Template into something like GUI "widgets" (or "controls").

Imagine you have a basic page layout following the standard setup, with a header, a lefthand navbar, and the main body in the bottom right. The header and navbar are the same for all pages of the site, but of course the main body differs from page to page:

Header
Navbar

Body

Naturally, you don't want to repeat the information for the header and the navbar explicitly on each page. Furthermore, if the HTML for the navbar changes, you don't want to have to modify each and every page. The <TMPL_INCLUDE> tag can help in this situation.

Create separate files for header and navbar, then include them in the template for each page (by convention, I use the filename extension .tpf for page fragments, to distinguish them from full-page templates: .tpl):

<html>
  <head></head>
  <body>
    <table>
    <tr colspan="2">
      <td><TMPL_INCLUDE NAME=header.tpf></td>
    </tr>
    <tr>
      <td><TMPL_INCLUDE NAME=navbar.tpf></td>
      <td> 
        <!-- Body goes here! -->
        ...
      </td>
    </tr>
    </table>
  </body>
</html>

Now HTML::Template will include the page fragments for the header and navbar in the page when it evaluates the template. Changes to either of the fragments will affect the entire site immediately.

(For simplicity of presentation, I am going to use the old familiar <table> method to fix the layout--this article is about HTML::Template, not CSS positioning!)

Note that both the header and the navbar may include other HTML::Template tags, such as <TMPL_VAR> or <TMPL_LOOP>: file fragment inclusion occurs before tag substitution. If you need dynamic content in either header or navbar, all you need to do is set the value of the corresponding parameter using the param() function before evaluating the template.

Better Encapsulation through Widgets

If there are only a few dynamic parameters in header and navbar, you can simply assign values to them together with the parameters required by the main body of the page. However, if the header and navbar themselves become sufficiently complicated, you probably don't want to repeat their parameter-setting logic with the actual Perl code managing the main business logic for each page of our site. Instead, you can control them through an API.

To establish a Perl API for a page fragment, hide the entire template handling, including parameter setting and template substitution, in a subroutine. The subroutine takes several parameters and returns a string containing the fully expanded HTML code corresponding to the page fragment. You can then include this string in the current page through a simple <TMPL_VAR> tag.

As an example, consider a navbar that contains the username of the currently logged-in user.

Here is the page-fragment template for the navbar:

Current User: <TMPL_VAR NAME=login>
<br />
<ul>
  <li><a href="page1.html">Page 1</a>
  <li><a href="page2.html">Page 2</a>
  <li><a href="page3.html">Page 3</a>
</ul>

For this example, the corresponding subroutine is very simple. It's easy to imagine a situation where the navbar requires some complex logic that you are glad to hide behind a function call--for instance, when the selection of links depends on the permissions (found through a DB call) of the logged-in user.

Demonstrating the principle is straightforward; find the template fragment, set the required parameter, and render the template:

sub navbar { 
  my ( $login ) = shift;

  my $tpl = HTML::Template->new( filename => 'navbar.tpf' );
  $tpl->param( login => $login );
  return $tpl->output();
}

The master-page template then includes the navbar string using a <TMPL_VAR> tag. (Note the header inclusion through a <TMPL_INCLUDE> tag.)

<html>
  <head></head>
  <body>
    <table>
    <tr colspan="2">
      <td><TMPL_INCLUDE NAME=header.tpf></td>
    </tr>
    <tr>
      <td><TMPL_VAR NAME=navbar></td>
      <td> 
        <!-- Body goes here! -->
        ...
      </td>
    </tr>
    </table>
  </body>
</html>

This approach provides pretty good encapsulation: the code calling the navbar() routine does not need to know anything about its implementation--in fact, the subroutine can be in a separate module entirely. It is not far-fetched to imagine a shared module for all reusable page fragments used on the site.

Pages: 1, 2

Next Pagearrow





Contact Us | Advertise with Us | Privacy Policy | Press Center | Jobs | Submissions Guidelines

Copyright © 2000-2008 O’Reilly Media, Inc. All Rights Reserved. | (707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on the O'Reilly Network are the property of their respective owners.

For problems or assistance with this site, email