Using Macros and METAL in Plone Page Templates
Page templates are used to display dynamic data in Plone. The syntax used (TALES) is pretty straightforward and is covered in depth elsewhere. While it’s fairly easy to understand TALES syantax and start creating your own simple page templates, it can sometimes be confusing as to the best way to:
- Create page templates that have the look and feel of the rest of your Plone site.
- Modify specific areas of a Plone site (such as the footer area)
- Reuse a section of code across many different page templates.
The ways these tasks are accomplished are using a special set of tags. The tags use the namespace “metal” which stands for Macro Expansion Template Attribute Language. Here’s a very minimal example of metal at work:
<html metal:use-macro="here/main_template/macros/master"> </html>
The preceeding template produces an html page with the Plone look and feel and no content as in the screen shot below.
How does this work? The page template we created above references a snippet of template code found using a path expression. This snippet is refered to as a "macro". The path expression in this case is "here/main_template/macros/master":
Specifically, this path expression points us to a file name main_template.pt and a macro it defines named “master”. The template code from the "master" macro is evaluated, returned as html to the calling template and finally returned to the end user’s browser as a web page. The diagram below attempts to illustrate this interaction. Not too confusing yet I hope!
What if we want to add some of our own text to the page before it’s returned to the user? After all, a completely empty page won’t be of much value to anyone. The key to accomplishing this is to populate pre-defined placeholders called "slots". Slots allow you to customize pre-defined sections in the html that's returned from a macro. Let’s look at some example code that returns a slightly more interesting page than our last template:
<html metal:use-macro="here/main_template/macros/master"> <div metal:fill-slot="main"> Hello Metal! </div> </html>
The preceding template will produce a page that uses the standard Plone layout but now with the text “Hello Metal!” in the body. See the following screen shot:
Watch out for this common error: make sure you always include the metal namespace! While this might look correct, it will give you a nasty error that may be hard to understand at first:
<html use-macro="here/main_template/macros/master"> <div metal:fill-slot="main"> Hello Metal! </div> </html>
This template will generate the following error:
zope.tal.taldefs.METALError: fill-slot must be within a use-macro, at line 2, column 1
You’ll need to add the “metal” namespace to make the template work properly.
How does this all work? Let’s look at this example closely to see exactly what we did.
As in the first example, we call the “master” macro from the main_template.pt page template to get our fully constructed plone looking html page. The next trick is to get our custom text into the body of the response. This is accomplished by “filling” a “slot”. A slot is a placeholder defined inside a macro that may be populated with html by the calling template. For example, in this scenario the "master" macro defines a slot called “main”. The template code that defines the "main" slot in main_template.pt looks like this:
<metal:bodytext metal:define-slot="main" tal:content="nothing"> Page body text </metal:bodytext>
This time when I use the master macro, I’ll add a call that allows me to fill this "main" slot with text – in this case the text “Hello Metal!”.
… <div metal:fill-slot="main"> Hello Metal! </div> …
What this does is:
- Locates the area inside the master macro that defines the slot “main”
- Fills the slot by replacing it with the content we put in between our div tags – “Hello Metal!”
- Now that the slot has been filled with our content, the html returned by the master macro and then to the end user will contain our content.
Remember, you can only call fill-slot when you are in a block of template code that is using METAL’s use-macro tags. It wouldn’t make sense to try to fill a slot unless you were calling a macro which had defined a slot for you to fill. Also, the name of the slot you are trying to fill must of course match the name of a slot defined by the macro you are calling. For example if I tried to fill a slot named “main1” instead of “main”, I would not see the results I expected as slot “main1” was never defined.
Sometimes you may want to may want to define a macro that uses a macro in another template. There are a number of examples of this in the main_template.pt. For example, the “master” macro defined there uses the macro with the path expression “here/header/macros/html_header”:
<metal:page define-macro="master"> … <head metal:use-macro="here/header/macros/html_header">
The html_header macro defines a number of slots that may be filled by calling templates – for example “style_slot”:
From the page template header.pt:
<metal:block metal:define-slot="style_slot" tal:replace="nothing"> Inserts CSS specified from a page. </metal:block>
Sometimes you may want to make these types of slots available to the calling template by filling the slot and then defining the slot again:
<metal:styleslot fill-slot="style_slot"> <tal:comment replace="nothing"> A slot where you can insert CSS in the header from a template </tal:comment> <metal:styleslot define-slot="style_slot" /> </metal:styleslot>
Now we have made the slot named “styleslot” available to the users of the “master” macro which simply takes any value used to populate the slot and propogates it to the html_header macro’s styleslot.