NAME

Guide to Templates in OpenInteract

SYNOPSIS

This document reviews how the templating system works in OpenInteract. Template processing is at the heart of OpenInteract, and it is important to understand it well.

WHAT IS A TEMPLATE?

The Basics

A template is simply HTML combined with directives meant for the template processing engine. Here's an example:

 <p>Welcome back 
   <font color="red">[% login.full_name %]</font>!</p>

When run through the template processing engine with a normal user object in the 'login' key, this will result in:

 <p>Welcome back 
   <font color="red">Charlie Brown</font>!</p>

So the information between the '[%' and '%]' symbols ('login.full_name') was replaced by other text which was dependent on the user who was viewing the page. If another user viewed the page, she might have seen:

 <p>Welcome back 
   <font color="red">Peppermint Patty</font>!</p>

OpenInteract provides a number of tools for you in every template you write. However, you can also provide your templates access to query results from the various data stores that SPOPS provides.

CREATING YOUR OWN TEMPLATE

The general strategy behind OpenInteract applications is a well-known one: separate the display of data from how the data are retrieved or operated on.

To this end, the code behind an OpenInteract application normally just retrieves some data using parameters supplied by the user and then hands it off to the template. The template doesn't care how the data were retrieved -- it just knows what is supposed to be there. The template and code enter into a sort of contract -- the template expects certain data which both the code and the system provide.

So, let's do an example. Let's say you want to display a list of users who have accessed the system in the last n minutes. Your code might have a subroutine like this:

 my $DEFAULT_TIME_LIMIT = 30;

 sub list_time_limit {
  my $class = shift;
  my $p     = shift;
  my $R = OpenInteract::Request->instance;
  my $time_limit = $R->apache->param( 'time_limit' ) ||
                   $DEFAULT_TIME_LIMIT; 

  # This SQL is Sybase-specific, but should be clear
  my $where = 'datediff( minute, last_access, getdate() ) <= 30';

  # Note: 'fetch_group' returns an arrayref of objects.
  my $user_list = eval { MyApp::User->fetch_group({ 
                            where => $where,
                            order => 'last_access'
                         }) };
  my $params = { user_list => $user_list, time_limit => $time_limit };
  return OpenInteract::Template::Toolkit->handler(
             {}, $params, { db => 'user_list' } 
         );
 }

(The actual code would have lots of good things like error checking, but this is just an example.)

Note that we simply passed a hashref of variables to the template processing class ('OpenInteract::Template::Toolkit'). We didn't say how they should be displayed or any such thing.

And your template might look like:

 <h2>User Listing</h2>

 <p>Users with accesses in the last <b>[% time_limit %]</b> minutes.

 <table border="0" cellpadding="4">
  <tr align="center" valign="bottom" bgcolor="[% th.head_bgcolor %]">
    <td><b>Username</b></td>
    <td><b>Full Name</b></td>
    <td><b>Last Access</b></td>
  </tr>

 [% FOREACH user_object = user_list %]
  <tr align="center" valign="middle">
    <td>[% user_object.login_name %]</td>
    <td>[% user_object.full_name %]</td>
    <td>[% user_object.last_access %]</td>
  </tr>
 [% END %]
  
 </table>

There are a few things at work here:

  1. We're using the scalar variable 'time_limit'. Since this is a simple scalar, we can just refer to it by name in the template as a variable:

     [% time_limit %]
    

    and the contents of the variable will replace this directive.

  2. We loop through the variable 'user_list' which we passed to the template. The directive:

     [% FOREACH user_object = user_list %]
    

    is very similar to the foreach loop in perl -- for every thing in the list 'user_list', we assign that thing to the variable 'user_object' which we can then use within the loop.

    Within the loop we use both properties of the user object ('login_name' and 'last_access') and call a method on the object ('full_name').

     <td>[% user_object.full_name %]</td>
    

    One of the nice features of the Template Toolkit is that it treats objects and hashrefs in much the same way, using the dot notation. So 'user_object.full_name' could transparently translate to either:

     $user_object->{full_name}
     $user_object->full_name()
    

    Here we're using the 'user_object' variable (obviously) as an object. But we could modify the perl code to instead get all the information about the user and combine it with other information into a hashref and feed it to the same template. If we were to do this, we would not have to modify a single line of our template.

  3. We use the default system variable 'th', which is our theme. Instead of using the object interface of the theme, we collapse its properties into a hashref for efficiency.

    Even though we don't explicitly pass them into the template via the variable hashref -- as we did in this example with the variables 'time_limit' and 'user_list' -- a number of variables are still available to every template. These are Template System Variables and are described in more detail below.

Now, what if we wanted to change the display of the data? We could replace the 'user_list' template with the following:

 <h2>User Listing</h2>

 <p>Users with accesses in the last <b>[% time_limit %]</b> minutes.

 <ul>
 [% FOREACH user_object = user_list %]
  <li>[% user_object.full_name %] ([% user_object.login_name %])
       accessed the system at [% user_object.last_access %]</li>
 [% END %]
 </ul>

If we did this, we would not have to change a single line of our back-end code, since the ``contract'' between the code and template hasn't changed. This contract specifies that the code will provide a list of user objects and a time limit to the template. Even though the template uses these data somewhat differently now, the code is isolated from this change and indeed never cares about it.

HOW DOES THE PROCESS WORK?

Template processing is at the heart of OpenInteract, and it is very important to understand it well. (The authors have been bitten more times than they'd care to admin from not realizing all implications of the process.)

There are two OpenInteract classes (besides your own) involved in the process:

OpenInteract::Template

OpenInteract::Template::Toolkit

(Note that if you've replaced the Template Toolkit processing engine with a separate CPAN module or your own, the second will be different.)

You normally only see and use the second class, but it's important to know about the first if you ever want to use a different template processing engine. (But the Template Toolkit is so flexible and powerful that you probably won't need anything else.)

The first class makes available a number of variables to every template. (See Template System Variables below for details.) It also retrieves the template from either the database or filesystem as needed.

The second class has the option of installing additional template system variables. It does not currently do this, but it does install various template system behaviors, which are simple formatting and informational routines. These are similar to a component in OpenInteract, but they don't necessarily return HTML. (In fact, many of them do not return anything at all -- see L