BlueBream The Web Component Framework

9.3. Zope Page Templates

9.3.1. Introduction

Page Templates are a web page generation tool. They help programmers and designers collaborate in producing dynamic web pages for web applications. Designers can use them to maintain pages without having to abandon their tools, while preserving the work required to embed those pages in an application.

In this chapter, you’ll learn the basic features of Page Templates, including how you can use them in your website to create dynamic web pages easily.

The goal of Page Templates is to allow designers and programmers to work together easily. A designer can use a WYSIWYG HTML editor to create a template, then a programmer can edit it to make it part of an application. If required, the designer can load the template back into his editor and make further changes to its structure and appearance. By taking reasonable steps to preserve the changes made by the programmer, the designer will not disrupt the application.

Page Templates aim at this goal by adopting three principles:

  1. Play nicely with editing tools.
  2. What you see is very similar to what you get.
  3. Keep code out of templates, except for structural logic.

A Page Template is like a model of the pages that it will generate. In particular, it is parseable by most HTML tools.

9.3.2. How Page Templates Work

Page Templates use the Template Attribute Language (TAL). TAL consists of special tag attributes. For example, a dynamic page headline might look like this:

<h1 tal:content="context/title">Sample Page Title</h1>

The tal:content attribute is a TAL statement. Since it has an XML namespace (the tal: part) most editing tools will not complain that they don’t understand it, and will not remove it. It will not change the structure or appearance of the template when loaded into a WYSIWYG editor or a web browser. The name content indicates that it will set the text contained by the h1 tag, and the value context/title is an expression providing the text to insert into the tag. Given the text specified by context/title resolves to Susan Jones Home Page, the generated HTML snippet looks like this:

<h1>Susan Jones Home Page</h1>

All TAL statements consist of tag attributes whose name starts with tal: and all TAL statements have values associated with them. The value of a TAL statement is shown inside quotes.

To the HTML designer using a WYSIWYG tool, the dynamic headline example is perfectly parseable HTML, and shows up in their editor looking like a headline should look like. In other words, Page Templates play nicely with editing tools.

This example also demonstrates the principle that What you see is very similar to what you get. When you view the template in an editor, the headline text will act as a placeholder for the dynamic headline text. The template provides an example of how generated documents will look.

When this template is saved in Zope and viewed by a user, Zope turns the dummy content into dynamic content, replacing “Sample Page Title” with whatever context/title resolves to. In this case, context/title resolves to the title of the object to which the template is applied. This substitution is done dynamically, when the template is viewed.

There are template statements for replacing entire tags, their contents, or just some of their attributes. You can repeat a tag several times or omit it entirely. You can join parts of several templates together, and specify simple error handling. All of these capabilities are used to generate document structures. Despite these capabilities, you can’t create subroutines or classes, perform complex flow control, or easily express complex algorithms using a Page Template. For these tasks, you should use Python-based application components.

The Page Template language is deliberately not as powerful and general-purpose as it could be. It is meant to be used inside of a framework (such as BlueBream) in which other objects handle business logic and tasks unrelated to page layout.

For instance, template language would be useful for rendering an invoice page, generating one row for each line item, and inserting the description, quantity, price, and so on into the text for each row. It would not be used to create the invoice record in a database or to interact with a credit card processing facility.

9.3.3. TALES Expressions

The expression template/title in your simple Page Template is a path expression. This is the most common type of expression. There are several other types of expressions defined by the TAL Expression Syntax (TALES) specification.

9.3.3.1. Path Expressions

The template/title path expression fetches the title attribute of the template. Here are some other common path expressions:

  • context/__name__: The name of the context object.
  • request/URL: The URL of the current web request.

To see what these examples return, just copy the following lines into a Page Template and select the Test tab. You’ll notice that context/objectValues returns a list that needs further treatment to be useful. We’ll come back to that later in this chapter:

<p tal:content="context/__name__"></p>
<p tal:content="request/URL"></p>
<p tal:replace="structure view/widgets/name"></p>

Every path expression starts with a variable name. The available variable names refer either to objects like context, request or view that are bound to every Page Template by default or variables defined within the Page Template using TAL.

If the variable itself returns the value you want, you are done. Otherwise, you add a slash (‘/’) and the name of a sub-object or attribute. You may need to work your way through several sub-objects to get to the value you’re looking for.

9.3.3.2. Python Expressions

A good rule of thumb is that if you need Python to express your logic, you better factor out the code into a method inside the view class. But BlueBream is a good tool for prototyping and sometimes it would be overkill to write a method for one line of code. And looking at existing packages you will see quite often Python expressions, so it’s better to know them.

Recall the first example of this chapter:

<h1 tal:content="context/title">Sample Page Title</h1>

Let’s try to rewrite it using a Python expression:

<h1 tal:content="python: context.title">Sample Page Title</h1>

While path expressions are the default, we need a prefix to indicate other expression types. This expression with the prefix python: does (at least here) the same as the path expression above. Path expressions try different ways to access title, so in general they are more flexible, but less explicit.

There are some simple things you can’t do with path expressions. The most common are comparing values like in:

"python: variable1 == variable2"

... or passing arguments to methods, e.g.:

"python: view.gettValues('test')"

9.3.4. TAL Attributes

Page Templates are example pages or snippets. TAL statements define how to convert them dynamically. Depending on the used TAL attribute they substitute example content or attributes by dynamic values, or remove or repeat example elements depending on dynamic values.

9.3.4.1. Inserting Text

In your ticketmain.pt template created in the ticket collector example, you used the tal:content statement on a bold tag. When you tested it, BlueBream replaced the content of the HTML bold element with the number of the ticket.

This is easy as long as we want to replace the complete content of an HTML element. But what if we want to replace only some words within an element?

In order to place dynamic text inside of other text, you typically use tal:replace on an additional span tag. For example, add the following lines to your example:

<p>The URL is
  <span tal:replace="request/URL">
    http://www.example.com</span>.</p>

The span tag is structural, not visual, so this looks like: The URL is http://www.example.com., when you view the source in an editor or browser. When you view the rendered version, however, it may look something like:

The URL is http://localhost:8080/test/simple_page.

If you look at the source code of the rendered version, the span tags are removed.

To see the difference between tal:replace and tal:content, create a page template and include the following in the body:

<b tal:content="context/__name__"></b>
<b tal:content="request/URL"></b>
<b tal:replace="context/__name__"></b>
<b tal:replace="request/URL"></b>

There are two other ways to add elements that are only needed for TAL attributes and that are removed again in the rendered version:

<p>The URL is
  <span tal:content="request/URL" tal:omit-tag="">
    http://www.example.com</span>.</p>

... which is more useful in other situations and will be discussed there and:

<p>The URL is
  <tal:span tal:content="request/URL">
    http://www.example.com</tal:span>.</p>

While you can get really far by using HTML elements and ‘tal:replace’ or tal:omit-tag, some people prefer to use TAL elements if the elements are only used to add TAL attributes. TAL is an attribute language and doesn’t define any elements like ‘tal:span’, but it uses a complete XML namespace and allows to use any element name you like. They are silently removed while the Page Template is rendered.

This is useful for using speaking names like tal:loop, tal:case or tal:span and to insert additional elements where HTML doesn’t allow elements like span or div. And if her browser or editor also ignores these tags, the designer will have less trouble with TAL elements than with additional HTML elements.

9.3.4.2. Repeating Structures

Let’s start with a simple three-liner:

<p tal:repeat="number python: range(4)" tal:content="number">
  999
</p>

number is our repeat variable and range(4) is a Python expression that returns the list [0, 1, 2, 3]. If this code is rendered, the repeat statement repeats the paragraph element for each value of the sequence, replacing the variable number by the current sequence value. So the rendered page will not show the example number 999, but 4 paragraph elements containing the numbers of our list.

In most cases we want to iterate over more complex sequences. Our next example shows how to use a sequence of (references to) objects. A template could be created with an item list, in the form of a list of the objects. You will make a table that has a row for each object, and columns for the id, meta-type and title. Add these lines to the bottom of your example template:

<table border="1" width="100%">
  <tr>
    <th>Id</th>
    <th>Meta-Type</th>
    <th>Title</th>
  </tr>
  <tr tal:repeat="item view/getValues">
    <td tal:content="item/getId">Id</td>
    <td tal:content="item/meta_type">Meta-Type</td>
    <td tal:content="item/title">Title</td>
  </tr>
</table>

The tal:repeat statement on the table row means “repeat this row for each item in my context’s list of object values”. The repeat statement puts the objects from the list into the item variable one at a time (this is called the repeat variable), and makes a copy of the row using that variable. The value of item/getId in each row is the Id of the object for that row, and likewise with item/meta_type and item/title.

You can use any name you like for the repeat variable (item is only an example), as long as it starts with a letter and contains only letters, numbers, and underscores (_). The repeat variable is only defined in the repeat tag. If you try to use it above or below the tr tag you will get an error.

You can also use the repeat variable name to get information about the current repetition.

9.3.4.3. Conditional Elements

Using Page Templates you can dynamically query your environment and selectively insert text depending on conditions. For example, you could display special information in response to a cookie:

<p tal:condition="request/cookies/verbose | nothing">
  Here's the extra information you requested.
</p>

This paragraph will be included in the output only if there is a ‘verbose’ cookie set. The expression, ‘request/cookies/verbose | nothing’ is true only when there is a cookie named ‘verbose’ set.

Using the tal:condition statement you can check all kinds of conditions. A tal:condition statement leaves the tag and its contents in place if its expression has a true value, but removes them if the value is false. Zope considers the number zero, a blank string, an empty list, and the built-in variable nothing to be false values. Nearly every other value is true, including non-zero numbers, and strings with anything in them (even spaces!).

Another common use of conditions is to test a sequence to see if it is empty before looping over it. For example in the last section you saw how to draw a table by iterating over a collection of objects. Here’s how to add a check to the page so that if the list of objects is empty no table is drawn.

To allow you to see the effect, we first have to modify that example a bit, showing only Folder objects in the context folder. Because we can’t specify parameters using path expressions like context/objectValues, we first convert it into the Python expression context.objectValues() and then add the argument that tells the objectValues method to return only sub-folders:

<tr tal:repeat="item python: context.objectValues(['Folder'])">

If you did not add any sub-folders to the template_test folder so far, you will notice that using the Test tab the table header is still shown even if we have no table body. To avoid this we add a tal:condition statement in the table tag. The complete table now looks like this:

<table tal:condition="python: context.objectValues(['Folder'])"
       border="1" width="100%">
  <tr>
    <th>Id</th>
    <th>Meta-Type</th>
    <th>Title</th>
  </tr>
  <tr tal:repeat="item python: context.objectValues(['Folder'])">
    <td tal:content="item/getId">Id</td>
    <td tal:content="item/meta_type">Meta-Type</td>
    <td tal:content="item/title">Title</td>
  </tr>
</table>

If the list of sub-folders is an empty list, the condition is false and the entire table is omitted. You can verify this by using the Test tab again.

Go and add three Folders named 1, 2, and 3 to the template_test folder in which your simple_page template lives. Revisit the simple_page template and view the rendered output via the Test tab. You will see a table that looks much like the below:

Id          Meta-Type          Title
1           Folder
2           Folder
3           Folder

9.3.4.4. Changing Attributes

Most, if not all, of the objects listed by your template have an icon attribute that contains the path to the icon for that kind of object. In order to show this icon in the meta-type column, you will need to insert this path into the src attribute of an img tag. Edit the table cell in the meta-type column of the above example to look like this:

<td><img src="file_icon.gif"
         tal:attributes="src item/icon" />
  <span tal:replace="item/meta_type">Meta-Type</span></td>

The tal:attributes statement replaces the src attribute of the img tag with the value of item/icon. The src attribute in the template (whose value is file_icon.gif) acts as a placeholder.

Notice that we’ve replaced the tal:content attribute on the table cell with a tal:replace statement on a span tag. This change allows you to have both an image and text in the table cell.

9.3.5. XML Page Templates

Creating XML with Page Templates is almost exactly like creating HTML. You switch to XML Mode by setting the content-type field to ‘text/xml’ or whatever the content-type for your XML should be.

In XML Mode no “loose” markup is allowed. Zope assumes that your template is well-formed XML. Zope also requires an explicit TAL and METAL XML namespace declarations in order to emit XML. For example, if you wish to emit XHTML, you might put your namespace declarations on the html tag:

<html xmlns:tal="http://xml.zope.org/namespaces/tal"
  xmlns:metal="http://xml.zope.org/namespaces/metal">

To browse the source of an XML template you go to source.xml rather than source.html.

9.3.5.1. Debugging and Testing

Zope helps you find and correct problems in your Page Templates. Zope notices problems at two different times: when you’re editing a Page Template, and when you’re viewing a Page Template. Zope catches different types of problems when you’re editing and than when you’re viewing a Page Template.

You may have already seen the trouble-shooting comments that Zope inserts into your Page Templates when it runs into problems. These comments tell you about problems that Zope finds while you’re editing your templates. The sorts of problems that Zope finds when you’re editing are mostly errors in your TAL statements. For example:

<!-- Page Template Diagnostics
 Compilation failed
 TAL.TALDefs.TALError: bad TAL attribute: 'contents', at line 10, column 1
-->

This diagnostic message lets you know that you mistakenly used tal:contents rather than tal:content on line 10 of your template. Other diagnostic messages will tell you about problems with your template expressions and macros.

If you don’t notice the diagnostic message and try to render a template with problems you’ll see a message like this:

Error Type: PTRuntimeError
Error Value: Page Template hello.html has errors.

That’s your signal to reload the template and check out the diagnostic message.

In addition to diagnostic messages when editing, you’ll occasionally get regular Zope errors when viewing a Page Template. These problems are usually due to problems in your template expressions. For example, you might get an error if an expression can’t locate a variable:

Error Type: KeyError
Error Value: 'unicorn'

This error message tells you that it cannot find the unicorn variable. To help you figure out what went wrong, Zope includes information about the environment in the traceback. This information will be available in your error_log (in your Zope root folder). The traceback will include information about the place where the error occurred and the environment:

URL: /sandbox/demo
Line 1, Column 14
Expression: standard:'context/unicorn'
Names:
  {'container': <Folder instance at 019AC4D0>,
   'context': <Application instance at 01736F78>,
   'default': <Products.PageTemplates.TALES.Default instance at 0x012F9D00>,
   ...
   'root': <Application instance at 01736F78>,
   'template': <ZopePageTemplate at /sandbox/demo>,
   'traverse_subpath': [],
   'user': admin}

This information is a bit cryptic, but with a little detective work it can help you figure out what went wrong. In this case, it tells us that the context variable is an Application instance. This means that it is the top-level Zope folder (notice how root variable is the same Application instance). Perhaps the problem is that you wanted to apply the template to a folder that had a unicorn property, but the root on which you called the template hasn’t such a property.

9.3.6. Macros

So far, you’ve seen how Page Templates can be used to add dynamic behavior to individual web pages. Another feature of page templates is the ability to reuse look and feel elements across many pages.

For example, with Page Templates, you can have a site that has a standard look and feel. No matter what the content of a page, it will have a standard header, side-bar, footer, and/or other page elements. This is a very common requirement for websites.

You can reuse presentation elements across pages with macros. Macros define a section of a page that can be reused in other pages. A macro can be an entire page, or just a chunk of a page such as a header or footer. After you define one or more macros in one Page Template, you can use them in other Page Templates.

9.3.7. Using Macros

You can define macros with tag attributes similar to TAL statements. Macro tag attributes are called Macro Expansion Tag Attribute Language (METAL) statements. Here’s an example macro definition:

<p metal:define-macro="copyright">
  Copyright 2009, <em>Foo, Bar, and Associates</em> Inc.
</p>

This metal:define-macro statement defines a macro named copyright. The macro consists of the p element (including all contained elements, ending with the closing p tag).

Macros defined in a Page Template are stored in the template’s macros attribute. You can use macros from other Page Templates by referring to them through the macros attribute of the Page Template in which they are defined. For example, suppose the copyright macro is in a Page Template called “master_page”. Here’s how to use the copyright macro from another Page Template:

<hr />
<b metal:use-macro="container/master_page/macros/copyright">
  Macro goes here
</b>

In this Page Template, the b element will be completely replaced by the macro when Zope renders the page:

<hr />
<p>
  Copyright 2009, <em>Foo, Bar, and Associates</em> Inc.
</p>

If you change the macro (for example, if the copyright holder changes) then all Page Templates that use the macro will automatically reflect the change.

Notice how the macro is identified by a path expression using the metal:use-macro statement. The metal:use-macro statement replaces the statement element with the named macro.

9.3.8. Macro Details

The metal:define-macro and metal:use-macro statements are pretty simple. However there are a few subtleties to using them which are worth mentioning.

A macro’s name must be unique within the Page Template in which it is defined. You can define more than one macro in a template, but they all need to have different names.

Normally you’ll refer to a macro in a metal:use-macro statement with a path expression. However, you can use any expression type you wish so long as it returns a macro. For example:

<p metal:use-macro="python:context.getMacro()">
  Replaced with a dynamically determined macro,
  which is located by the getMacro script.
</p>

In this case the path expression returns a macro defined dynamically by the ‘getMacro’ script. Using Python expressions to locate macros lets you dynamically vary which macro your template uses. An example of the body of a getMacro Script (Python) is as follows:

return container.ptMacros.macros['amacroname']

You can use the default variable with the metal:use-macro statement:

<p metal:use-macro="default">
  This content remains - no macro is used
</p>

The result is the same as using default with tal:content and tal:replace. The default content in the tag doesn’t change when it is rendered. This can be handy if you need to conditionally use a macro or fall back on the default content if it doesn’t exist.

If you try to use the nothing variable with metal:use-macro you will get an error, since nothing is not a macro. If you want to use nothing to conditionally include a macro, you should instead enclose the metal:use-macro statement with a tal:condition statement.

Zope handles macros first when rendering your templates. Then Zope evaluates TAL expressions. For example, consider this macro:

<p metal:define-macro="title"
   tal:content="template/title">
  template's title
</p>

When you use this macro it will insert the title of the template in which the macro is used, not the title of the template in which the macro is defined. In other words, when you use a macro, it’s like copying the text of a macro into your template and then rendering your template.

If you check the Expand macros when editing option on the Page Template Edit view, then any macros that you use will be expanded in your template’s source.

9.3.9. Using Slots

Macros are much more useful if you can override parts of them when you use them. You can do this by defining slots in the macro that you can fill in when you use the template. For example, consider a side bar macro:

<div metal:define-macro="sidebar">
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
  </ul>
</div>

This macro is fine, but suppose you’d like to include some additional information in the sidebar on some pages. One way to accomplish this is with slots:

<div metal:define-macro="sidebar">
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
  </ul>
  <span metal:define-slot="additional_info"></span>
</div>

When you use this macro you can choose to fill the slot like so:

<p metal:use-macro="container/master.html/macros/sidebar">
  <b metal:fill-slot="additional_info">
    Make sure to check out our <a href="/specials">specials</a>.
  </b>
</p>

When you render this template the side bar will include the extra information that you provided in the slot:

<div>
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
  </ul>
  <b>
    Make sure to check out our <a href="/specials">specials</a>.
  </b>
</div>

Notice how the span element that defines the slot is replaced with the b element that fills the slot.

9.3.10. Customizing Default Presentation

A common use of slot is to provide default presentation which you can customize. In the slot example in the last section, the slot definition was just an empty span element. However, you can provide default presentation in a slot definition. For example, consider this revised sidebar macro:

<div metal:define-macro="sidebar">
  <div metal:define-slot="links">
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
  </ul>
  </div>
  <span metal:define-slot="additional_info"></span>
</div>

Now the sidebar is fully customizable. You can fill the links slot to redefine the sidebar links. However, if you choose not to fill the links slot then you’ll get the default links, which appear inside the slot.

You can even take this technique further by defining slots inside of slots. This allows you to override default presentation with a fine degree of precision. Here’s a sidebar macro that defines slots within slots:

<div metal:define-macro="sidebar">
  <div metal:define-slot="links">
  Links
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/support">Support</a></li>
    <li><a href="/contact">Contact Us</a></li>
    <span metal:define-slot="additional_links"></span>
  </ul>
  </div>
  <span metal:define-slot="additional_info"></span>
</div>

If you wish to customize the sidebar links you can either fill the links slot to completely override the links, or you can fill the additional_links slot to insert some extra links after the default links. You can nest slots as deeply as you wish.

9.3.11. Combining METAL and TAL

You can use both METAL and TAL statements on the same elements. For example:

<ul metal:define-macro="links"
    tal:repeat="link context/getLinks">
  <li>
    <a href="link url"
       tal:attributes="href link/url"
       tal:content="link/name">link name</a>
  </li>
</ul>

In this case, getLinks is an (imaginary) Script that assembles a list of link objects, possibly using a Catalog query.

Since METAL statements are evaluated before TAL statements, there are no conflicts. This example is also interesting since it customizes a macro without using slots. The macro calls the getLinks Script to determine the links. You can thus customize your site’s links by redefining the getLinks Script at different locations within your site.

It’s not always easy to figure out the best way to customize look and feel in different parts of your site. In general you should use slots to override presentation elements, and you should use Scripts to provide content dynamically. In the case of the links example, it’s arguable whether links are content or presentation. Scripts probably provide a more flexible solution, especially if your site includes link content objects.

9.3.12. Whole Page Macros

Rather than using macros for chunks of presentation shared between pages, you can use macros to define entire pages. Slots make this possible. Here’s an example macro that defines an entire page:

<html metal:define-macro="page">
  <head>
    <title tal:content="context/title">The title</title>
  </head>

  <body>
    <h1 metal:define-slot="headline"
        tal:content="context/title">title</h1>

    <p metal:define-slot="body">
      This is the body.
    </p>

    <span metal:define-slot="footer">
      <p>Copyright 2010 Fluffy Enterprises</p>
    </span>

  </body>
</html>

This macro defines a page with three slots, headline, body, and footer. Notice how the headline slot includes a TAL statement to dynamically determine the headline content.

You can then use this macro in templates for different types of content, or different parts of your site. For example here’s how a template for news items might use this macro:

<html metal:use-macro="container/master.html/macros/page">

  <h1 metal:fill-slot="headline">
    Press Release:
    <span tal:replace="context/getHeadline">Headline</span>
  </h1>

  <p metal:fill-slot="body"
     tal:content="context/getBody">
    News item body goes here
  </p>

</html>

This template redefines the headline slot to include the words Press Release and call the getHeadline method on the current object. It also redefines the body slot to call the getBody method on the current object.

The powerful thing about this approach is that you can now change the page macro and the press release template will be automatically updated. For example you could put the body of the page in a table and add a sidebar on the left and the press release template would automatically use these new presentation elements.

blog comments powered by Disqus