NAME

OpenInteract Templates - Using 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">[% OI.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, $p ) = @_;
  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 { $R->user->fetch_group({ 
                            where => $where,
                            order => 'last_access'
                         }) };
  my %params = ( user_list => $user_list, time_limit => $time_limit );
  return $R->template->handler( {}, \%params,
                                { name => 'mypkg::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 (found from calling the 'template' alias on $R; this is normally mapped to OpenInteract::Template::Process). We didn't say how they should be displayed or any such thing.

And your template might look like:

 [%- DEFAULT theme = OI.theme_properties -%]
 <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="[% theme.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 grab the current theme keys and values using the OI plugin. This plugin is available in every template processed by OI; it has a number of properties available regarding the current request as well as a number of useful functions. See OpenInteract::Template::Plugin for the scoop.

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.

Accessing Other Templates

You can access any template available to OI through a slight manipulation of the normal TT syntax. We've created a custom template provider so that the PROCESS and INCLUDE commands use the OI template loader rather than the default TT one. This allows you to treat your templates like components available from everywhere. You can do this with a number of the global templates shipped with OpenInteract. For instance, the command:

 [% PROCESS form_hidden( name = "foo", value = "bar" ) %]

Will produce:

 <input type="hidden" name="foo" value="bar">

And you can create a 'list' component for your objects

 # This is in mypkg::my_list
 <ul>
 [% FOREACH foo = foo_list %]
   <li>[% foo.name %] ([% foo.count %])
 [% END %]
 </ul>

and use it in different ways:

 [% IF foo_list.size > 0 -%]
   # The 'foo_list' variable will get passed into the PROCESS scope
   [% PROCESS mypkg::my_list -%]
 [% ELSE %]
   No members available
 [% END %]
 [% FOREACH foo_parent = foo_parent_list -%]
   [% foo_parent.title %]
   Members:
   # Specify the 'foo_list variable explicitly
   [% PROCESS mypkg::my_list( foo_list = foo_parent.children ) %]
 [% END %]

See the Template::Manual::Directives for the difference between INCLUDE and PROCESS; a brief example is in the OI Template Widgets example.


TEMPLATE TYPES

As of version 1.50, all templates in OI are stored in the filesystem. There are two types of templates:

When OI is asked for a template without a package, it will only look in the Global directory. If not found, it will return an error.

When OI looks for a template with a package, it first looks in the global package directory, then the package directory. Templates in the global package template directory override those in the package template directory

When you first apply a package, its templates are found in the package directory. When you upgrade that package, the templates in the old package directory are no longer accessible to OI (without manual intervention). This proved annoying whenever you would modify templates in a package and then upgrade the package: you'd need to copy the templates from the old package to the new package.

Now, whenever you edit a template via the browser OI will automatically save it in the global package directory. When you upgrade the package, the templates will still be in the global package directory and still override the ones in the package template directory.


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.)

The class involved in processing the templates is OpenInteract::Template::Process, but there are a number of helper classes also. When OpenInteract is initialized it creates a single $template object that is reused for all templates. This means it has access to the same cached information as the other requests, which can speedup your requests greatly.

OI also allows you to step into this initialization process and have some input. See the docs in the module for more infomration on this.

The other class you will encounter is OpenInteract::Template::Plugin, which is a Template Toolkit plugin available to all templates processed by OI. See the extensive module docs for the functions and properties available and examples of their use.


SEE ALSO

OpenInteract::Template::Process

OpenInteract::Template::Plugin

OpenInteract::Template::Provider

Template Toolkit

http://www.template-toolkit.org/


AUTHORS

Chris Winters <chris@cwinters.com>


COPYRIGHT

Copyright (c) 2001-2002 intes.net, inc.. All rights reserved.

This script is free software; you can redistribute it and/or modify it under the same terms as Perl itself.