Guide to Templates in OpenInteract
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.
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.
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:
[% time_limit %]
and the contents of the variable will replace this 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.
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.
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
The second class also does the actual template processing action. To do
this, it takes the variables you've passed in, the system variables and the
system behaviors and submits them along with the text of the template to
some template processing engine. This engine is currently a Template
Toolkit object.
Retrieving the Template Toolkit Object
A little sidenote. You might notice in the code that we retrieve the
Template Toolkit object like this:
This is a little cryptic and deserves a short explaination. When we startup
the server for an application, one of the things we do is create this TT
object. The main reason for this is caching and other efficiency issues. We
currently store it in the 'Stash class' so our application always has
access to it. When we make this call to $R, we're asking for our
application's TT object.
OpenInteract provides a number of variables to every single template
processed by the system. Think of these variables as you would environment
variables in a normal program: they're always around, and they change based
on who is running the program, from where it's run and using what
arguments, etc.
Note that list and hash variables provided to templates must all be
references, so 'hash' and 'hashref' may be used interchangably below.
security_scope (\%)
Hashref of scope tags to get the variables exported by
Example:
security_level (\%)
Hashref of level tags to get the variables exported by
Examples:
login ($object)
The user object representing the user currently logged in. If the user is
not logged in this will be empty. (Note: this may change in the future if
we decide to represent users who are not logged in by an 'anonymous' user.)
Example:
List of group objects the user who is logged in belongs to.
Example:
return_url ($)
The URL where the system should return if the user logs out or does any
similar action. If you have just processed POST form data, you don't
necessarily want the user coming back to the current URL.
By default, this is set to the current URL. But you can also set this from
anywhere in your code:
Also note that it is not a fully-qualified URL with a protocol and
hostname.
th (\%)
Hashref of theme properties matched up with the values for that theme. This
is probably the most frequently used default system variable.
Example:
You can edit the themes and theme variables with your web browser using the
Theme Editor.
error_hold (\%)
Information returned by the error handler. It's currently in a slight
transition, as every template has access to information from every other
template. Each key of the hashref is a template, and the messages are
stored by key in a hashref there. For example:
The template in this case is 'newuser' and the error message is stored in
the (unimaginative) name of 'message'.
session (\%)
Gives you access to the information in the current session. An important
note: the information in the session has not necessarily been saved yet.
(The stage for saving the session information after the content generation
process, see the file OpenInteract.pm
located in the
The information in the session could have been retrieved from the session
store (usually a database) or it could have been added by a handler during
the current request.
Example:
group_context ($object)
(Should be documented under
group_context_default ($)
(Should be documented under
OpenInteract also provides a number of behaviors to all the templates it
processes. These behaviors are mostly for convenience -- you can easily get
the information using perl code and passing it to the template -- but it's
often easier and cleaner to do so in the template itself.
All of these behaviors are passed as code references to your template, so
in the Template Toolkit syntax you access them much like you would a normal
subroutine call:
It's important to note how parameters are processed. If you want to use
named parameters, the Template Toolkit will put them all into a hashref as
the last argument for the procedure call. For instance:
Will act like the following subroutine call:
However, the following:
Will act like:
limit_string ($string, $length)
Truncates the string
Returns: Modified string as described above.
Example:
javascript_quote ($string)
Quotes the string so it can be used in generated Javascript code. For
instance, if you're trying to create Javascript objects from SPOPS objects,
you might do something like:
However, if the field 'author' is something like ``Mike O'Malley'', your
browser will display an error and won't be able to execute your Javascript,
since you'll have something like:
However, if you use:
You'll get:
And no error.
regex_chunk ($string, $pattern)
Still under development.
now ([$format_string])
Returns the current date and time. You can also pass a formatting string
using the
Example:
box_add ($box_identifier, [\%parameters])
Adds the box identified by
Returns: nothing.
add_context ([$type])
(Should be documented under the
limit_sentences ($text, $num_of_sentences)
Still under development/testing.
date_into_hash ($date_string)
Splits the date string into year, month and day and puts it into a hash
suitable for passing into the 'date_select' component. Note that this isn't
a smart routine:
Returns: hashref with 'year', 'month' and 'day' keys corresponding to the
appropriate information from the given date.
Example:
sprintf ($pattern, @strings)
Simulates everyone's favorite formatter,
Returns: string formatted with
Example:
percent_format ($number, [
Returns the
money_format ($number)
Returns the
object_info ($object)
Returns a hashref of information about $object, including its class, oid,
url to display it ('url'), general name ('name') and specific title
('title').
dump_it (@references)
Passes each reference to Data::Dumper and sandwiches everything in a
'<pre></pre>' block.
my $tmpl = $R->template_object;
Template System Variables
SPOPS::Secure
for the different security scopes.
<p>The security scopes currently in the system are:
<ul>
<li><b>World</b>: [% security_scope.world %]
<li><b>Group</b>: [% security_scope.group %]
<li><b>User</b>: [% security_scope.user %]
</ul>
=item *
SPOPS::Secure
for the different security levels.
<p>The security levels currently in the system are:
<ul>
<li><b>None</b>: [% security_level.none %]
<li><b>Read</b>: [% security_level.read %]
<li><b>Write</b>: [% security_level.write %]
</ul>
[% IF news.tmp_security_level >= security_level.write %]
You can edit this object
[% END %]
Welcome back [% login.full_name %]!
<p>You are in the following groups:<br>
[% FOREACH group_object = login_group %]
[% th.bullet %] [% group_object.name %]<br>
[% END %]
$R->{page}->{return_url} = '/Return/to/here';
# Create a table within a table and set the outer table
# to have a particular border color and the inner table
# to have the color of the page.
<table bgcolor="[% th.border_color %]" cellpadding="2"
cellspacing="0">
<tr><td>
<table border="0" bgcolor="[% th.bgcolor %]" width="100%"
cellpadding="4" cellspacing="0">
...
</table>
</td></tr>
</table>
[%- IF error_hold.newuser.message %]
<tr align="center">
<td colspan="2"><font color="#ff0000">
<b>[% error_hold.newuser.message %]</b>
</font></td>
</tr>
[% END -%]
base
package.)
<p>Your last 10 searches:<br>
[% FOREACH search_info = session %]
[% th.bullet %] [% search_info.query %]
([% search_info.date %])<br>
[% END %]
group_community
package.)
group_community
package.)
Template System Behaviors
[% my_action( param1, param2, param3 ) %]
[% my_action( param1, param2, param3 ) %]
my_action( $param1, $param2, $param3 )
[% my_action( param1, param2 = value2, param3 ) %]
my_action( $param1, $param3, { param2 => $value2 } )
$string
at $length
characters. If the string is shorter than $length, no change is made.
However, if the string is longer than $length
we chop it off
there and add an ellipsis (...) to indicate that the string has been
modified.
[% object.title = 'Writing Apache Modules with Perl and C' %]
Long title: [% limit_string( object.title, 20 ) %]
>> Long title: Writing Apache Modul...
[% count = 0 %]
var book_list = new Array();
[% FOREACH book_info = book_list %]
book_list[ [% count %] ] = new Object;
book_list[ [% count %] ].title = '[% book_info.title %]';
book_list[ [% count %] ].author = '[% book_info.author %]';
book_list[ [% count %] ].isbn = '[% book_info.isbn %]';
[% count = count + 1 %]
[% END %]
book_list[ 2 ].author = 'Mike O'Malley';
book_list[ [% count %] ].author = \
'[% javascript_quote( book_info.author ) %]';
book_list[ 2 ].author = 'Mike O\'Malley';
strftime
conventions to format the information. (See the Date::Format
module for more information.)
<p>Current time is: [% now() %]
$box_identifier
and the optional
\%parameters to the queue. Note that currently boxes are implemented as a
queue -- everyone goes to the back of the line.
group_community
package.)
$date_string
must be formatted 'yyyy-mm-dd'
to work.
[% expires_on_info = date_into_hash( news.expires_on ) %]
[% comp( 'date_select', day = expires_on_info.day,
year = expires_on_info.year,
month = expires_on_info.month ) %]
sprintf
. Operates exactly like the perl documentation says, so read that.
$pattern
and @strings.
<pre>
[% sprintf( '$%5.2f - $%5.2f = $%5.2f', in_bank, owed, total ) %]
</pre>
$places
])
$number
formatted (to either $places
or 2) as a percent.
$number
formatted as a money field (currently US$
only).
TO DO
SEE ALSO
Template Toolkit: perldoc Template
or
http://www.template-toolkit.org/