2. Modularization techniques

A single template never contains the complete output code. Instead, the code is generated from several smaller templates providing certain parts of the output, such as the general structure, menu or content. We will call it modularization, because usually each application module has its own, custom template. We do not specify what the module actually is. It could be a controller action, a plugin or any other logical piece of code you need that displays something.

The simplest modularization technique is to take the scissors and cut the code in several places, producing files such as header.tpl, footer.tpl etc. Next, we parse them in the requested order. However, Open Power Template actively defends itself against scissors and does not allow you to do so. There are two reasons for that. Firstly, OPT templates are XML documents, so you cannot open <body> tag in one file and close it in another. Another problem is the flexibility. The menu location is hard-coded in the application and if we had a layout where menu code should appear somewhere else, we would have to redesign the application structure.

As a result, we see that templates must be consistent. We are not allowed to simply cut them in several smaller files, so the library must provide an alternative. Indeed, OPT provides you two basic but powerful modularization techniques: including and template inheritance. We are going to describe them now.

Including templates

To include a template within another, we use opt:include instruction. As an argument, it takes either a view object or the template name. OPT looks for the template in the repository and executes it in the specified place. The effect resembles the PHP include() command. Of course, everything happens dynamically, so we can control what to show at any time. To illustrate the idea, let's take a look at the example:

$layoutView = new Opt_View('layout.tpl');
$layoutView->layoutData = 'Layout data';
 
if($action == 'foo')
{
    $view = new Opt_View('foo.tpl');
    $view->data = 'Some data';
}
else
{
    $view = new Opt_View('bar.tpl');
    $view->data = 'Some other data';
}
$layoutView->module = $view;
 
$output->render($layoutView);

The layout.tpl file looks like this:

<?xml version="1.0" ?>
<opt:root>
 
<p>This is my website.</p>
 
<!-- include a view stored in the $module variable -->
<opt:include view="$module" />
 
<p>Footer</p>
</opt:root>

Depending on the chosen action, the script displays different views - either with foo.tpl or bar.tpl template. We pass it to the template engine and to opt:include which loads it and displays in the requested place. Note that the included view contains custom variables and it does not see the layout view data. We can see it for ourselves by writing the following template foo.tpl:

<?xml version="1.0" ?>
<opt:root>
<p>My data: {$data}</p>
<p>Layout data: {$layoutData}</p>
</opt:root>

The result would be:

My data: Some data
Layout data:

The first variable is displayed correctly, as it belongs to the current view. However, $layoutData is a part of view layout which is not accessible here. In PHP functions, the situation is similar. Each function has its own variable scope and sees neither caller nor global variables... unless we import them. OPT lets you passing arguments to the view which is useful in some cases:

<opt:include file="template.tpl" />

We include a template, but specify its name explicitely. OPT creates a new, temporary view for it, but it does not contain any local variables. We can pass them as attributes to opt:include:

<opt:include file="template.tpl" foo="$variable" />

Now, the included template contains the $foo variable with the value of the attribute expression. View arguments work for the view loading (opt:include view), too. Finally, we may request to share the variable scope with the caller:

<opt:include file="template.tpl" import="yes" />

The import attribute imports all the calling view variables to the local view. Note that they are not references, so the caller would not see any changes made by the included template.

The file attribute expects a string by default. If we want to load the template name from a variable, we have to use the following construction: <opt:include parse:file="$variable" />.

The last interesting feature of opt:include is the support for exceptional situations. If we tried to load an unexisting template, OPT would throw an exception by default. However, we may force a different reaction: loading a different template or a default content:

<opt:include view="$view" default="noTemplate.tpl" />
 
<opt:include view="$view">
    <p>Cannot load the specified template.</p>
</opt:include>

Snippets

Snippets are a kind of macros. We may save a commonly used code into a snippet and insert it everywhere we need it. The modification is not a problem anymore. We have to edit the snippet only and the changes will be automatically propagated to all templates. Snippets are processed during compilation and they have two important features:

  1. They match to the context in a given template. For example, if our snippet contains a variable $foo and we insert it within a section foo, it starts to point to the current section element. In other places, $foo may be an ordinary variable. Data formats act similarly.
  2. We cannot select a snippet to execute dynamically, reading the name i.e. from a variable. The library must know everything during compilation and cannot be changed later.

Let's take a look at a sample code. A popular technique is to create a file called snippets.tpl, save there the snippets and load it in every template, so that we have an easy access to them. We will put there a pagination layout:

<?xml version="1.0"?>
<opt:root>
 
 <opt:snippet name="pagination">
    <div id="pagination">
        <div class="info">Page {$pageNumber} of {$pageCount}</div>
        <div class="list">Go to page:
            <opt:selector name="pages">
                <opt:page><a parse:href="$pages.url">{$pages.number}</a></opt:page>
                <opt:active><a parse:href="$pages.url" class="active">{$pages.number}</a></opt:active>
            </opt:selector>
        </div>
    </div> 
 </opt:snippet>
 
</opt:root>

Then we go to a concrete template where we would like to have pagination. All we have to do is to load snippets.tpl and insert the snippet:

<?xml version="1.0"?>
<opt:root include="snippets.tpl">
 
<h1>Product list</h1>
 
<p>Some list here...</p>
 
<opt:insert snippet="pagination" />
 
</opt:root>

Snippet files must not be loaded with opt:include, as it is a run-time instruction, so we make use of include argument in opt:root instruction. Such a file will be loaded during compilation and analyzed against snippets. Any other code in the template is ignored.

It is also possible to replace a content of a tag with a snippet:

<div opt:use="snippet">
    <p>Default content</p>
</div>

If the snippet is not loaded, OPT shows the default content instead of overwriting it.

Starting from Open Power Template 2.1, snippets can take arguments:

<?xml version="1.0"?>
<opt:root>
 
    <opt:snippet name="mySnippet" arg="required">
        <p>An argument: {$arg}</p>
    </opt:snippet>
 
    <opt:use snippet="mySnippet" arg="$variable" />
</opt:root>

For the sake of consistence, opt:insert instruction has been renamed to opt:use. There are a few such modifications in the new version, but we do not have to worry about them thanks to a compatibility mode and a template converter.

Remember that the compiler does not see snippets included with opt:include instruction.

Procedures

Procedures are a completely new feature introduced by Open Power Template 2.1. At first sight, they are very similar to snippets. We enclose a piece of code in them, specify arguments, give the name and use everywhere we need. However, there is one important difference. Procedures work on runtime which has the following consquences:

  1. Data formats and the way the procedure work are defined once and for all during compilation. All the templates the procedure is used must match it.

  2. We can dynamically select the procedure we wish to run.

Below, we see an example where the script decides what procedure is going to be used to display the loop content:

<?xml version="1.0"?>
<opt:root>
    <opt:procedure name="way1" data="required">
        <p>{@data.title}</p>
        {u:@data.body}
    </opt:procedure>
 
    <opt:procedure name="way2" data="required">
        <h1>{@data.title}</h1>
        {u:@data.body}
    </opt:procedure>
 
    <opt:section name="list">
        <opt:use procedure="$list.procedureName" data="$list" />
    </opt:section>
</opt:root>

Contrary to snippets, procedure arguments are registered as local template variables (followed by @ symbol instead of $ to avoid naming collisions). The procedure has also the full access to the variables of the view it was called in.

Unless you need a dynamic selection, we recommend to use snippets. They are processed during compilation which results in a faster execution, and furthermore they cause less problems thanks to the automatic context matching.

Template inheritance

Template inheritance is the most advanced modularization feature available in Open Power Template. It is based on snippets, so it is processed during the compilation (however, there is a trick that makes it more dynamic). The idea was borrowed from object-oriented programming. The templates act as classes, where the methods are replaced with snippets. The snippets can be overwritten by an extending template which may also add new snippets. The only difference is the base template. It does not contain any snippet, but the base HTML code where we put the snippets from extending templates. The describtion may seem very complex and abstract, so let's take a look, how to use it in practice. We start by writing the base template base.tpl:

<?xml version="1.0"?>
<opt:root>
<opt:prolog />
<opt:dtd template="html5" />
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title opt:use="titleSnippet">Default title</title>
 
        <opt:insert snippet="extraHeaders">
        <link rel="stylesheet" type="text/css" href="design/style.css" media="all"  />
        </opt:insert>
    </head>
    <body>
        <div id="header" opt:use="header">
            Default header.
        </div>
 
        <div id="content" opt:use="content">
            Default content.
        </div>
 
        <div id="footer" opt:use="footer">
            <p>© My Company 2010</p>
        </div>
    </body>
</html>
</opt:root>

Using opt:insert and their attribute version opt:use we have defined some placeholders that will be filled in by the extending templates depending on certain modules. If the template does not create a snippet for a given placeholder, OPT uses the default content from the base template. Now we can create a template for the home page (index.tpl):

<?xml version="1.0"?>
<opt:extend file="base.tpl">
    <opt:snippet name="title">My website</opt:snippet>

    <opt:snippet name="extraHeaders">
        <opt:parent />
        <link rel="stylesheet" type="text/css" href="design/news.css" media="all"  />
    </opt:snippet>

    <opt:snippet name="content">
        <h1>Welcome to my web page</h1>

        <p>Welcome to my web page!</p>
    </opt:snippet>
</opt:extend>

In an extending template, opt:root has been replaced with opt:extend. In the file attribute we specify the template to extend. OPT ignores the entire content of opt:extend except snippets which are displayed in the placeholders from the base template. Notice that the original, default content is not definitely lost. It can be still displayed in the overwriting snippet with opt:parent tag. It can also display the content of an overwritten snippet. This feature allows us to use the default CSS files defined in the base template even if we replace it with a custom extraHeaders snippet. Of course, index.tpl can be extended by another template which will add or overwrite some more snippets and so on. We get a chain of templates that produce a complete output during the compilation.

However, template inheritance has some limitations. The templates are executed within a single OPT view and they share the variables. What is more, we cannot select a template we want to extend on runtime because everything is calculated during compilation. The dynamic nature of template inheritance is based on a trick. We cannot load the template name from a variable, but we can create a special channel to pass such information to the compiler. In the template we just inform the compiler that it should expect dynamic data:

<?xml version="1.0"?>
<opt:extend file="base.tpl" dynamic="yes">
...

On the script side we have to use the inherit(extending template, extended template) method:

$view->inherit('index.tpl', 'differentLayout.tpl');

In this way we make index.tpl extend differentLayout.tpl instead of base.tpl as it is specified in a template. The extended template can be changed at any time, but we must remember that every possible combination is compiled separately. If we provide too many choices, OPT will produce a lot of compiled templates for all the possible cases.

There is also another dynamic feature called branches:

<?xml version="1.0"?>
<opt:extend file="base.tpl" simplified="simplified.tpl">
...

In the script:

$view->setBranch('simplified');

This time the script still has an opportunity to choose the case it needs, but lets the template to decide what physical template is extended in a specific case (which is called inheritance branch). If a template does not have the specified branch defined in the opt:extend tag, OPT uses the default file from file attribute which has to be always present.