The Road Ahead
In the first part of this series, I introduced you to Zope Page Templates, explaining how they offered Zope developers an advantage over traditional DTML by separating user interface code from program logic. I also told you a little bit about the three components of ZPT, and gave you a crash course in TAL syntax and expressions.
In this second part, I'm going to continue exploring the ZPT landscape, delving deeper into the mysteries of TAL with a look at the special TAL attributes used to define template variables and perform conditional processing in your templates. While I will be including lots of examples to help you understand the concepts better, you will need to have read the first part of this article in order to figure out what I'm driving at in this second one. In case you haven't already - go get yourself up to speed and then flip the page for more.
A Little Male Bonding
Variables are the bread and butter of any programming language - and TAL is no exception. In TAL, variables are defined via the special "define" attribute, which must be provided with both a variable name and a value (which can be any valid TALES expression). Let's take a look at a simple example, which demonstrates:
<h2>The name's <i tal:define="name string:Bond, James Bond" tal:content="string:$name">name here</i>.</h2>
Here's how Zope renders it:
The name's Bond, James Bond.
In this case, the "define" attribute is used to define a variable named "name", and assign it the value "Bond, James Bond". The "string" keyword in the expression is used to indicate that the following text is - well - a string.
<i tal:define="name string:Bond, James Bond" ... >name here</i>
This value may then be accessed using a TAL "content" or "replace" attribute, in combination with the variable name (which must be prefixed by a dollar ($) symbol).
<i ... tal:content="string: $name">name here</i>
You can even set variable values dynamically, by using TALES expressions instead of hard-wired string values. Consider the following example, which illustrates:
<h1>The name's <i tal:define="whoami template/title_or_id" tal:content="string:$whoami">name here</i>.</h1>
Here's the output (assuming that the page title was "Agent007"):
The name's Agent007.
The Number Game
Remember how I told you TALES expressions need not only be paths, but can be Python expressions too? Here's an example of how that might work:
<h1>10 * 34 is <i tal:define="product python:10*34" tal:replace="string:$product">Product here</i>.</h1>
In this case, instead of using the "string" keyword, I've used the "python" keyword, which instructs Zope to evaluate the Python expression and store the result in the variable "product". Here's the output:
10 * 34 is 340
You can define more than one variable at a time as well - take a look:
<h1>The name's <i tal:define="firstname string:Robin;lastname string:Hood" tal:content="string:$lastname, $firstname $lastname">name here</i>.</h1>
As you can see, in order to define two variables in a single statement, you simply need to separate them with a semi-colon (;). Here's the output:
The name's Hood, Robin Hood.
A Question Of Scope
If you look closely at all the examples on the previous page, you will see that I've defined and used every variable within the same element. This isn't really very practical - it's unlikely that you'll have the luxury of using a variable only at the time of definition in real-world development.
So the question you should be asking is, can a variable defined in one particular element be used in other elements too? Let's find out.
<span tal:define="name string:Bond, James Bond"></span> Now, who did you say you were again? <b tal:content="string:$name">name here again.</b>
In this code snippet, I've defined the variable "name" in one HTML element, but used it in another. And when I attempt to have Zope render this page for me, it barfs all over the screen...
Does this mean that variables can only be accessed within the same element? I thought so...until a quick read of the TAL documentation clarified things.
You see, in TAL, variables exist by default only within the local scope - the element in which they have been defined, and all the children of that element. In the example above, the
<b> element is not a child of the <span> element containing the variable definition - which is why Zope can't find it.
Now, if I were to rewrite the code above to make the
<b> element a child of the <span> element,
<span tal:define="name string:Bond, James Bond"> Now, who did you say you were again? <b tal:content="string:$name">name here again.</b> </span>
things would work a lot better.
Obviously, while this is certainly better than having a Zope error screen thrown at you, it's not really an optimal solution. Which is why TAL also offers the "global" keyword...
<span tal:define="global name string:Bond, James Bond" ></span> Now, who did you say you were again? <b tal:content="string:$name">name here again.</b>
As you've probably guessed by now, the "global" keyword allows you to define a particular variable as global, rather than local; this immediately makes it available to all the elements within a template, and eliminates the hassles of creating and maintaining complex parent-child relationships between the elements of your user interface.
This difference between global and local variables should be clearly understood; it's one of the most common errors newbies make, and it can lead to lots of head-scratching when things don't behave as you expect. Here's one more example, one which should help clear any lingering doubts:
<span tal:define="global flavour string:strawberry"></span> <br> -- global scope -- Current flavour is <span tal:content="string:$flavour">flavour here</span> <br> <span tal:define="local flavour string:peach">-- local scope -- Current flavour is <span tal:content="string:$flavour">flavour here</span>.</span> <br> -- global scope -- Current flavour is <span tal:content="string:$flavour">flavour here</span> <br>
Here's the output:
-- global scope -- Current flavour is strawberry -- local scope -- Current flavour is peach. -- global scope -- Current flavour is strawberry
In this case, the value of the variable "flavour" is set to "strawberry", globally. However, in the middle section of the template, this global value is overridden by a new local definition for the same variable, which sets it to "peach". And once the local scope is exited, the global value comes back and is used in all subsequent variable accesses.
Switching Things Around
Now that you know how variables work, it's time to examine the TAL "condition" attribute, which lets you add decision-making capabilities to your templates. In this section, I'll be examining conditional expressions and comparison operators, and demonstrating, with practical examples, how they can be used to craft more intelligent code.
Let's begin with a simple example:
<span tal:define="global switch string:on"></span> <span tal:condition="switch">You left the light switch on</span>
In this case, the visibility of the second
<span> depends on the presence or absence of the "switch" variable. If the variable is present, the second
<span> will be displayed; if not, it will remain hidden.
All this happens via the special TAL "condition" attribute, which, according to the TAL specification, is used to "...include a particular part of a Template only under certain conditions, and omit it otherwise". This TAL attribute is thus analogous to the traditional "if" statement found in all programming languages, and it can come in very handy when you need to add control flow routines to your templates.
The expression provided to the "condition" attribute must be a valid TALES expression, which evaluates to either true or false.
Comparing Apples and Oranges
The example you've just seen was very rudimentary. To really add some punch, you need to know how to construct what the geeks call a conditional statement. And the very basis of a conditional statement is comparison - for example, "if this is equal to that, do thus and such".
Since Zope is built around Python, and Python comes with a bunch of useful operators designed specifically for use in conditional statements, you can use Python operators to build conditional statements in your ZPT. Here's a list:
Assume $delta = 12 and $omega = 9
|Operator||What It Means||Expression||Evaluates To|
|==||is equal to||$delta == $omega||False|
|!=||is not equal to||$delta != $omega||True|
|>||is greater than||$delta > $omega||True|
|<||is less than||$delta < $omega||False|
|>=||is greater than/equal to||$delta >= $omega||True|
|<=||is less than/equal to||$delta <= $omega||False|
These comparison operators can be used for both strings and numbers. A positive result returns true, while a negative result returns false.
Here's an example which illustrates how these operators can be combined with the "condition" attribute discussed on the previous page to set up multiple decision branches within a template.
<span tal:define="global name string:max"></span> <span tal:condition="python:name == 'neo'"> <font face="Arial" size="2">Welcome to the Matrix, Neo. Access granted.</font> </span> <span tal:condition="python:name != 'neo'"> <font face="Arial" size="2">I wonder if you've heard of Shakespeare, <span tal:content="name">name here</span>. <p>He postulated that a rose by any other name would smell just as sweet.</p> <p>Unfortunately for you, I disagree. Access denied.</p></font> </span>
Here's what the output looks like:
Welcome to the Matrix, Neo. Access granted.
Now, try changing the value of the "name" variable in the first line of the template,
<span tal:define="global name string:joe"></span>
and look what happens:
I wonder if you've heard of Shakespeare, joe. He postulated that a rose by any other name would smell just as sweet. Unfortunately for you, I disagree. Access denied.
Nothing too complicated here. A quick glance at the code will show you that I've used the TAL "condition" attribute twice, in two different tests. The first time around, the first conditional test will evaluate to true and the second will evaluate to false, because the value of the variable is set to "neo"; consequently, the contents of the first
<span> block will be displayed, while the contents of the second will be omitted.
<span tal:condition="python:name == 'neo'"> <font face="Arial" size="2">Welcome to the Matrix, Neo. Access granted. </font></span>
The second time around, since the value of the variable is no longer "neo", the second test will evaluate to true and the contents of the second
<span> block will be displayed.
<span tal:condition="python:name != 'neo'"> <font face="Arial" size="2">I wonder if you've heard of Shakespeare, <span tal:content="name">name here</span>. <p>He postulated that a rose by any other name would smell just as sweet.</p> <p>Unfortunately for you, I disagree. Access denied.</p></font> </span>
I know what you're thinking - this would have been much easier to implement with an "if-else" conditional statement. Unfortunately, TAL doesn't have one of those yet; the only way to obtain that functionality is to simulate it via multiple "condition" attributes, as in the example above.
If It's Thursday, It Must be Italy
ZPT also lets you "nest" conditional statements. For example, this is perfectly valid ZPT code:
<span tal:define="global day string:Thursday; global time string:12; global place string:Italy"></span> <span tal:condition="python:day == 'Thursday'"> <span tal:condition="python:time == '12'"> <span tal:condition="python:place == 'Italy'"> How about some pasta for lunch? </span> </span> </span>
I'm sure you'll agree, though, that this is both complex and frightening. And so, in addition to the comparison operators I've used so liberally thus far, Python also provides the "and", "or" and "not" logical operators which allow you to group conditional expressions together. The following table should make this clearer.
Assume delta = 12, gamma = 12 and omega = 9
|delta == gamma and delta > omega||True|
|delta == gamma and delta < omega||False|
|delta == gamma or delta < omega||True|
|delta > gamma or delta < omega||False|
Given this knowledge, it's a simple matter to rewrite the example above in terms of logical operators:
<span tal:define="global day string:Thursday; global time string:12; global place string:Italy"></span> <span tal:condition="python:day == 'Thursday' and time == '12' and place == 'Italy'"> How about some pasta for lunch? </span>
Submitting To The King
You may remember, from the first part of this series, an example involving a form and a form processor. In that example, I used two Zope objects - a single DTML Document to display the form, and a separate page template to process the form input and generate appropriate output. However, ZPT provides an elegant method to combine those two pages into one via the "submit" variable.
In order to better understand this, create a Page Template named "DualPurposeForm" and add the following code to it:
<div tal:define="global submit request/form/submit | nothing" tal:condition="not:submit"> <form action="DualPurposeForm" method="POST"> Species: <br> <input name="species"> <p> Home planet: <br> <input name="planet"></p> <p> Distance (light years) from Earth: <br> <input name="distance"></p> <p> <input type="Submit" name="submit" value="Beam Me Up, Scotty"></p> </form> </div> <div tal:condition="submit"> <p>Welcome to Earth, <b tal:content="request/form/species">alien species name</b> from the planet <b tal:content="request/form/planet">planet name</b>. </p> <p>How was your journey? Travelling <b tal:content="request/form/distance">distance</b> light years must be quite a shock to the system. Why don't you relax and have a drink?</p> </div>
There are two conditional tests in the template above, both keyed on the presence or absence of the "submit" variable. The first time the template is accessed, the "submit" variable will not exist, and therefore the first conditional test will evaluate to true and the form will be displayed. Once the form has been submitted, the same template will be called again. However, this time around, the "submit" variable will exist in the request context, and so the second part of the template will be displayed.
There are a couple of things to note in the example above. First, the template variable "submit", which is set depending on the presence of a form "submit" variable in the request object. In case this variable does not exist, the template variable "submit" is set to the special value "nothing". You may remember, again from the first part of this series, that "nothing" is a special TAL variable used to represent a null value.
Second, the usage of the "not" keyword in the first conditional test. This keyword evaluates the TALES expression that follows, and returns the Boolean reverse of the result. Thus, the first time the template is accessed, the value of the "submit" variable will be set to null, the TALES expression will evaluate to Boolean false and the "not" negation will convert it to Boolean true...which serves as a flag to display the empty form.
This technique makes it possible to reduce the number of objects used, and perhaps make your Zope object collection easier to handle.
And that's about it for the moment. In this article, you learnt 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.
In the next segment of this series, I'll be rounding out the fundamentals with a look at the various loop constructs provided by TAL. As with everything you've seen so far, TAL comes with its own twist (pardon the pun) on the traditional way of implementing loops. Come back next time to see what I'm talking about...and, until then, go practise.
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 on 24 Sep 2002.