This is a style guide containing suggested best practices for writing Page Templates. It is not intended to be an authoritative set of rules. But this should be a good starting point.
To quote from PEP 8: Style Guide for Python Code :
A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important.
But most importantly: know when to be inconsistent - sometimes the style guide just doesn't apply. When in doubt, use your best judgement. Look at other examples and decide what looks best. And don't hesitate to ask!
Indentation and Line Breaks
tal: attributes with multiple bindings (i.e. tal:define,
tal:attributes) should have each binding on a separate line,
with the second and following bindings starting at the same column
as the first binding.
Example:
Bad:
<span tal:define="var1 context/getVar1; var2 request/var2 | context/var2;
var3 python:str(context.var3).split()"
>
...
</span>
Better:
<span tal:define="var1 context/getVar1;
var2 request/var2 | context/var2;
var3 python:str(context.var3).split()"
>
...
</span>
Best:
<span tal:define="
var1 context/getVar1;
var2 request/var2 | context/var2;
var3 python:str(context.var3).split()
">
...
</span>
(The last version is preferred because it's typically easiest to do in a text editor, and the lines are shorter.)
Do not place metal: or tal: attributes on the same line as any other
attribute (normal HTML, or even in the same namespace).
Example:
bad:
<a href="." tal:attributes="href container/absolute_url">My container</a>
better:
<a href="."
tal:attributes="href container/absolute_url">My container
</a>
Order of Attributes
tal: attributes should come in their "execution order", per the
TAL specification
The order is:
- tal:define
- tal:condition
- tal:repeat
- tal:content or tal:replace (an element may have only one of these)
- tal:attributes
- tal:omit-tag
Example:
<a tal:define="x context/getSomeObject"
tal:condition="x"
tal:content="x/title_or_id"
tal:attributes="href x/absolute_url_path"
>
Title Goes Here
</a>
On a given element, "normal" HTML attributes should come first, then
metal: attributes, then tal: attributes.
Example:
Bad:
<span tal:content="context/title"
metal:fill-slot="main"
class="big">
...
</span>
Better:
<span
class="big"
metal:fill-slot="main"
tal:content="context/title"
>
...
</span>
Use "context", not "here"
The "here" namespace should be deprecated in favor of "context." You will find that older templates (prior to Zope 2.7) use "here" liberally. For Zope 3, if not before, "here" will be removed. If you are using Zope 2.7 or higher, start using "context" in your templates now.
Example:
Bad:
<h1 tal:content="here/title">The Title</h1>
Better:
<h1 tal:content="context/title">The Title</h1>
Factoring for Readability
TALES expressions which can't be fit onto a single line, using the guidelines above, are definitely suspect: consider moving that logic out into a Python script, and replacing the expression with a simple path expression which calls the new script.
The script should be given a name which clearly and concisely communicates its purpose.
If there are a large number of expressions to refactor, it may be worthwhile to put the logic in a simple Product instead. (It would be nice if we had Zope3's "Persistent Modules").
Suggested Preferred Idioms
Always write an end tag for non-EMPTY elements, and do not write end tags for EMPTY elements. (The EMPTY elements in HTML are: br, hr, img, input, base, meta, link, area, param, col.) Examples:
Bad:
<span tal:content="context/foo" />
<img tal:replace="context/barimage"></img>
Better:
<span tal:content="context/foo"></span>
<img tal:replace="context/barimage" />
Use path expressions whenever possible. For simple string substitution, use a string expression. Only use python expressions if you cannot use either a path or string expression.
Examples:
Bad:
<tal:block replace="python:context.getDate()"> The Date </tal:block>
<a tal:attributes="href python:context.absolute_url() + "/foo"> Foo </a>
Better:
<tal:block replace="context/getDate"> The Date </tal:block>
<a tal:attributes="href string:${context/absolute_url}/foo"> Foo </a>
Use "not:" to negate non-Python expressions. Use the python "not" operator within Python expressions. This way, you limit the number of different systems you need to keep in mind while reading the expression: either Python or TALES, not both.
Examples:
Bad:
<span tal:condition="not:python:a==b">...</span>
<span tal:condition="python:not context.foo()">...</span>
Better:
<span tal:condition="python:not a==b">...</span>
<span tal:condition="not:context/foo">...</span>
Your unrendered template should closely reflect the rendered version - it should have the same HTML tags, even if some of them will be replaced. Accordingly, use "tal:block replace" whenever the result of the expression is not an HTML tag. (NOTE: if your HTML editor does not preserve unrecognized tags, you may have to use "tal:omit-tag" or "tal:replace" instead of "tal:block".)
Examples:
Bad:
<span tal:replace="string: Hi there!"></span>
Better:
<tal:block replace="string: Hi there!" />
Or, if you use an editor that does not preserve "tal:block", you could write
that as:
<span tal:replace="string: Hi there!"></span>
Conversely, use an HTML tag with "tal:replace" when the result of the expression will be the same HTML element.
Examples:
Bad:
<tal:block replace="context/someSpanTag"> This will be replaced with a SPAN tag. </tal:block>
Bad:
<p tal:replace="context/someSpanTag"> This will be replaced with a SPAN tag. </p>
Better:
<span tal:replace="context/someSpanTag"> This will be replaced with a SPAN tag. </span>