Zope Page Templates

Zope Page Templates let you define dynamic content using attributes on existing HTML/XML tags.


February 01, 2002
URL:http://www.drdobbs.com/web-development/zope-page-templates/184404974

Feb02: Zope Page Templates

Amos Latteier is a programmer for Zope Corp. and coauthor (with Michel Pelletier) of The Zope Book (New Riders Publishing, 2001). He can be contacted at [email protected].


Active Server Pages, Java Server Pages, PHP, Cold Fusion Markup Language, Server Side Includes, and other templating systems are designed to solve the problem of creating dynamic HTML and XML. They do so by embedding special tags in HTML or XML that are later evaluated to create dynamic content inserted into the template. However, systems such as these suffer from several flaws:

Zope Page Templates (ZPTs) are one solution to problems such as these. Rather than creating new tags that are interspersed with HTML/XML tags, ZPTs define dynamic content using attributes on existing HTML/XML tags. These attributes do not disturb the tags because the attributes belong to a different XML namespace.

Zope is an open-source web application platform for content management. It is freely available at http://www.zope.org/. Zope Page Templates are available as an add-on for Zope 2.4 and will be included as a core component in Zope 2.5.

How Page Templates Work

Here's an example of how you might create a dynamic page title with ZPTs:

<title tal:content="here/title">

Page Title </title>

The tal:content HTML attribute is a ZPT command. The tal: part is an XML namespace prefix (the XML namespace declaration is not included in this example). The attribute name content indicates that the template dynamically replaces the content of the title tag. The attribute value here/title provides information about the content to insert into the tag. When the template renders, it dynamically fetches the content and produces something like this:

<title>Keep Your Pigeons Flying</title>

Because ZPTs do not add tags or content to templates, they can be created with HTML/XML editors and previewed in web browsers. Browsers won't display ZPT attributes, and most editing tools leave them alone and aren't bothered by them. This lets you easily create and edit templates. Typically, designers create HTML templates using WYSIWYG tools that show how the page should look. Programmers then add ZPT attributes to selected tags to let them call application logic and insert dynamic content. Later, designers can change the templates without breaking them.

ZPTs let you insert content into tags, repeat tags, change tag attributes, test conditions, handle errors, and share page elements between templates. ZPTs do not let you write business logic or perform complex calculations. Your application takes care of the logic and data.

Using Zope Page Templates

You can insert content with the tal:content and tal:replace attributes. The tal:content attribute lets you insert dynamic content into a tag:

<p>Hello,

<b tal:content="user/getUserName">

Name</b>.</p>

When rendered, this template evaluates to something like this:

<p>Hello, <b>Christine</b>.</p>

Notice how the dummy content and tal attribute disappear from the rendered template. If you want to insert inline content without adding tags, use tal:replace like this:

<p>Hello,

<span tal:replace="user/getUserName">

Name</span>.</p>

This template might evaluate to something like this when rendered:

<p>Hello, Ricky.</p>

Notice here how the span tag is removed from the output. When using tal:replace, it's a good idea to use a span tag since it is not visible in a browser when previewing the template. This minimizes the visual differences between the template mock-up and rendered result.

You can also tailor your template's output by testing conditions with the tal:condition attribute. For example, if your application has a creditManager component, you could use it within a template like this:

<p> tal:condition=

"here/creditManager/exceededLimit">

Sorry, you have exceeded your credit

limit.</p>

This template calls the exceededLimit method of the creditManager application component and, if the result is True, displays a warning message. If the condition is False, the entire tag is removed, along with all contained tags. You can use tal:condition to test the request and current user, as well as the application. Condition testing in ZPTs is rudimentary; there are no "else" or "case" controls. For more complex calculations, you should use an application logic component.

ZPTs let you repeat tags using the tal:repeat attribute. For example:

<ul>

<li tal:repeat="person here/getNeighbors">

<span tal:replace="person/name">

name</span>,

<span tal:replace="person/occupation"> occupation</span>

</li>

</ul>

This template queries the getNeighbors application component for a sequence of results and creates an unordered list with one list item for each item in the results. The rendered template might look like this:

<ul>

<li>Dan Preece, Truck Driver</li>

<li>Danielle Yates, Student</li>

<li>Ken Bould, Architect</li>

</ul>

The tal:repeat attribute repeats its tag (and all contained tags) for each item of the sequence. It sets a local repeat variable for each iteration. In this example, the repeat variable is named person. You can name the repeat variable whatever you wish, which makes it easy to nest repeats inside each other.

Template Expressions

All tal attributes have associated value expressions. Unlike in HTML, tal attributes must have values. ZPTs use several types of expressions, including path expressions, string expressions, and Python expressions.

Path expressions let you refer to application components using path notation (slash separated steps); for example:

"here/title"

"template/author"

"request/form/name"

"root/objectValues"

Paths begin with a known component and may include a series of steps from that component to subcomponents or component properties. The most common built-in components are:

Depending on how ZPTs are implemented by your framework, there may be additional built-in components available.

String expressions let you create formatted strings with variable substitution. Variables are inserted using a dollar sign ($) followed by a variable name, optionally in brackets; for example:

"string:Just text."

"string:© $year, by $author."

"string:Three ${vegetable}s, please."

"string:Your name is ${user/getUserName}!"

String expressions are often used with tal:content and tal:replace attributes.

Python expressions consist of short Python expressions that are evaluated using a security-restricted environment. You can use Python expressions to call application objects, perform string formatting, perform simple conditional tests, and more. Python expressions are not appropriate for complex calculations. For example:

"python:user.age >= 18"

"python:modules['random'].choice(range(100))"

"python:here.objectIds(['Folder', 'File'])"

Python expressions must be valid Python, so when they refer to built-in variables such as modules, here, and user, they must use Python's subobject or subitem notation, rather than path notation. Python expressions are particularly valuable for tal:condition and tal:repeat attributes.

An Example

To illustrate how you can use ZPTs, let's develop the presentation layer for a web application. Suppose you are developing a web application to sell pigeon supplies. The presentation layer of the application consists of a page for each item in your catalog along with some query pages. The application's logic consists of components to handle database access, purchasing, and other logic. Begin by mocking up a page for the example catalog item in Figure 1. Listing One shows the HTML code for this page. HTML designers don't have to know anything about how the application works to build this mock-up.

Before attaching the mock-up to the application components, assume that application data is stored in a relational database using the item and order tables in Figure 2. Further suppose that you have application components to query these tables: queryItem returns a row from the item table given an item ID, and queryOrder returns one or more rows from the order table given an item ID (in Zope, these components would be implemented as ZSQL methods).

Use the tal:content attribute to make the template title dynamic:

<title tal:content="here/name">

Econo Feeder</title>

...

<h1 tal:content="here/name">

Econo Feeder</h1>

The tal:content attribute replaces the content of the title and h1 tags with the results of an expression. The expression here/name refers to the name variable in the application context in which the template is used. In this case, I use this template in the context of a queryItem query, and so the variables item_id, name, description, and image_url will be available. (In Zope terms, the here context would be a result item from a ZSQL method query.)

The description of the item can be made dynamic the same way:

<p tal:content="here/description">

description goes here</p>

To create a dynamic image, change the src and alt attributes on the img tag. You do this with the tal:attributes attribute:

<img src="econo-feeder.gif" alt="Econo Feeder"

tal:attributes="src here/image_url;

alt here/title">

Using tal:attributes, you can dynamically replace tag attributes.

At this point, you've made the template completely dynamic except for the ordering information at the bottom of the page. To make the order information dynamic, call the queryOrder component and loop over the results printing one table row for each result:

<tr tal:repeat="order here/queryOrder">

<td tal:content="order/number">

510-115</td>

<td tal:content="order/description">

24 inch</td>

...

</tr>

The tal:repeat attribute inserts a copy of its tag for each item in a sequence. In this case, the sequence is a list of rows returned by the queryOrder component. For each loop iteration, the order variable is set to the value of the current item. While this example shows how to insert the order number and description, how do you insert the price and order links? The answer is to use tal:replace and tal:attributes:

<tr tal:repeat="item here/queryOrder">

<td tal:content="item/number">

510-115</td>

<td tal:content="item/description">

24 inch</td>

<td>$<span tal:replace="item/price">

7.95</span></td>

<td><a href="orderItem?number=510-115"

tal:attributes="href

string:orderItem?number=${item/number}">

Add to cart</a></td>

</tr>

To format the price as a number with a dollar sign, you can use the tal:replace attribute on a span tag (you could also use a string expression). This causes the span tag to be replaced by the price.

You can create a dynamic href attribute on the anchor tag using tal:attributes. In this case, you create the href attribute using a string expression rather than a path expression in order to construct a link to the orderItem application component.

That's it. You've connected an application template. Listing Two is the completed template, which is as viewable and editable as HTML. As your application develops, you can change the template's connection to application components. This is the chief virtue of ZPTs — programmers and designers can collaborate without disrupting each other.

Sharing Presentation with Macros

All but the simplest of applications require more than one template. For example, you might want templates to handle online ordering, templates to query the catalog, and templates to display company information. Application templates should share a certain amount of look-and-feel; for example, they all might have a common navigation bar, and similar page headers and footers. ZPTs can share elements using a macro facility. For instance:

<b metal:define-macro="company">

King Super Pigeon Supply, Inc.</b>

The metal:define-macro attribute defines a macro consisting of the current tag and all contained tags. In this case, the macro is named company and consists of an HTML bold tag and some contained text. You can use a macro with the metal:use-macro attribute:

<p>As President of <span metal:use-macro= "container/master.html/macros/company"> company name</span>, I welcome you

to our web site.</p>

When the template is rendered, the span tag is replaced with the company macro:

<p>As President of <b>King Super Pigeon

Supply, Inc.</b>,I welcome you to

our web site.</p>

The path expression container/master.html/macros/company refers to a macro named "company" in a ZPT named master.html, which is located in the same folder as the current template. With macros, you can share page elements such as headers, footers, logos, and navigation bars.

You can customize macros using slots. Slots define parts of a macro that can be overridden when the macro is used. For example, you might want to have a shared navigation bar with a slot to allow local links:

<p metal:define-macro="navigation">

<a href="/">Home</a> |

<a href="/Help">Help</a> |

<span metal:define-slot="local-nav">

<a href="/PigeonInfo">Pigeon Info</a> |

</span>

<a href="/AboutUs">About Us</a>

</p>

The metal:define-slot attribute declares its tag (and all contained tags) as a slot. When you use the "navigation" macro you may choose to override the local-nav slot using the metal:fill-slot attribute:

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

<span metal:fill-slot="local-nav">

<a href="/PigeonInfo/Health">Pigeon Health</a> |

<a href="/PigeonInfo/Diseases">Pigeon Diseases</a>|

</span>

</span>

This would render to the following HTML code:

<p>

<a href="/">Home</a> |

<a href="/Help">Help</a> |

<span>

<a href="/PigeonInfo/Health">Pigeon Health</a> |

<a href="/PigeonInfo/Diseases">Pigeon Diseases</a> |

</span>

<a href="/AboutUs">About Us</a>

</p>

Slots let you create collections of templates that share elements while still being unique. One use of macros and slots is to create macros that cover the entire page, leaving only a few slots to be filled by other macros. For example:

<html metal:define-macro="page">

<head metal:define-slot="head">

<title tal:content="here/title">title</title>

</head>

<body metal:define-slot="body">

<p>Body text</p>

</body>

</html>

You can use macros and slots to create sophisticated relationships between templates. For example, slots can be defined inside other slots allowing fine-grained control over which page elements to override.

While these examples don't demonstrate it, macros are easily editable by HTML designers because they are expanded when a template is edited. The designer always sees the complete page, even if much of it comes from a macro. This lets designers always work with a valid and complete mock-up.

Conclusion

Zope Page Templates present a novel templating system that allows designers and programmers to collaborate on web applications without disturbing each other. ZPTs allow HTML (and XML) designers to work with complete and valid page mock-ups, while programmers get a simple but expressive system to bind templates to application content and logic. Unlike many other template systems, ZPTs do not provide a complete scripting environment. They adhere to the philosophy that logic, content, and presentation should be separate.

Acknowledgment

Thanks to Evan Simpson, Jim Fulton, and Michel Pelletier for their help on this article.

DDJ

Listing One

<html>
  <head>
    <title>Econo Feeder</title>
  </head>
  <body>
    <h1>Econo Feeder</h1>
    <img src="econo-feeder.gif" alt="Econo Feeder">
    <p>These feeders are made in Taiwan from a light-weight metal with
    plastic ends, and have an anti-spill edge to prevent the birds
    from kicking the feed out of the tray. They have a wooden rollbar
    to prevent the birds from sitting on them.</p>
    <table>
    <tr>
      <th>Item Number</th>
      <th>Description</th>
      <th>Price</th>
      <th></th>
    </tr>
    <tr>
      <td>510-115</td>
      <td>24 inch</td>
      <td>$7.95</td>
      <td><a href="orderItem?number=510-115">Add to cart</a></td>        
    </tr>
    <tr>
      <td>510-116</td>
      <td>36 inch</td>
      <td>$8.95</td>
      <td><a href="orderItem?number=510-116">Add to cart</a></td>        
    </tr>
    </table>
  </body>
</html>

Back to Article

Listing Two

<html>
  <head>
    <title tal:content="here/title">Econo Feeder</title>
  </head>
  <body>
    <h1 tal:content="here/title">Econo Feeder</h1>
    <img src="econo-feeder.gif" alt="Econo Feeder"
         tal:attributes="src here/image_url; alt here/title">
    <p tal:content="here/description">These feeders are made in Taiwan
    from a light-weight metal with plastic ends, and have an
    anti-spill edge to prevent the birds from kicking the feed out of
    the tray. They have a wooden rollbar to prevent the birds from
    sitting on them.</p>
    <table>
    <tr>
      <th>Item Number</th>
      <th>Description</th>
      <th>Price</th>
      <th></th>
    </tr>
    <tr tal:repeat="item here/queryOrder">
      <td tal:content="item/number">510-115</td>
      <td tal:content="item/description">24 inch</td>
      <td>$<span tal:replace="item/price">7.95</span></td>
      <td><a href="orderItem?number=510-115"
             tal:attributes="href string:orderItem?number=${item/number}">
             Add to cart</a></td>
    </tr>
    </table>
  </body>
</html>


Back to Article

Feb02: Zope Page Templates

Figure 1: Mock-up of a catalog item page. The HTML source of this page is shown in Listing One.

Feb02: Zope Page Templates

Figure 2: (a) Item table; (b) order table.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.