ZPT Basics (part 3)

Learn about TAL loops, dynamically-generated attributes and error handlers.

A Twist In The Tail

If you've been following along, you now know a little more about ZPT, specifically how to introduce decision-making logic into your templates by means of variables and conditional tests. You also saw how all that theory can be put to practical use by implementing a simple form processor for use on a Web page.

That's not all, though - ZPT also comes with a couple of low-key loop constructs that allow you to iterate over sequences of both variables and objects. This ability to create loops is not unique to ZPT - almost every programming language on the planet allows you to do this - but as with DTML, ZPT includes some fairly interesting twists to the traditional approach. Let's take a look.

Playing The Numbers

Before we get started, let's get the jargon straight - what's a sequence anyway?

As traditionally understood, a "sequence" is a series of items, usually connected to each other by a logical thread. For example,

0, 1, 2, 3, 4, 5...

p, q, r, s, t...

huey, dewey, louie...

are all valid sequences

In the Zope context, the definition of a sequence can be scoped down a little further - a sequence here is usually a set of objects. For example, if you create a folder in Zope, the objects stored within that folder can be considered a sequence. Or, if you retrieve a set of records from a database, the resultset returned can also be considered a sequence of data items.

A sequence, however, is just one part of the jigsaw. In order to access the elements that make up a sequence, you usually need a programming structure that will iterate, or loop, through the sequence, processing each element in turn. This loop can be combined with conditional statements to perform specific actions or execute specific commands while processing the elements in a sequence.

In order to demonstrate this, let's create a sequence in Zope and write a loop to process it. Fire up Zope, log into the Zope management interface, and create an instance of the Page Template object. Name it "CountingDown"

and fill it with the following code:

<ul>
<li tal:repeat="countdown python:10,9,8,7,6,5,4,3,2,1,0">
<span tal:replace="countdown" />
</li>
</ul>

When you view the output, here's what you'll see:

* 10
* 9
* 8
* 7
* 6
* 5
* 4
* 3
* 2
* 1
* 0

First, I've created a simple sequence by brute force - it contains numbers from 0 to 10, in reverse order. Note the use of the "python" keyword - this is used to indicate that what follows is valid Python code.

The TAL "repeat" attribute is used to loop through the sequence, in a manner similar to that used in the "for" loops PHP and Perl programmers are familiar with. This "repeat" attribute takes two arguments - a variable name and an expression, which must evaluate to a sequence. For each sequence iteration, the value of the variable specified is set to the current element of the sequence. This variable value is then used with the TAL "replace" attribute to print the contents of each element in the sequence. From the output, it's obvious that this loop iterates eleven times to display the elements in the sequence.

When In Rome...

Not impressed yet? Let's alter the code a little to see what else you can do with sequences.

<span tal:repeat="fruits python:'apple', 'banana', 'orange', 'apricot', 'grape'">
<i tal:condition="repeat/fruits/start">Here's where it all
starts</i>
<br>
I am a <span tal:replace="fruits" />, my
sequence position is <span tal:replace="repeat/fruits/number" />,
and my index number is <span tal:replace="repeat/fruits/index" />.
<br>
<i tal:condition="repeat/fruits/end">And here's where it all
ends.</i>
</span>

Now take a look at the output.

Here's where it all starts
I am a apple, my sequence position is 1, and my index number is 0.

I am a banana, my sequence position is 2, and my index number is 1.

I am a orange, my sequence position is 3, and my index number is 2.

I am a apricot, my sequence position is 4, and my index number is 3.

I am a grape, my sequence position is 5, and my index number is 4.
And here's where it all ends.

How does this work? Well, you may remember, from your DTML exploits, that all sequences have built-in variables that can be used to identify the current pointer position within the sequence. The "number" variable represents the position of the current item in the sequence, while the "index" variable provides an alternative zero-based indexing mechanism that serves the same purpose. And the "start" and "end" variables provide an easy way to find out if you're at the beginning or end of the sequence - the former is true when the current item is the first element of the sequence and the latter is true when the current element is the last element of the sequence.

Now, in order to access these values in your Zope Page Templates, you need to use one or more TALES expressions. Here are the ones I've used:

repeat/fruits/start - A TALES expression for the value of the "start" variable

repeat/fruits/number - A TALES expression for the value of the "number" variable

repeat/fruits/index - A TALES expression for the value of the "index" variable

repeat/fruits/end - A TALES expression for the value of the "end" variable

You may remember, from earlier articles in this series, that every TALES path must begin with a variable name. "repeat" is one such pre-defined variable, and it stores information on the start and end position, number, index and length of the sequence. This information can then be accessed using a three-part path, such as the ones demonstrated above

The example you just saw used a simple sequence. However, you can also loop over a collection of objects using the same technique. Take a look at a simple example that lists the objects in the current folder.

This folder contains:
<ul>
<li tal:repeat="filelist container/objectIds">
<span tal:replace="filelist"/>
</li>
</ul>

Here's the output:

This folder contains:
* CountingDown

Here, I've used the "container" built-in variable together with the "objectIds" variable, which returns a sequence with the list of objects in the current folder.

Members Only

You can also use the "repeat" attribute to hook your template up to a database, and dynamically generate output based on an SQL resultset. Consider the following example, which demonstrates how the "repeat" attribute can be used to iterate through a MySQL resultset and dynamically build a table containing elements of that resultset.

<table cellpadding="5" cellspacing="0" border="1" width="250">
<tr bgcolor="#CFCFCF">
<td align="left"><b>User Details</b></td>
</tr>
<tr tal:repeat="memberlist here/SelectMembers">
<td align="left"><font size="2"><b><span tal:replace="memberlist/fname">First name here</span><span tal:replace="memberlist/lname">Last name here</span></b></font>
<br>
<font size="1"><i tal:content="memberlist/tel">Telephone number here</i>
<br>
<span tal:replace="memberlist/email">Email address here</span></font>
</tr>
</table>

In this example, SelectMembers is a ZSQL method used to retrieve a list of members from a database

SELECT * FROM members

as a sequence (for the uninitiated, a ZSQL Method is a special Zope object that allows you to communicate with a database). A ZPT loop iterates through it and prints the data as an HTML table.

Here's what the output looks like:

This example uses the "repeat" TAL attribute to loop over the result set returned by the SelectMembers ZSQL Method.

<tr tal:repeat="memberlist here/SelectMembers">

The TALES expression - "here/SelectMembers" - executes the ZSQL method. The rest of the code is good ol' HTML to make the page look pretty.

More information on the "repeat" attribute can be found at http://www.zope.org/Wikis/DevSite/Projects/ZPT/RepeatVariable

Sticks And Stones

ZPT also allows you to dynamically generate attributes and attribute values in a template, via the special "attributes" attribute (try saying that fast!). This TAL attribute makes it possible to dynamically assign values to HTML attributes, or to replace default attribute values with user-supplied ones.

In order to illustrate, consider the following simple template (named "SticksAndStones"):

<font tal:attributes="size request/size;color request/color; face request/face">
Sticks and stones will break my bones
</font>

In this case, the font face, colour and size will be dynamically set by ZPT based on values in the URL request. So, when you access this particular page with the following URL,

http://localhost:8080/SticksAndStones?size=8&color=red&face=Arial

here's what you'll see:

If you take a look at the source code of the dynamically-generated page, you'll see that ZPT has used the values passed on the URL within the template, via the special "attributes" attribute, to dynamically generate HTML attributes for the <font> tag.

You can also specify default values for your HTML attributes, and have ZPT replace them with user-defined ones if available. In order to illustrate this, consider the following rewrite of the previous example:

<font color="blue" size="2" face="verdana"
      tal:attributes="color request/color | default; size request/size | default; face request/face | default">
Sticks and stones will break my bones
</font>

In this case, if I access the URL

http://localhost:8080/SticksAndStones

without sending it any input parameters, the template will use the default attributes of the <font> tag,

whereas if I pass it specific values on the URL,

http://localhost:8080/SticksAndStones?size=8&color=lime&face=Arial

it will replace the default attribute values with the user-supplied values.

Note my usage of the | conditional operator and the special "default" keyword, which tells ZPT to use the default value in case a user-supplied value is not available.

Here's another example, this one demonstrating how the "attributes" attribute may be used in combination with a loop:

<span tal:repeat="s python:1,2,3,4,5">
<font face="Verdana" tal:attributes="size s">
Sticks and stones will break my bones
</font>
<br>
</span>

In this case, the "repeat" attribute is used to iterate through a sequence of font sizes, with the "attributes" attribute used in each iteration to dynamically generate a new "size" attribute for the <font> tag. Here's the output:

Handling Errors

You can add primitive error-handling to your templates via the special "on-error" attribute, which allows you to specify a simple error message that is displayed when TAL encounters an error. Consider the following example:

<font color="red" size="2" face="verdana"
      tal:on-error="string:Form input error"
      tal:attributes="color request/color; size request/size; face request/face">
Sticks and stones will break my bones
</font>

In this case, since my code requires the "size", "face" and "color" input variables to be specified as input parameters via the URL (notice that I have not used the "default" condition here, as I did in the previous example), an error will be generated if these variables are not present. However, rather than displaying the standard Zope error message,

I can intercept the error with TAL's "on-error" attribute, and replace it with my own, simpler error message.

"on-error" attributes are processed in a hierarchical manner, with TAL recursing upwards from the source of the error until it finds an "on-error" error handler. In the following example, even though the error is generated by the innermost <font> element, the error message generated comes from the outermost <div> element.

<div tal:on-error="string:Form input error">
<b><font color="red" size="2" face="verdana"
         tal:attributes="color request/color; size request/size; face request/face">
Sticks and stones will break my bones
</font>
</b>
</div>

This is obviously a primitive example - you can do some pretty complex things with the "on-error" attribute. Take a look at http://www.zope.org/Wikis/DevSite/Projects/ZPT/RenderErrorHandlingStrategies for more information.

Endgame

And that's about it for the moment. In this article, I gave you a crash course in the remaining TAL attributes, showing you how the "repeat" attribute could be used to iterate over sequences and replicate the functionality of the standard loop constructs found in other languages; I also demonstrated how this could be used in the real world, by dynamically building a Web page from a MySQL database and a single repeating template.

Next, I illustrated how ZPT lets you dynamically define attributes in your Web page, and also showed you how you could combine this capability with the "repeat" attribute discussed previously to iteratively build a Web page. Finally, I wrapped things up with a quick look at the primitive error-handling capabilities available in ZPT, demonstrating how it can provide a viable - if not very powerful - alternative to Zope's built-in error handler.

In the next - and concluding - article in this series, I'll be taking you on a guided tour of METAL, the macro expansion language that forms an important component of ZPT's reusability. Come back soon for that...and until then, stay healthy!

Note: All examples in this article have been tested on Linux/i586 with Zope 2.5.0. Examples are illustrative only, and are not meant for a production environment. Melonfire provides no warranties or support for the source code described in this article. YMMV!

This article was first published on04 Oct 2002.