Home | Wiki | OI 1.x Docs | OI 2.x Docs |
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.
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.
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: sub list_time_limit { 2: my ( $self ) = @_; 3: my $request = CTX->request; 4: my $time_limit = $self->param( 'time_limit' ) 5: || $request->param( 'time_limit' ); 6: 7: # This SQL is Sybase-specific, but should be clear 8: my $where = 'datediff( minute, last_access, getdate() ) <= 30'; 9: 10: # Note: 'fetch_group' returns an arrayref of objects. 11: my $user_list = eval { 12: CTX->lookup_object( 'user' ) 13: ->$user_class->fetch_group({ where => $where, 14: order => 'last_access' }) 15: }; 16: return $self->generate_content( { user_list => $user_list, 17: time_limit => $time_limit }, 18: { name => 'mypkg::user_list' } ); 19: }
(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:
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 (line 5) and the contents of the variable will replace this directive.
We loop through the variable 'user_list' which we passed to the
template. The FOREACH directive used in the template (line 11) 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', lines 12 and 14) and call a method on the object ('full_name', line 13).
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.
We access the OpenInteract plugin ('OI') and find the theme properties from it ('OI.theme_properties', line 1). 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.
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, also known as content generators. Each action specifies (in its metadata) the templating engine that will process the data generated by the action into usable content. This type can also be modified programmatically by the action code itself.
The templating engines are setup in the server configuration using the 'content_generator' key. OpenInteract comes with at least three content generators configured for you: 'TT' (Template Toolkit), 'HTMLT' (HTML::Template) and 'TextTmpl' (Text::Template).
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.
As we mentioned above your action code has no direct contact with a
content generator. The action method generate_content()
takes care
of this, choosing a generator and passing the given parameters (plus
one or two others) to the engine. This provides another means for us
to modify the content generated by the action without ever modifying
the action itself -- very useful.
In addition to that the action doesn't even need to specify the template source. We can declare that as well by adding the following to our action configuration:
[userlist template_source] list_time_limit = mypkg::user_list
The handler now doesn't even need to pass a template source, so the
return
can be modified like this:
16: return $self->generate_content( { user_list => $user_list, 17: time_limit => $time_limit } ); 18: }
Now we can use the same action code with multiple template sources. For instance, we could create a heavily table-driven site that most people will see and one that uses very little layout for visually impaired folks. Further, we'll assume that the actions for visually impaired folks begin with a 'V_', so our action configuration might look like:
[timelimit] class = OpenInteract2::Action::UserList method = list_time_limit [timelimit template_source] list_time_limit = mypkg::userlist_normal [v_timelimit] class = OpenInteract2::Action::UserList method = list_time_limit [timelimit template_source] list_time_limit = mypkg::userlist_plain
Package templates are located in one or two places:
The package template/
directory. These are the templates shipped
with the package. When you upgrade the package you get whatever
templates are distributed with the upgrade.
The site template/packagename/
directory. These are templates
edited by you that will persist package upgrades. When you edit a
template with the browser interface it will automatically save your
modified templates here.
The OpenInteract2::ContentGenerator::TT2Provider and OpenInteract2::SiteTemplate classes take care of making the loading process transparent. So all you need to do is request a template with the 'package-name::template-name' syntax and the rest is done for you.
OpenInteract2::ContentGenerator::TT2Plugin Template
Copyright (c) 2001-2003 Chris Winters. All rights reserved.
Chris Winters <Chris@cwinters.com>
Generated from the OpenInteract 1.99_03 source.