In Chapter 5, "Using Zope Page Templates" you learned the basics about Page Templates. In this chapter you'll learn about advanced techniques including new types of expressions and macros.
You've already learned about a few TAL statements. In this section we'll go over all TAL statements and their various options. Note, this material is covered more concisely in Appendix C, "Zope Page Templates Reference".
You've already seen how tal:content
and tal:replace
work in
Chapter 5, "Using Zope Page Templates". In this section you'll
learn some advanced tricks for inserting content.
Normally, the tal:replace
and tal:content
statements
escape HTML tags and entities in the text that they insert,
converting <
to <
, for instance. If you actually want
to insert the unquoted text, you need to precede the
expression with the structure
keyword. For example:
<p replace="structure here/story"> the <b>story</b> </p>
XXXAmos: first time since it is used and because it is used a lot in the following examples,
explain here
variable. -TD
This feature is useful when you are inserting a fragment of HTML or XML that is stored in a property or generated by another Zope object. For instance, you may have news items that contain simple HTML markup such as bold and italic text when they are rendered, and you want to preserve this when inserting them into a "Top News" page. In this case, you might write:
<p tal:repeat="newsItem here/topNews" tal:content="structure newsItem"> A news item with<code>HTML</code> markup. </p>
This will insert the news items including their HTML markup into paragraphs.
XXXAmos: Explain example more: If topNews returns a list of newsItems and is a method/list of the template?, then this will insert the news items including their HTML markup into paragraps. -TD
You can include page elements that are visible in the template
but not in generated text by using the built-in variable
nothing
, like this:
<tr tal:replace="nothing"> <td>10213</td><td>Example Item</td><td>$15.34</td> </tr>
This can be useful for filling out parts of the page that will be populated with dynamic content. For instance, a table that usually has ten rows will only have one row in the template. By adding nine dummy rows, the template's layout will look more like the final result.
It's not always necessary to use the tal:replace="nothing"
trick to get dummy content into your Page Template. For
example, you've already seen that anything inside a
tal:content
or tal:replace
element is normally removed
when the template is rendered. In these cases you don't have
do anything special to make sure that dummy content is
removed.
You can leave the contents of a tag along by using the
default
expression with tal:content
or tal:replace
. For
example:
<p tal:content="default">Spam<p>
This renders to:
<p>Spam</p>
Most often you will want to selectively include default content, rather than always including it. For example:
<p tal:content="python:here.getFood() or default">Spam</p>
Note: Python expressions are explained later in the
chapter. If the getFood
method returns a true value than its
result will be inserted into the paragraph, otherwise it's
Spam for dinner.
You've already seen most of what you can do with the
tal:repeat
statement in Chapter 5, "Using Zope Page
Templates". This section covers a few advanced features of the
tal:repeat
statement.
One topic that bears more explanation are repeat
variables. Repeat variables provide information about the
current repetition. The following attributes are available on repeat
variables:
You can access the contents of a repeat variable using path
expressions or Python expressions. In path expressions, you
write a three-part path consisting of the name repeat
, the
statement variable's name, and the name of the information you
want, for example, repeat/item/start
. In Python expressions,
you use normal dictionary notation to get the repeat variable,
then attribute access to get the information, for example,
'python:repeat['item'].start'.
XXXAmos: Repeat here the information (given in chapter5), concerning why you need to use repeat/item/start and not repeat/start. -TD
Here are a couple practical tips that you may find
useful. Some times you'd like to repeat a tag, but not have an
enclosing tag. For example, you might want to repeat a number
of paragraph tags, but there is no need to enclose them in
another tag. You can do this with the tal:omit-tag
statement:
<div tal:repeat="quote here/getQuotes" tal:omit-tag=""> <p tal:content="quote">quotation</p> </div>
The tal:omit-tag
statement is described later in this
chapter.
While it's been mentioned before, it's worth saying again: you
can nest tal:repeat
statements inside each other. Each
tal:repeat
statement must have a different repeat variable
name. Here's an example that shows a math times-table:
<table border="1"> <tr tal:repeat="x python:range(1, 13)"> <div tal:repeat="y python:range(1, 13)" tal:omit-tag=""> <td tal:content="python:'%d x %d = %d' % (x, y, x*y)"> X x Y = Z </td> </div> </tr> </table>
This example uses Python expressions, and the tal:omit-tag
statement both of which are covered later in this chapter.
If you've done much work with the dtml-in DTML repetition
statement, you will have encountered batching. Batching is the
process of chopping up a large list into smaller lists. You
typically use it to display a small number of items from a
large list on a web page. Think of how a search engine batches
its search results. The tal:repeat
statement does not
support batching, but Zope comes with a batching utility. See
the section, "Batching" later in this chapter.
Another useful feature that isn't supplied by tal:repeat
is
sorting. If you want to sort a list you can either write
your own sorting script (which is quite easy in Python) or you
can use the sequence.sort
utility function. Here's an
example of how to sort a list of objects by title, and then by
modification date:
<table tal:define="objects here/objectValues; sort_on python:(('title', 'nocase', 'asc'), ('bobobase_modification_time', 'cmp', 'desc')); sorted_objects python:sequence.sort(objects, sort_on)"> <tr tal:repeat="item sorted_objects"> <td tal:content="item/title">title</td> <td tal:content="item/bobobase_modification_time"> modification date</td> </tr> </table>
This example tries to make things clearer by defining the sort
arguments outside the sort
function. The
sequence.sort
function takes a sequence and a description of
how to sort it. In this example the description of how to sort
the sequence is defined in the sort_on
variable. See Appendix
B, "API Reference" for more information on the powerful
sequence.sort
function.
You've already met the tal:attributes
statement. You can use
it to dynamically replace tag attributes, for example, the
href
attribute on an a
element. You can replace more than
one attribute on a tag by separating attributes with
semicolons:
<a href="link" tal:attributes="href here/getLink; class here/getClass">link</a>
You can also define attributes with XML namespaces. For example:
<Description dc:Creator="creator name" tal:attributes="dc:Creator here/owner/getUserName"> Description</Description>
Simply put the XML namespace prefix before the attribute name and you can create attributes with XML namespaces.
You can define your own variable using the tal:define
attribute. There are several reasons that you might want to do
this. One reason is to avoid having to write long expressions
repeatedly in a template. Another is to avoid having to call
expensive methods repeatedly. You can define a variable once and
then use it many times in a template. For example, here's a list
that defines a variable and later tests it and repeats over it:
<ul tal:define="items container/objectIds" tal:condition="items"> <li tal:repeat="item items"> <p tal:content="item">id</p> </li> </ul>
The tal:define
statement creates the variable items
, which
you can use it anywhere in the ul
tag. Notice also how you
can have two TAL statement on the same ul
tag. See the
section, "Interactions Between TAL Statements" later in this
chapter for more information about using more than one statement
on a tag. In this case the first statement assigns the variable
items
and the second uses items
in a condition to see
whether or not it is false (in this case, an empty sequence) or
true. If the items
variable is false, then the ul
tag is not
shown.
Now, suppose that instead of simply removing the list when there are no items, you want to show a message. To do this, place the following before the list:
<h4 tal:condition="not:container/objectIds">There Are No Items</h4>
The expression, not:container/objectIds
is true when
container/objectIds
is false, and vice versa. See the section,
"Not Expressions" later in this chapter for more information.
You can't use your items
variable here, because it isn't
defined yet. If you move the definition of items
to the h4
tag, then you can't use it in the ul
tag any more, because it
becomes a local variable of the h4
tag. You could place the
definition on some tag that enclosed both the h4
and the ul
,
but there is a simpler solution. By placing the keyword
global
in front of the variable name, you can make the
definition last from the h4
tag to the bottom of the
template:
<h4 tal:define="global items container/objectIds" tal:condition="not:items">There Are No Items</h4>
XXXAmos: Isn't the following more appropriate? (ie In the above example, when h4-tag will be removed, we also loose our global items definition, which is still needed by the list)::
You can define more than one variable using tal:define
by
separating them with semicolons. For example:
<p tal:define="ids container/objectIds; title container/title">
You can define as many variables as you wish. Each variable can have its own global or local scope. You can also refer to earlier defined variables in later definitions. For example:
<p tal:define="title template/title; tlen python:len(title);">
With judicious use of tal:define
you can improve the
efficiency and readability of your templates.
XXXAmos: Give another example, telling if variables below are both global or not::
This template solves the problem by defining the n
variable on
an enclosing div
tag. Notice that the div
tag will not
appear in the output on account of it's tal:omit-tag
statement. This may be ugly, but it works. Perhaps future
versions of Page Templates will solve this problem in a nicer
fashion.
You can process forms in DTML using a common pattern called the "form/action pair". A form/action pair consists of two DTML methods or documents: one that contains a form that collects input from the user, and one that contains an action that is taken on that input and returns the user a response. The form calls the action. See Chapter 4, "Dynamic Content with DTML" for more information on the form/action pattern.
Zope Page Templates don't work particularly well with the form/action pattern since it assumes that input processing and response presentation are handled by the same object (the action). Instead of the form/action pattern you should use form/action/response pattern with Page Templates. The form and response should be Page Templates and the action should be a script. The form template gathers the input and calls the action script. The action script should process the input and return a response template. This pattern is more flexible than the form/action pattern since it allows the script to return any of a number of different response objects.
XXXAmos: explain/example '...input processing and response presentation are handled by the same object' -TD
For example here's a part of a form template:
... <form action="action"> <input type="text" name="name"> <input type="text" name="age:int"> <input type="submit"> </form> ...
This form could be processed by this script:
## Script (Python) "action" ##parameters=name, age ## container.addPerson(name, age) return container.responseTemplate()
This script calls a method to process the inputs and then returns another template, the response. You can render a Page Template from Python by calling it. The response template typically contains an acknowledgment that the form has been correctly processed.
The action script can do all kinds of things. It can validate input, handle errors, send email, and more. Here's a sketch of how to validate input with a script:
## Script (Python) "action" ## if not context.validateData(request): # if there's a problem return the form page template # along with an error message return context.formTemplate(error_message='Invalid data') # otherwise return the thanks page return context.responseTemplate()
This script validates the form input and returns the form
template with an error message if there's a problem. You can
pass Page Templates extra information with keyword
arguments. The keyword arguments are available to the template
as via the options
built-in variable. So the form template in
this example might include a section like this:
<b tal:condition="options/error_message | nothing" tal:content="options/error_message"> Error message goes here. </b>
This example shows how you can display an error message that is passed to the template via keyword arguments.
Depending on your application you may choose to redirect the user to a response Page Template instead of returning it directly. This results in twice as much network activity, but might be useful because it changes the URL displayed in the user's browser to the URL of the Page Template, rather than that of the action script.
XXXAmos: give example of such -TD
If you insist on doing a crummy job of things, you can always create a lame version of the form-action pair using Page Templates. You should only do this when you don't care about error handling and when the response will always be the same, not matter what the user submits. Since Page Templates don't have an equivalent of dtml-call, you can use one of any number of hacks to call an input processing method without inserting its results. For example:
<span tal:define="unused here/processInputs" tal:omit-tag=""/>
This sample calls the processInputs
method and assigns the
result to the unused
variable.
You've already encountered Page Template expressions. Expressions
provide values to template statements. For example, path
expressions describe objects by giving them paths such as
request/form/age
, or user/getUserName
. In this section you'll
learn about all the different types of expressions, and variables.
Variables are names that you can use in expressions. You have
already seen some examples of the built-in variables such as
template
, user
, repeat
, and request
. Here is the
complete list of the other built-in variables and their uses:
nothing
tal:replace
or tal:content
to erase a tag or its
contents. If you set an attribute to nothing
, the attribute
is removed from the tag (or not inserted), unlike a blank
string.default
tal:replace
, tal:content
, or tal:attributes
. It
leaves the template text in place.options
XXXAmos: What about if called from a Perl script? -TD
attrs
root
here
here
variable is analogous to the
context
variable in Python-based scripts.container
container
and
here
variables refer to the same object when a template is
called from its normal location. However, when a template is
applied to another object (for example, a ZSQL Method) the
container
and here
will not refer to the same object.modules
You'll find examples of how to use these variables through out this chapter.
String expressions allow you to easily mix path expressions with
text. All of the text after the leading string:
is taken and
searched for path expressions. Each path expression must be
preceded by a dollar sign ('$'). Here are some examples:
"string:Just text. There's no path here." "string:copyright $year, by me."
XXXAmos: is year the same as template/year
? -TD
If the path expression has more than one part, or needs to be separated from the text that follows it, it must be surrounded by braces ('{}'). For example:
"string:Three ${vegetable}s, please." "string:Your name is ${user/getUserName}!"
Notice how in the example above, you need to surround the
vegetable
path with braces so that Zope doesn't mistake it for
vegetables
.
Since the text is inside of an attribute value, you can only
include a double quote by using the entity syntax "
.
Since dollar signs are used to signal path expressions, a
literal dollar sign must be written as two dollar signs
('$$'). For example:
"string:Please pay $$$dollars_owed" "string:She said, "Hello world.""
Some complex string formatting operations (such as search and replace or changing capitalization) can't easily be done with string expressions. For these cases, you should use Python expressions or Scripts.
Path expressions refer to objects with a path that resembles a URL path. A path describes a traversal from object to object. All paths begin with a known object (such as a built-in variable, a repeat variable, or a user defined variable) and depart from there to the desired object. Here are some example paths expressions:
template/title container/files/objectValues user/getUserName container/master.html/macros/header request/form/address root/standard_look_and_feel.html
XXXAmos: With a user defined variable
as a know object for beginning the path, do you
mean that template/year
is the same as year
? -TD
With path expressions you can traverse from an object to its sub-objects including properties and methods. You can also use acquisition in path expressions. See the section entitled "Calling Scripts from the Web" in Chapter 8, "Advanced Zope Scripting" for more information on acquisition and path traversal.
Zope restricts object traversal in path expressions in the same way that it restricts object access via URLs. You must have adequate permissions to access an object in order to refer to it with a path expression. See Chapter 6, "Users and Security" for more information about object access controls.
The path template/title
is guaranteed to exist every time
the template is used, although it may be a blank string. Some
paths, such as request/form/x
, may not exist during some
renderings of the template. This normally causes an error
when Zope evaluates the path expression.
When a path doesn't exist, you may have a fall-back path or
value that you would like to use instead. For instance, if
request/form/x
doesn't exist, you might want to use here/x
instead. You can do this by listing the paths in order of
preference, separated by vertical bar characters ('|'):
<h4 tal:content="request/form/x | here/x">Header</h4>
Two variables that are very useful as the last path in a list
of alternates are nothing
and default
. For example,
default
tells tal:content
to leave the dummy
content. Different TAL statements interpret default
and
nothing
differently. See Appendix C, "Zope Page Templates
Reference" for more information.
You can also use a non-path expression as the final part in an alternate-path expression. For example:
<p tal:content="request/form/age|python:18">age</p>
In this example, if the request/form/age
path doesn't exist,
then the value is the number 18. This form allows you to
specify default values to use which can't be expressed as
paths. Note, you can only use a non-path expression as the
last alternative.
You can also test the existence of a path directly with the exists expression type prefix. See the section "Exists Expressions" below for more information on exists expressions.
Not expressions let you negate the value of other expressions. For example:
<p tal:condition="not:here/objectIds"> There are no contained objects. </p>
Not expressions return true when the expression they are applied to is false, and vice versa. In Zope, non-existent variables, zero, empty strings, empty sequences, nothing, and None are considered false, while everything else is true.
There isn't much reason to use not expressions with Python
expressions since you can use the Python not
keyword instead.
An ordinary path expression tries to render the object that it fetches. This means that if the object is a function, Script, Method, or some other kind of executable thing, then the expression will evaluate to the result of calling the object. This is usually what you want, but not always. For example, if you want to put a DTML Document into a variable so that you can refer to its properties, you can't use a normal path expression because it will render the Document into a string.
If you put the nocall:
expression type prefix in front of a
path, it prevents the rendering and simply gives you the
object. For example:
<span tal:define="doc nocall:here/aDoc" tal:content="string:${doc/getId}: ${doc/title}"> Id: Title</span>
This expression type is also valuable when you want to define a variable to hold a function or class from a module, for use in a Python expression.
Nocall expressions can also be used on functions, rather than objects:
<p tal:define="join nocall:modules/string/join">
This expression defines the join
variable as a function
('string.join'), rather than the result of calling a function.
An exists expression is true if its path exists, and otherwise is false. For example here's one way to display an error message only if it is passed in the request:
<h4 tal:define="err request/form/errmsg | nothing" tal:condition="err" tal:content="err">Error!</h4>
You can do the same thing more easily with an exists expression:
<h4 tal:condition="exists:request/form/errmsg" tal:content="request/form/errmsg">Error!</h4>
XXXAmos:Isn't
You can combine exists expressions with not expressions, for example:
<p tal:condition="not:exists:request/form/number">Please enter a number between 0 and 5</p>
Note that in this example you can't use the expression,
"not:request/form/number", since that expression will be true if
the number
variable exists and is zero.
The Python programming language is a simple and expressive one. If you have never encountered it before, you should read one of the excellent tutorials or introductions available at the Python website.
A Page Template Python expression can contain anything that the
Python language considers an expression. You can't use
statements such as if
and while
. In addition, Zope imposes
some security restrictions to keep you from accessing protected
information, changing secured data, and creating problems such
as infinite loops. See Chapter Chapter 8, "Advanced Zope
Scripting" for more information on Python security restrictions.
One place where Python expressions are practically necessary
is in tal:condition
statements. You usually want to compare
two strings or numbers, and there isn't any other way to do
that without Python expressions. You can use the comparison
operators <
(less than), >
(greater than), ==
(equal
to), and !=
(not equal to). You can also use the boolean
operators and
, not
, and or
. For example:
<p tal:repeat="widget widgets"> <span tal:condition="python:widget.type == 'gear'> Gear #<span tal:replace="repeat/widget/number>1</span>: <span tal:replace="widget/name">Name</span> </span> </p>
This example loops over a collection of objects, testing each
object's type
attribute.
XXXAmos:'...If the object is of type gear, than the name and count number is written down' -TD
Sometimes you want to choose different values inside a single
statement based on one or more conditions. You can do this
with the test
function, like this:
You <span tal:define="name user/getUserName" tal:replace="python:test(name=='Anonymous User', 'need to log in', default)"> are logged in as <span tal:replace="name">Name</span> </span>
XXXAmos: Explain example: If the user is Anonymous
, then the attribute is replaced
with the text need to log in
, otherwise the default content is used, which is in this
case are logged in ...
-TD
The test
function works like an if/then/else statement. See
Appendix A, "DTML Reference" for more information on the
test
function. Here's another example of how you can use the
test
function:
<tr tal:define="oddrow repeat/item/odd" tal:attributes="class python:test(oddrow, 'oddclass', 'evenclass')">
Without the test
function you'd have to write two tr
elements each with a different condition, one for even rows,
and the other for odd rows.
You can use other expression types inside of a Python
expression. Each expression type has a corresponding function
with the same name, including: path()
, string()
,
exists()
, and nocall()
. This allows you to write
expressions such as:
"python:path('here/%s/thing' % foldername)" "python:path(string('here/$foldername/thing'))" "python:path('request/form/x') or default"
The final example has a slightly different meaning than the path expression, "request/form/x | default", since it will use the default text if "request/form/x" doesn't exists or if it is false.
Much of the power of Zope involves tying together specialized objects. Your Page Templates can use Scripts, SQL Methods, Catalogs, and custom content objects. In order to use these objects you have to know how to get access to them within Page Templates.
Object properties are usually attributes, so you can get a template's title with the expression "template.title". Most Zope objects support acquisition, which allows you to get attributes from "parent" objects. This means that the Python expression "here.Control_Panel" will acquire the Control Panel object from the root Folder. Object methods are attributes, as in "here.objectIds" and "request.set". Objects contained in a Folder can be accessed as attributes of the Folder, but since they often have Ids that are not valid Python identifiers, you can't use the normal notation. For example, you cannot use this Python expression:
"python:here.penguin.gif"
XXXAmos: ... if you need to access the penguin.gif
object. -TD
You must write:
"python:getattr(here, 'penguin.gif')"
since Python doesn't support attribute names with periods.
Some objects, such as request
, modules
, and Zope Folders
support Python item access, for example:
request['URL'] modules['math'] here['thing']
When you use item access on a Folder, it doesn't try to acquire the name, so it will only succeed if there is actually an object with that Id contained in the Folder.
As shown in previous chapters, path expressions allow you to ignore details of how you get from one object to the next. Zope tries attribute access, then item access. You can write:
"here/images/penguin.gif"
instead of:
"python:getattr(here.images, 'penguin.gif')"
and:
"request/form/x"
instead of:
"python:request.form['x']"
The trade-off is that path expressions don't allow you to specify those details. For instance, if you have a form variable named "get", you must write:
"python:request.form['get']"
since this path expression:
"request/form/get"
will evaluate to the "get" method of the form dictionary.
If you prefer you can use path expressions inside Python
expressions using the path()
function, as described above.
Script objects are often used to encapsulate business logic and complex data manipulation. Any time that you find yourself writing lots of TAL statements with complicated expressions in them, you should consider whether you could do the work better in a Script. If you have trouble understanding your template statements and expressions, then it's better to simplify your Page Template and use Scripts for the complex stuff.
Each Script has a list of parameters that it expects to be given when it is called. If this list is empty, then you can use the Script by writing a path expression. Otherwise, you will need to use a Python expression in order to supply the argument, like this:
"python:here.myscript(1, 2)" "python:here.myscript('arg', foo=request.form['x'])"
If you want to return more than one item of data from a Script
to a Page Template, it is a good idea to return it in a
dictionary. That way, you can define a variable to hold all
the data, and use path expressions to refer to each item. For
example, suppose the getPerson
script returns a dictionary
with name
and age
keys:
<span tal:define="person here/getPerson" tal:replace="string:${person/name} is ${person/age}"> Name is 30</span> years old.
Of course, it's fine to return Zope objects and Python lists as well.
Unlike Scripts, DTML Methods and Documents don't have an explicit parameter list. Instead, they expect to be passed a client, a mapping, and keyword arguments. They use these parameters to construct a namespace. See Chapter 7 for more information on explicitly calling DTML.
When Zope publishes a DTML object through the web, it passes the context of the object as the client, and the REQUEST as the mapping. When one DTML object calls another, it passes its own namespace as the mapping, and no client.
If you use a path expression to render a DTML object, it will
pass a namespace with request
, here
, and the template's
variables already on it. This means that the DTML object will
be able to use the same names as if it were being published in
the same context as the template, plus the variable names
defined in the template.
XXXAmos: Perhaps give some examples on these, to get better grip on above more theoretical explanation -TD
The Python language comes with a large number of modules, which provide a wide variety of capabilities to Python programs. Each module is a collection of Python functions, data, and classes related to a single purpose, such as mathematical calculations or regular expressions.
Several modules, including "math" and "string", are available
in Python expressions by default. For example, you can get
the value of pi from the math module by writing
"python:math.pi". To access it from a path expression,
however, you need to use the modules
variable,
"modules/math/pi".
The "string" module is hidden in Python expressions by the
"string" expression type function, so you need to access it
through the modules
variable. You can do this directly in
an expression in which you use it, or define a global variable
for it, like this:
tal:define="global mstring modules/string" tal:replace="python:mstring.join(slist, ':')"
In practice you'll rarely need to do this since you can use string methods most of the time rather than having to rely on functions in the string module.
Modules can be grouped into packages, which are simply a way of organizing and naming related modules. For instance, Zope's Python-based Scripts are provided by a collection of modules in the "PythonScripts" subpackage of the Zope "Products" package. In particular, the "standard" module in this package provides a number of useful formatting functions that are standard in the DTML "var" tag. The full name of this module is "Products.PythonScripts.standard", so you could get access to it using either of the following statements:
tal:define="global pps modules/Products/PythonScripts/standard" tal:define="global pps python:modules['Products.PythonScripts.standard']"
Most Python modules cannot be accessed from Page Templates, DTML, or Scripts unless you add Zope security assertions to them. This procedure is outside the scope of this book. See the Zope Developer's Guide for more information.
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.
XXXAmos: I would write here also 'as well as you could/can with DTML,
although somewhat different. With DTML you glued
DTML documents/methods
together, while ZPT allows to create a framework of macros' -TD
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 web sites.
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.
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 2001, <em>Foo, Bar, and Associates</em> Inc. </p>
This metal:define-macro
statement defines a macro named
"copyright". The macro consists of the p
tag and its contents
(including all contained tags).
Macros defined in a Page Template are stored in the template's
macro
attribute. You can use macros from other page template
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 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
tag will be completely replaced
by the macro when Zope renders the page:
<hr> <p> Copyright 2001, <em>Foo, Bar, and Associates</em> Inc. </p>
XXXAmos:So isn't it better to use instead of ? Perhaps make a note of it. To not confuse the page designer with a tag which will never be rendered. If so, perhaps change all examples below to use the tag. What is the xml equivalient of ? -TD
If you change the macro (for example, if the copyright holder changes their name) 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.
XXXAmos:Perhaps give a number to every remark, ie summ them up -TD
The metal:define-macro
and metal:use-macro
statements are
pretty simple. However there are a few subtleties worth
mentioning.
A macro's name must be unique within the Page Template in which it's 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:here.getMacro()"> Replaced with a dynamically determined macro, which is located by the getMacro script. </p>
XXXAmos:Explain example; 'In this case the path expression is returned by the getMacro method defined somewhere in the container or parent-containers of the page template. -TD
Using Python expressions to locate macros lets you dynamically vary which macro your template uses.
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 statement element doesn't change.
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.
XXXAmos:Give example of above -TD
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. This is Zope's default behavior, and in general this is what you want, since it allows you to edit a complete and valid page. Sometimes, however, especially when you're editing in the ZMI, rather than using a WYSIWYG editing tool, it's more convenient not to expand macros when editing. In these cases, simply uncheck the option.
XXXAmos:Is it possible to define macros into macros? -TD
XXXAmos: The examples below give a error concerning the
tag, it looks like using the
tag in such a way isn't allowed in Zope. -TD
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:
<p 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> </p>
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:
<p 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> </p>
When you use this macro you can choose to fill the slot like so:
<p metal:fill-slot="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:
<p> 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> </p>
Notice how the span
element that defines the slot is replaced
with the b
element that fills the slot.
XXXAmos:Explain why or why not macros inside macros can be used instead of slots -TD
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"> <p 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> </p> <span metal:define-slot="additional_info"></span> </div>
XXXAmos: The above example gives an error concerning no closing
tag...-TD
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"> <p 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> </p> <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.
XXXAmos: I think it would be a good idea to write a paragraph concerning the differences between slots and macros. Why they both exist, where they are different and when they should be used The above example gives an error -TD
You can use both METAL and TAL statements on the same elements. For example:
<ul metal:define-macro="links" tal:repeat="link here/getLinks"> <li> <a href="link url" tal:attributes="url link/url" tal:content="link/name">link name</a> </li> </ul>
XXXAmos:Isn't it tal:attributes="href link/url" What kind of objects does getLinks return? A folder object doesn't return an url or name property Perhaps provide an example of getLinks script -TD
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 be 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.
XXXAmos: Isn't the following possible too?:
<span metal:fill-slot='container/master.html/macros/sidebar"> <span metal:fill-slot="links" tal:repeat="link here/getLinks"> <li> <a href="link url" tal:attributes="url link/url" tal:content="link/name">link name</a> </li> </span> </span>
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="here/title">The title</title> </head> <body> <h1 metal:define-slot="headline" tal:content="here/title">title</h1> <p metal:define-slot="body"> This is the body. </p> <span metal:define-slot="footer"> <p>Copyright 2001 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="here/getHeadline">Headline</span> </h1> <p metal:fill-slot="body" tal:content="here/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.
This is a much more flexible solution to control page look and
feel then the DTML standard_html_header
and
standard_html_footer
solution. In fact, Zope comes with a
stock page template in the root folder named
standard_template.pt
that includes a whole page macro with a
head
and body
slot. Here's how you might use this macro in a
template:
<html metal:use-macro="here/standard_template.pt/macros/page"> <div metal:fill-slot="body"> <h1 tal:content="here/title">Title</h1> <p tal:content="here/getBody">Body text goes here</p> </div> </html>
Using the standard_template.pt
macro is very similar to using
other whole page macros. The only subtlety worth pointing out is
the path used to locate the macro. In this example the path
begins with here
. This means that Zope will search for the
standard_template.pt
object using acquisition starting at the
object that the template is applied to. This allows you to
customize the look and feel of templates by creating custom
standard_template.pt
objects in various locations. This is
exactly the same trick that you can use to customize look and
feel by overriding standard_html_header
and
standard_html_footer
in site locations. However, with
standard_template.pt
you have more choices. You can choose to
start the path to the macro with root
or with container
, as
well as with here
. If the path begins with root
then you
will always get the standard template which is located in the
root folder. If the path begins with container
then Zope will
search for a standard template using acquisition starting in the
folder where the template is defined. This allows you to
customize look and feel of templates, but does not allow you to
customize the look and feel of different objects based on their
location in the site.
XXXAmos: perhaps give here more information on the difference of the here
and container
variable. I myself have still problems to know exactly when to use which :-( -TD
While rendering Page Templates normally is quite fast, sometimes it's not fast enough. For frequently accessed pages, or pages that take a long time to render, you may want to trade some dynamic behavior for speed. Caching lets you do this. For more information on caching see the "Cache Manager" section of Chapter 3, "Basic Objects".
You can cache Page Templates using a cache manager in the same way that you cache other objects. To cache a Page Template, you must associate it with a cache manager. You can either do this by going to the Cache view of your Page Template and selecting the cache manager, or by going to the Associate view of your cache manager and locating your Page Template.
XXXAmos:I would say '...to the Cache view (available when there exists a cache manager along the aquisition path' -TD
Here's an example of how to cache a Page Template. First create a
Python-based script name long.py
with these contents:
## Script (Python) "long.py" ## for i in range(500): for j in range(500): for k in range(5): pass return 'Done'
The purpose of this script is to take up a noticeable amount of execution time. Now create a Page Template that uses this script, for example:
<html> <body> <p tal:content="here/long.py">results</p> </body> </html>
Now view this page. Notice how it takes a while to render. Now let's radically improve its rendering time with caching. Create a Ram Cache Manager if you don't already have one. Make sure to create it within the same folder as your Page Template, or in a higher level. Now visit the Cache view of your Page Template. Choose the Ram Cache Manager you just created and click Save Changes. Click the Cache Settings link to see how your Ram Cache Manager is configured. By default, your cache stores objects for one hour (3600 seconds). You may want to adjust this number depending on your application. Now return to your Page Template and view it again. It should take a while for it to render. Now reload the page, and watch it render immediately. You can reload the page again and again, and it will always render immediately since the page is now cached.
If you change your Page Template, then it will be removed from the cache. So the next time you view it, it will take a while to render. But after that it will render quickly since it will be cached again.
Caching is a simple but very powerful technique for improving performance. You don't have to be a wizard to use caching, and it can provide great speed-ups. It's well worth your time to use caching for performance-critical applications.
XXXAmos:can you refer to some techniques/tips to use the cache wisely?-TD
Zope Page Templates are powerful but simple. Unlike DTML, Page Templates don't give you a lot of convenience features for things like batching, drawing trees, sorting, etc. The creators of Page Templates wanted to keep them simple. However, you may miss some of the built-in features that DTML provides. To address these needs, Zope comes with utilities designed to enhance Page Templates.
When a user queries a database and gets hundreds of results, it's often better to show them several pages with only twenty results per page, rather than putting all the results on one page. Breaking up large lists into smaller lists is called batching.
Unlike DTML, which provides batching built into the language,
Page Templates support batching by using a special Batch
object that comes from the ZTUtils
utility module. See
Appendix B, "API Reference", for more information on the
ZTUtils
Python module.
Here's a simple example, showing how to create a Batch
object:
<ul tal:define="lots python:range(100); batch python:modules['ZTUtils'].Batch(lots, size=10, start=0)"> <li tal:repeat="num batch" tal:content="num">0 </li> </ul>
This example renders a list with 10 items (in this case, the
numbers 0 through 9). The Batch
object chops a long list up
into groups or batches. In this case it broke a one hundred item
list up into batches of ten items.
You can display a different batch of ten items by passing a different start number:
<ul tal:define="lots python:range(100); batch python:modules['ZTUtils'].Batch(lots, size=10, start=13)">
This batch starts with the fourteenth item and ends with the
twenty third item. In other words, it displays the numbers 13
through 22. It's important to notice that the batch start
argument is the index of the first item. Indexes count from
zero, rather than from one. So index 13 points to the fourteenth
item in the sequence. Python uses indexes to refer to list
items.
Normally when you use batches you'll want to include navigation elements on the page to allow users to go from batch to batch. Here's a full-blow batching example that shows how to navigate between batches:
<html> <head> <title tal:content="template/title">The title</title> </head> <body tal:define="employees here/getEmployees; start python:path('request/start') or 0; batch python:modules['ZTUtils'].Batch(employees, size=10, start=start); previous python:batch.previous; next python:batch.next"> <p> <a tal:condition="previous" tal:attributes="href string:${request/URL0}?start:int=${previous/first}" href="previous_url">previous</a> <a tal:condition="next" tal:attributes="href string:${request/URL0}?start:int=${next/first}" href="next_url">next</a> </p> <ul tal:repeat="employee batch" > <li> <span tal:replace="employee/name">Bob Jones</span> makes $<span tal:replace="employee/salary">100,000</span> a year. </li> </ul> </body> </html>
This example iterates over batches of results from the
getEmployees
ZSQL Method. It draws a previous and a next
link as necessary to allow you to page through all the results a
batch at a time.
Take a look at the tal:define
statement on the body
element. It defines a bunch of batching variables. The
employees
variable is a potentially big list of employee
objects returned by the getEmployees
ZSQL Method. The second
variable, start
, is either set to the value of request/start
or to zero if there is no start
variable in the request. The
start
variable keeps track of where you are in the list of
employees. The batch
variable is a batch of ten items from the
lists of employees. The batch starts at the location specified
by the start
variable. The previous
and next
variables
refer to the previous and next batches (if any). Since all these
variables are defined on the body
element, they are available
to all elements inside the body.
Next let's look at the navigation links. They create hyper links
to browse previous and next batches. The tal:condition
statement first tests to see if there is a previous and next
batch. If there is a previous or next batch, then the link is
rendered, otherwise there is no link. The tal:attributes
statement creates a link to the previous and next batches. The
link is simply the URL or the current page ('request/URL0')
along with a query string indicating the start index of the
batch. For example, if the current batch starts with index 10,
then the previous batch will start with an index of 0. The
first
variable of a batch gives its staring index, so in this
case, previous.start
would be 0.
It's not important to fully understand the workings of this
example. Simply copy it, or use a batching example created by
the Z Search Interface. Later when you want to do more complex
batching you can experiment by changing the example code. Don't
forget to consult Appendix B, "API Reference" for more
information on the ZTUtils
module and Batch
objects.
Zope provides a couple Python modules which may come in handy
when using Page Templates. The string
, math
, and random
modules can be used in Python expressions for string formatting,
math function, and pseudo-random number generation. These same
modules are available from DTML and Python-based scripts. See
Appendix B, "API Reference" for more information on these
modules.
The Products.PythonScripts.standard
module is designed to
provide utilities to Python-based scripts, but it's also useful
for Page Templates. It includes various string and number
formatting functions. See Appendix B, "API Reference" for more
information.
As mentioned earlier in the chapter, the sequence
module
provides a handy sort
function. See Appendix B, "API
Reference" for the details.
Finally the AccessControl
module includes a function and a
class which you'll need if you want to test access and to get
the authenticated user. See Appendix B, "API Reference" for more
information.
This chapter covers all the nooks and crannies of Page Templates, and after reading it you may feel a little overwhelmed. Don't worry, you don't need to know everything in this chapter to effectively use Page Templates. You should understand the different path types and macros, but you can come back to the rest of the material when you need it. The advanced features that you've learned about in this chapter are there for you when you need them. It's encouraging to know that when you're ready you can do some pretty impressive tricks with Page Templates.