I had a template with a handful of tables that basically just displayed the same tabular data, just different data sets depending on the table. In case your curious, my data is segregated by workflow state.
I’ve had this problem before but in the past I had a skin layer registered too (thanks to ArchGenXML).
Defining macros is pretty easy, the Zope Book does a good job of explaining how to define them. It’s actually easier than it looks. If you don’t have a need for slots, it basically just comes down to slapping a metal:define-macro attribute into any arbitrary block of code.
Here’s a quick example:
<html>
<body>
<p metal:define-macro="title">
<span tal:replace="view/context/Title" />
</p>
</body>
</html>
I’ve left out most of the usual ZPT window-dressings (like a i18n domain declaration), but I’ll assume you already know how to do that :).
This is also of course, a pretty stupid example. But it does illustrate an important concept in METAL: when your macro is being expanded, it’s as if you wrote the code right where the metal:use-macro attribute is used. So any variables that are available to the template that’s calling the macro are available here.
You have to be careful of this though, because it does limit how much your macro can be re-used.
In the past, because of the skin layer, I was able to drop a ZPT file into my skins directory, and call them like this (assume I put the last macro into a file called “macros.pt” in the skins directory along side this template):
<html>
<body>
<p metal:use-macro="here/macros/macros/title" />
</body>
</html>
I saw a lot of examples where the name of the file itself was used (e.g. here/macros.pt/macros/title), but I don’t recall doing it that way.
As far as I can tell, this doesn’t work with templates on the filesystem that aren’t registered in a skin layer. So when you aren’t using a skins folder, because you’ve had the forethought to put that sort of stuff in a policy or theme product, this sort of macro expansion doesn’t seem to work.
I found a solution to this by accident. I don’t have my Zope 3 book (the essential Web Component Development with Zope 3) with me today, and was poking around Google aimlessly to solve this problem. I stumbled upon a page from a hereto unidentified Zope 3 course that showed me what I needed to do.
Zope 3 registers page templates, much like Zope 2/Plone does with the skins concept. But in Zope 3 you do it with ZCML as opposed to the CMF/GenericSetup glue that you need to register a Plone skin layer.
So in a handy configure.zcml (I put mine in the browser directory of my product), you first need to register the template containing your macros as a browser page:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
...
<browser:page
for="*"
name="my.product.macros"
template="macros.pt"
permission="zope.Public"
/>
</configure>
Note that I since my macro file (macros.pt) is in the same directory as my configure.zcml, I didn’t have to specify any sort of relative path information in the template attribute.
Since the macro template is being registered like any other browser view, you can specify a particular interface that is only allowed to use it, using the for attribute. Likewise, you can set a specific permission that will restrict access to the macros using the permission attribute. I haven’t tested either attribute with a browser page that’s being used for macros, so I don’t know what the expected behavior is.
OK, so with the ZCML tag above added to configure.zcml and a quick restart of the server (I had trouble with plone.reload in this case, FYI), any macros in macros.pt are available, to any view on any content type that feels inclined to use them.
To access the macros within a browser view/page (or viewlet, etc), the syntax is similar to what it was in the “skins layer” scenario mentioned earlier, but it’s very Zope 3:
<html>
<body>
<p metal:use-macro="view/context/@@my.product.macros/title" />
</body>
</html>
You access the macro by getting the browser page defined in the ZCML above applied to the current context. That’s it.
So to summarize: you can use a browser page like a sort of template/code snippet resource. And as a bonus, said resource can contain METAL macros. METAL macros are a nice way to clean up your code, enforce DRY, and reduce bugs.
Side Note:
I’ve also come to realize that the same @@viewname syntax works to apply a view to an object inside of a page template. This is a natural predecessor to the macro stuff I was just talking about, but I didn’t realize it was possible until now. Whoo-hoo!
6 Comments
March 3, 2009 at 10:00 am
This is a nice, succinct article. You explain things well.
This is just showing my bias towards doing things in Python code versus configuration, but another way you could have done it is load the template with your macros directly in your view:
class MyView(BrowserView):
…
blah blah
…
@property
@memoize
def macros(self):
return ViewPageTemplateFile(“macros.pt”)
Then in your page template you just call:
This particular example is untested, so mileage may vary.
March 3, 2009 at 10:17 am
Good point! The negative is that you can’t use the macros in another view though… or you’d have to instantiate the view with the macros property to do so…
looks like WP chopped off your template code…
Is this (more or less) what you wanted to put after “you just call:”:
<span metal:use-macro=”view/macros/blah” />
Nobody’s ever called me succinct before… thanks!!
March 3, 2009 at 11:16 am
Yes, there are advantages to actually registering it. In my case, if I needed it in more than one view I’d probably just replicate the macros method. Or I’d do it your way. I just thought it was neat the different ways to approach the same problem.
And yes, that template code is pretty close to what I typed in.
March 3, 2009 at 5:27 pm
Do you hear that tapping sound?
It’s those hundreds of Plone newbies patting your back :)
Thanks!
March 5, 2009 at 10:35 am
Josh, nice post. Mikko, thank you thank you THANK YOU for all of your excellent tutorials on plone.org.
June 19, 2009 at 8:39 am
Thanks for this – someone needs to update the METAL docs to reflect how METAL interacts with Zope3. I could not figure it out until I saw your page.