The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Templates in OpenInteract2

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.

This document focuses on web applications and the Template Toolkit. If other templating systems get implemented in OpenInteract we'll stick pointers in here to those docs.

WHAT IS A TEMPLATE?

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

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

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

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

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

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

OpenInteract provides a number of tools for you in every template you write (see OpenInteract2::ContentGenerator::TT2Plugin). 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:

  1: 
  2: sub list_time_limit {
  3:     my ( $self ) = @_;
  4:     my $request = CTX->request;
  5:     my $time_limit = $request->param( 'time_limit' ) ||
  6:                          || $self->param( 'time_limit' );
  7: 
  8:     # This SQL is Sybase-specific, but should be clear
  9:     my $where = 'datediff( minute, last_access, getdate() ) <= 30';
 10: 
 11:     # Note: 'fetch_group' returns an arrayref of objects.
 12:     my $user_class = CTX->lookup_object( 'user' );
 13:     my $user_list = eval {
 14:         $user_class->fetch_group({ where => $where,
 15:                                    order => 'last_access' })
 16:     };
 17:     return $self->generate_content( { user_list  => $user_list,
 18:                                       time_limit => $time_limit };
 19:                                     { name => 'mypkg::user_list' } );
 20: }

(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 method generate_content(), which decides what template engine to use and passes along some basic information about our action to the template. What we did not say was how the variables we passed were to be displayed.

And your template might look like:

  1: [%- DEFAULT theme = OI.theme_properties -%]
  2: 
  3: <h2>User Listing</h2>
  4: 
  5: <p>Users with accesses in the last <b>[% time_limit %]</b> minutes.
  6: 
  7: <table border="0" cellpadding="4">
  8: 
  9: [% PROCESS header_row( [ 'Username', 'Full Name', 'Last Access' ] ) %]
 10: 
 11: [% FOREACH user_object = user_list %]
 12:  <tr align="center" valign="middle">
 13:    <td>[% user_object.login_name %]</td>
 14:    <td>[% user_object.full_name %]</td>
 15:    <td>[% user_object.last_access %]</td>
 16:  </tr>
 17: [% END %]
 18:  
 19: </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 FOREACH directive used in the template 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').

    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:

      1: $user_object->full_name()
      2: $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 access the OpenInteract plugin ('OI') and find the theme properties from it ('OI.theme_properties'). These get assigned to a variable so we can use it multiple times throughout the template rather than calling the plugin every time.

    Note that we did not explicitly pass the plugin into the template via the variable hashref, as we did in this example with the variables 'time_limit' and 'user_list'. Instead, think of the 'OI' plugin as part of the template environment. You can use it to access information about the current user, the theme being used, various text manipulation routines, and more. We talk about it more below, but the plugin is well-documented in OpenInteract2::ContentGeneration::TT2Plugin.

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

  1: <h2>User Listing</h2>
  2: 
  3: <p>Users with accesses in the last <b>[% time_limit %]</b> minutes.
  4: 
  5: <ul>
  6: [% FOREACH user_object = user_list %]
  7:  <li>[% user_object.full_name %] ([% user_object.login_name %])
  8:       accessed the system at [% user_object.last_access %]</li>
  9: [% END %]
 10: </ul>

If we did this, we would not have to change a single line of our back-end code, since the "contract" between the action task and template hasn't changed. This contract specifies that the task 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.

Similarly, our content output could be a PDF instead of an HTML page. Instead of calling the template processing engine, we pass the data off to a separate process which formats it according to various rules and creates a PDF to send to the user. Again, the backend code does not need to be modified at all. We just need to change the action configuration to specify this new method of generating content and create whatever PDF-specific methods are to be used.

HOW DOES IT WORK

If you're curious about this, then you first need to understand how OpenInteract actions work. See OpenInteract2::Action for more information.

OpenInteract2 can support multiple types of templating engines. Each action specifies (in its metadata) the templating engine that will process the data generated by the action into usable content.

The templating engines are setup in the server configuration using the 'content_generator' key. By default OpenInteract comes with one content generator: 'TT'. Each content generator can have multiple processors so that each processor can be configured for a particular task.

In practice, many sites will only have one content generator and one template processor for the web content. But you might want to use a different content generator when you're creating customized emails or something similar.

SEE ALSO

OpenInteract2::ContentGenerator::TT2Plugin

Template

Template::Manual

COPYRIGHT

Copyright (c) 2001-2003 Chris Winters. All rights reserved.

AUTHORS

Chris Winters <Chris@cwinters.com>