diff --git a/docs/classic-ui/templates.md b/docs/classic-ui/templates.md index b9c012ab6..166214e29 100644 --- a/docs/classic-ui/templates.md +++ b/docs/classic-ui/templates.md @@ -1,13 +1,974 @@ --- myst: html_meta: - "description": "" - "property=og:description": "" - "property=og:title": "" - "keywords": "" + "description": "Page Templates in Plone Classic UI using TAL, TALES, and METAL" + "property=og:description": "Page Templates in Plone Classic UI using TAL, TALES, and METAL" + "property=og:title": "Templates" + "keywords": "Plone, Classic UI, templates, TAL, TALES, METAL, Chameleon, page templates" --- (classic-ui-templates-label)= # Templates +Page Templates are the primary way to generate HTML output in Plone Classic UI. +They are HTML files enhanced with special attributes written in TAL (Template Attribute Language), TALES (TAL Expression Syntax), and METAL (Macro Expansion for TAL). + +Plone uses [Chameleon](https://chameleon.readthedocs.io/) as its template engine, integrated through the Zope framework. +Chameleon is a fast HTML/XML template engine that implements the ZPT (Zope Page Templates) specification with additional features. + + +(templates-basics-label)= + +## Template basics + +A Page Template is a valid HTML or XML file with special `tal:`, `metal:`, and `i18n:` attributes that control how the template is rendered. + +Here is a minimal example: + +```html + + + + +

Hello, World!

+

Placeholder title

+
+ + + +``` + +The three parts serve different purposes: + +TAL (Template Attribute Language) +: Controls the structure and content of the output. + TAL attributes like `tal:content`, `tal:repeat`, and `tal:condition` modify how elements are rendered. + +TALES (TAL Expression Syntax) +: Defines the syntax for expressions used in TAL attributes. + TALES supports path expressions, Python expressions, string expressions, and more. + +METAL (Macro Expansion for TAL) +: Enables template reuse through macros and slots. + Use METAL to inherit from base templates and define reusable template fragments. + + +(templates-filesystem-vs-ttw-label)= + +## Filesystem vs. TTW templates + +Templates in Plone can be stored in two locations, with important security implications: + +**Filesystem templates** (recommended) +: Templates stored in your add-on package's directory structure, typically in `browser/templates/` or `views`. + These templates run as **trusted code** with full Python capabilities, just like any other code in your package. + They have no security restrictions on Python expressions. + +**TTW (Through-The-Web) templates** +: Templates created and stored in the ZODB through the Zope Management Interface (ZMI). + These templates run with **RestrictedPython** sandboxing for security. + They have limited Python functionality: no `import` statements, restricted builtins, and security-checked attribute access. + +```{note} +Always develop templates on the filesystem in your add-on package. +TTW templates are discouraged for production code because they are harder to maintain, version control, and test. +``` + + +(templates-tal-label)= + +## TAL statements + +TAL uses special attributes to control template rendering. +When an element has multiple TAL attributes, they execute in this order: + +1. `tal:define` +2. `tal:condition` +3. `tal:repeat` +4. `tal:content` or `tal:replace` +5. `tal:attributes` +6. `tal:omit-tag` + + +(templates-tal-define-label)= + +### `tal:define` + +Defines one or more variables for use in the template. + +```html +
+

Portal URL: ${portal_url}

+

User: ${user/getId}

+
+``` + +Use semicolons to define multiple variables. +Variables are available within the element and its children. + +For global variables accessible throughout the template, use `tal:define="global varname expression"`. + + +(templates-tal-condition-label)= + +### `tal:condition` + +Conditionally includes or excludes an element and its children. + +```html +

+ ${context/description} +

+ +

+ Found ${python:len(items)} items. +

+ + +

+ No description available. +

+``` + +If the condition evaluates to a false value, the entire element and all its children are removed from the output. + + +(templates-tal-repeat-label)= + +### `tal:repeat` + +Repeats an element for each item in a sequence. + +```html + + + + + + + +
${brain/Title}${brain/Description}
+``` + +Inside a repeat loop, you have access to the `repeat` variable which provides information about the current iteration: + +| Variable | Description | +|----------|-------------| +| `repeat/item/index` | Zero-based index (0, 1, 2, ...) | +| `repeat/item/number` | One-based index (1, 2, 3, ...) | +| `repeat/item/even` | True for even indices | +| `repeat/item/odd` | True for odd indices | +| `repeat/item/start` | True for the first item | +| `repeat/item/end` | True for the last item | +| `repeat/item/length` | Total number of items | +| `repeat/item/letter` | Lowercase letter (a, b, c, ...) | +| `repeat/item/Letter` | Uppercase letter (A, B, C, ...) | + +Example using repeat variables: + +```html + + + + + +
${repeat/item/number}${item/title}
+``` + + +(templates-tal-content-label)= + +### `tal:content` + +Replaces the content of an element with the expression result. + +```html +

Placeholder Title

+ +

+ This placeholder text will be replaced. +

+``` + +By default, content is HTML-escaped. +To insert raw HTML, use the `structure` keyword: + +```html +
+ Raw HTML will be inserted here. +
+``` + + +(templates-tal-replace-label)= + +### `tal:replace` + +Replaces the entire element (not just its content) with the expression result. + +```html +Placeholder + + + + +``` + + +(templates-tal-attributes-label)= + +### `tal:attributes` + +Sets or modifies HTML attributes dynamically. + +```html + + ${context/title} + + + + + + ... + +``` + +Setting an attribute to `None` removes it from the output. + + +(templates-tal-omit-tag-label)= + +### `tal:omit-tag` + +Removes the element tag but keeps its content. + +```html + + This text appears without any wrapper. + + + +
+ Content here +
+``` + + +(templates-tal-on-error-label)= + +### `tal:on-error` + +Provides error handling for template expressions. + +```html +
+

+ This might raise an exception. +

+
+``` + + +(templates-tal-block-label)= + +### Pure TAL blocks + +When you need TAL logic without generating HTML elements, use the `tal:block` style elements: + +```html + + +

${item/Title}

+
+
+``` + +You can use any tag name with the `tal:` prefix: + +```html + +

${item/Title}

+
+``` + + +(templates-tal-switch-label)= + +### `tal:switch` and `tal:case` (Chameleon extension) + +Chameleon provides switch/case statements as an extension to standard TAL: + +```html +
+

This is a document.

+

This is a news item.

+

This is an event.

+

This is something else.

+
+``` + + +(templates-tales-label)= + +## TALES expressions + +TALES (TAL Expression Syntax) defines how expressions are evaluated in templates. +Plone uses **path expressions** as the default expression type. + + +(templates-tales-path-label)= + +### Path expressions + +Path expressions traverse object attributes and items using `/` as a separator. + +```html + +

Title

+ + +

Search term

+ + +

URL

+ + +

Items from view

+``` + +The pipe operator `|` provides fallback values: + +```html + +

Fallback

+ + +

Optional

+ + +

Desc

+``` + +```{warning} +The `|` operator catches missing attributes and `None` values, but it also catches other errors. +Use it sparingly to avoid hiding bugs. +A typo like `context/ttle` (missing 'i') will silently use the fallback. +``` + + +(templates-tales-python-label)= + +### Python expressions + +Python expressions allow full Python syntax. +They must be prefixed with `python:`. + +```html +

TITLE

+ +

+ Showing first 10 of ${python:len(items)} items. +

+ + + + +

+ Title (Type) +

+``` + +In filesystem templates, you have full Python access. +In TTW templates, RestrictedPython limits what you can do. + +```{important} +In Plone templates, the default expression type is `path:`, not `python:`. +You must explicitly use the `python:` prefix for Python expressions. +This differs from standalone Chameleon where Python is the default. +``` + + +(templates-tales-string-label)= + +### String expressions + +String expressions create formatted strings with variable interpolation. + +```html + + Edit + + +

+ Greeting +

+ + +``` + +Inside `${...}` you can use any TALES expression, but the default is path. + + +(templates-tales-not-label)= + +### The `not:` expression + +Negates a boolean expression. + +```html +

+ No description available. +

+ +
+ No items found. +
+``` + + +(templates-tales-exists-label)= + +### The `exists:` expression + +Tests whether a path exists without evaluating it. + +```html +

+ Custom field exists: ${context/custom_field} +

+``` + + +(templates-tales-nocall-label)= + +### The `nocall:` expression + +Returns an object without calling it. + +```html + + + Method: ${python:method.__name__} + +``` + + +(templates-built-in-variables-label)= + +## Built-in variables + +Plone templates have access to several built-in variables: + +| Variable | Description | +|----------|-------------| +| `context` | The content object the view is called on | +| `view` | The browser view instance | +| `request` | The current HTTP request object | +| `template` | The template object itself | +| `options` | Additional options passed to the template | +| `nothing` | Equivalent to Python's `None` | +| `default` | Special value to keep the original content | +| `repeat` | Dictionary of repeat variables in loops | +| `attrs` | Original attributes of the current element (in METAL) | + +Additional variables available through helper views: + +```html + +

Portal URL: ${portal_url}

+

User: ${user/getId}

+

Please log in.

+
+``` + + +(templates-metal-label)= + +## METAL macros + +METAL enables template reuse through macros and slots. + + +(templates-metal-define-macro-label)= + +### Defining macros + +Create a reusable template fragment: + +```html + + + + +``` + + +(templates-metal-use-macro-label)= + +### Using macros + +Include a macro in another template: + +```html + +
+ Placeholder for user info +
+ + + + ... + +``` + + +(templates-metal-slots-label)= + +### Defining and filling slots + +Slots allow customization of macro content: + +```html + + +
+
+ Default Header +
+
+ Default Body +
+
+
+ + +
+ +

Custom Header

+
+ +

Custom body content here.

+
+
+``` + + +(templates-main-template-label)= + +### The `main_template` + +Plone's `main_template` provides the full page structure. +Most views fill slots in this template: + +```html + + + + + +

My Custom Content

+

Description

+
+ + + +``` + +Common slots in `main_template`: + +| Slot | Description | +|------|-------------| +| `top_slot` | For setting request parameters (e.g., disabling columns) | +| `head_slot` | Additional content in the HTML `` | +| `style_slot` | For additional CSS | +| `javascript_head_slot` | For additional JavaScript in head | +| `content` | The entire content area | +| `content-core` | The main content area (most commonly used) | +| `content-title` | The page title area | +| `content-description` | The description area | + +Example disabling columns: + +```html + + + +``` + + +(templates-interpolation-label)= + +## Expression interpolation + +Chameleon supports `${...}` syntax for inline expression interpolation in text and attribute values. + + +(templates-interpolation-text-label)= + +### Text interpolation + +```html +

Welcome, ${user/fullname}!

+ +

The current time is ${python:datetime.now().strftime('%H:%M')}.

+ +

You have ${python:len(items)} items in your cart.

+``` + +```{important} +In Plone, `${...}` uses path expressions by default. +Use `${python:...}` for Python expressions. +``` + + +(templates-interpolation-attributes-label)= + +### Attribute interpolation + +```html +Edit + +${context/title} + +
+ ... +
+``` + + +(templates-interpolation-structure-label)= + +### Structure interpolation + +To insert raw HTML without escaping: + +```html +
${structure:context/text/output}
+ + +``` + + +(templates-code-blocks-label)= + +### Python code blocks + +Chameleon allows inline Python code blocks (filesystem templates only): + +```html +
+ +

${greeting}, ${user/fullname}!

+
+``` + +```{warning} +Python code blocks work only in filesystem templates. +TTW templates may skip or restrict these blocks for security. +Use sparingly—complex logic belongs in the view class. +``` + + +(templates-i18n-label)= + +## Internationalization (i18n) + +Templates support translation markup for internationalization. + + +(templates-i18n-domain-label)= + +### Setting the translation domain + +```html + + ... + +``` + + +(templates-i18n-translate-label)= + +### Translating text + +```html + +

Welcome to our site!

+ + +

Welcome to our site!

+ + + +``` + + +(templates-i18n-attributes-label)= + +### Translating attributes + +```html + + +Company Logo +``` + + +(templates-i18n-name-label)= + +### Variable substitution in translations + +```html +

+ Welcome, ${user/fullname}! +

+ + +``` + + +(templates-best-practices-label)= + +## Best practices + + +(templates-best-practices-logic-label)= + +### Keep logic in Python + +Templates should focus on presentation. +Move complex logic to view classes: + +```python +# In your view class +class MyView(BrowserView): + + def get_formatted_items(self): + items = self.context.get_items() + return [ + { + 'title': item.title, + 'url': item.absolute_url(), + 'css_class': 'featured' if item.featured else 'normal', + } + for item in items + if item.is_published() + ] +``` + +```html + + +``` + + +(templates-best-practices-readable-label)= + +### Write readable templates + +Use meaningful variable names: + +```html + + + +``` + + +(templates-best-practices-escape-label)= + +### Always escape user content + +Content is escaped by default. +Only use `structure` when you trust the content: + +```html + +

User input

+ + +
Rich text
+``` + + +(templates-best-practices-fallbacks-label)= + +### Provide fallbacks + +Handle missing or empty values gracefully: + +```html +

+ Description +

+ + +``` + + +(templates-debugging-label)= + +## Debugging templates + + +(templates-debugging-variables-label)= + +### Inspecting available variables + +Use `context` to see all available variables: + +```html +
+
${name}
+
${python:repr(context[name])[:100]}
+
+``` + + +(templates-debugging-pdb-label)= + +### Using the debugger + +In filesystem templates, you can use pdb: + +```html + +``` + + +(templates-debugging-errors-label)= + +### Common errors + +**`KeyError` or `AttributeError`** +: Check for typos in path expressions. + Use `exists:` to test if a path exists. + +**Unexpected output** +: Remember that `tal:content` replaces content, `tal:replace` replaces the entire element. + +**Expression not evaluated** +: Ensure you're using the correct expression type prefix (`python:`, `string:`). + +**Encoding errors** +: Ensure your template files are saved as UTF-8. + + +(templates-reference-label)= + +## Quick reference + + +(templates-reference-tal-label)= + +### TAL statements + +| Statement | Purpose | Example | +|-----------|---------|---------| +| `tal:define` | Define variables | `tal:define="title context/title"` | +| `tal:condition` | Conditional rendering | `tal:condition="context/description"` | +| `tal:repeat` | Loop over items | `tal:repeat="item items"` | +| `tal:content` | Replace element content | `tal:content="context/title"` | +| `tal:replace` | Replace entire element | `tal:replace="context/title"` | +| `tal:attributes` | Set HTML attributes | `tal:attributes="href context/absolute_url"` | +| `tal:omit-tag` | Remove element, keep content | `tal:omit-tag=""` | +| `tal:on-error` | Error handling | `tal:on-error="string:Error"` | + + +(templates-reference-tales-label)= + +### TALES expression types + +| Type | Prefix | Default in Plone | Example | +|------|--------|------------------|---------| +| Path | `path:` | Yes | `context/title` | +| Python | `python:` | No | `python:len(items)` | +| String | `string:` | No | `string:Hello ${name}` | +| Not | `not:` | No | `not:context/description` | +| Exists | `exists:` | No | `exists:context/image` | +| Nocall | `nocall:` | No | `nocall:context/method` | + + +(templates-reference-metal-label)= + +### METAL statements + +| Statement | Purpose | Example | +|-----------|---------|---------| +| `metal:define-macro` | Define reusable macro | `metal:define-macro="card"` | +| `metal:use-macro` | Use a macro | `metal:use-macro="context/@@macros/card"` | +| `metal:define-slot` | Define customizable slot | `metal:define-slot="content"` | +| `metal:fill-slot` | Fill a slot | `metal:fill-slot="content"` | + + +## See also + +- {doc}`views` +- {doc}`viewlets` +- {doc}`template-global-variables` +- [Chameleon documentation](https://chameleon.readthedocs.io/) +- [Plone Training: Page Templates](https://training.plone.org/mastering-plone-5/zpt.html)