Using Macros¶
Macros are a simple way to create re-usable snippets of templates. They can be used as-is, or they can be lightly customized at the point of use by filling slots.
This document will cover the ways in which macros can be used in Nikola themes created with this package; it will generally assume that you already understand the basics of creating and using macros on Chameleon. See the macro reference documentation for more details.
One important thing to point out: the value of a use-macro
attribute is a true expression. It is evaluated to find the macro
object. It’s not just a simple name. This allows most of the scenarios
we will discuss below.
Using Macros From the Same Template¶
While not particularly common, it is possible to create and use macros
all within the scope of a single template. One of the standard names
defined for path expressions is template
referring to the
currently executing template. A Chameleon template has a macros
dictionary that lists all the macros defined within by name.
Therefore, to use a macro from the same template, we would write an
expression like template/macros/macro_name
(here, in a file called
hello.tmpl.pt
):
<html>
<p metal:define-macro="hello">
Hello <p metal:define-slot="name">world</p>
</p>
<body>
<p tal:repeat="post context/posts"
metal:use-macro="template/macros/hello">
<p metal:fill-slot="name">${post/author}</p>
</p>
</body>
</html>
Using Macros From Other Templates¶
When a template defines macros, they can be used from other templates
as well. The key is that use-macro
takes an expression; we just
have to provide an expression that evaluates to a macro.
Load Expressions¶
The simplest (but least flexible) way to do this is with Chameleon’s
built-in load:
expression. This will load a template in from a file
relative to the current file. If the hello
macro from the previous
section was defined in hello.tmpl.pt
, the file index.tmpl.pt
could use
it like this:
<html>
<body tal:define="hello-template load:hello.tmpl.pt">
<p tal:repeat="post context/posts"
metal:use-macro="hello-template/macros/hello">
<p metal:fill-slot="name">${post/author}</p>
</p>
</body>
</html>
Flexibility Through the Component Architecture¶
Far more flexible is to make use of the component architecture. We can do this in a few ways. One is to rely on the fact
that views are automatically registered for each .tmpl.pt found in the theme’s templates
directory. Each such view will have an index
attribute that is a
template, and which in turn will have the macros
dictionary. We
could change index.tmpl.pt
to use this view:
<html>
<body>
<p tal:repeat="post context/posts"
metal:use-macro="context/@@hello.tmpl/index/macros/hello">
<p metal:fill-slot="name">${post/author}</p>
</p>
</body>
</html>
How is this more flexible? Isn’t that just another way to refer to a file?
This is more flexible for two reasons. The first is that a theme that
extends us (see Theme Inheritance) could provide its own
hello.tmpl
view (maybe defining different CSS classes or using
<div>
instead of <p>
) and we would automatically use it. The
second (and similar) reason is that the traversal through context
gives the component architecture a chance to define a more specific
view for that particular type of context. (Of
course we could traverse through any object and potentially locate a
more specific view for that object.) This lets us (or themes that
extend us) do something like use a generic macro for most types of
objects, but also provide a custom macro that automatically gets used
for galleries or slides. All without having to change the base
template!
macro:
Expressions¶
An even more powerful and flexible way to locate macros is with the
z3c.macro macro:
expression type. Unlike all of the above approaches, this abstracts
the notion of a macro away from its location (we don’t have to think
about what file or view the macro is defined in). Instead, we just
use the macro’s name: <p metal:use-macro="macro:hello" />
.
At first this may seem less flexible: at least with traversal we got to choose a variable to traverse through and could register macros for particular context objects. But the macro expression actually automatically searches for the macro based on all three of the context, request, and view. That gives us the same three degrees of freedom to define custom macros for particular types of context objects, pages and comment systems that we have for templates.
All macros found in *.macro.pt
files are automatically
registered for the lest specific, most generic
interfaces possible. You can easily add your own macros in ZCML for more specific registrations.
Finding Macros For a Different Context¶
The macro:
expression always looks up its target based on the
current context, request and view. Sometimes, particularly when
working with the contents of a container—such as the posts in an
index—you want to look up a macro with a different context (the
object in the container). The @@macros view lets
you do this:
<html>
<body>
<p tal:repeat="post context/posts">
<p metal:use-macro="post/@@macros/display">Display a post.</p>
</p>
</body>
</html>
The template
Variable and Macros¶
One useful quirk involves the template
variable: When you use a
macro from another file, no matter how you got it, whether from a load
expression or the component architecture, while that macro is
executing, template
still refers to the file that called it! This
is yet another way of overriding bits and pieces of macros if macros
are looked up from the template
variable; of course it does
introduce fairly tight coupling between the files.
Suppose we have page.tmpl.pt
for ordinary (non blog-post) pages:
<html>
<header metal:define-macro="header">
<h1>${context/title}</h1>
<div class="metadata"
metal:use-macro="template/macros/metadata">
Put the metadata here
</div>
</header>
<div metal:define-macro="metadata">
<!--! Pages don't have any metadata -->
</div>
<body>
<article>
<header metal:use-macro="template/macros/header">
</header>
...
</article>
</body>
</html>
Now, post.tmpl.pt
could use the header
macro from
page.tmpl.pt
and still fill in its own metadata
macro:
<html>
<div metal:define-macro="metadata">
<h1>Author: ${context/author}</h1>
</div>
<body>
<article>
<header metal:use-macro="context/@@page.tmpl/index/macros/header">
</header>
...
</article>
</body>
</html>
This is similar to filling slots, but works with any level of nesting.
Macro Limitations and Challenges¶
Macros always expand to exactly one piece of content (content for a macro cannot come from multiple places).
Slots can be difficult to use effectively through multiple levels of nesting.
The macro namespace is flat.
Some of these challenges are addressed with viewlets.