Template Combination With Overlays¶
Usually there’s more than one template in a project and there are certainly the same or similar elements on the different pages. In order to avoid code duplication, move the common elements into an extra file and load them where they are needed. TDI provides the concept of overlays for this purpose:
- Name the nodes you want to replace with a
tdi:overlay
attribute. These are the overlay targets. - Name the replacement nodes (usually in a different template) as well. These are the overlay sources.
- The python code now decides which template is which and overlays them. The result is a new template (which can be overlayed again...)
Especially the last item gives more flexibility than common include or inheritance mechanisms, because the final templates are explicitly picked at runtime (and not at template definition time). This allows for theming, localization, widgets and probably more use cases all at once.
As this may still sound complicated, here’s a picture:
Note that the figure is simplified:
- It’s possible to overlay more than one block at the same time
- If the same target block occurs more than once, it will be replaced more than once as well. (If the same source block occurs more than once, you’ll get an error.)
Here’s some example code:
from tdi import html
content = html.from_string("""
<html>
<body>
<h1 tdi="doctitle">doc title goes here</h1>
<ul tdi:overlay="menu"></ul>
<p tdi="intro" class="edit-intro">Intro goes here.</p>
<div class="list" tdi="list">
...
</div>
</body>
</html>
""")
menu_widget = html.from_string("""
<html>
<body>
<ul tdi:overlay="menu">
<li tdi="menu"><a tdi="link">some menu item</a></li><li tdi=":-menu">
</li>
</ul>
</body>
</html>
""")
class Model(object):
def __init__(self, menu, page):
self._menu = menu
self._page = page
def render_menu(self, node):
node.repeat(self.repeat_menu, self._menu)
def repeat_menu(self, node, (href, menuitem)):
node.link.content = menuitem
if (node.ctx[0] + 1 == self._page):
node.link.hiddenelement = True
else:
node.link['href'] = href
menu = [
(u'/some/', u'Some Menu Item'),
(u'/other/', u'Editing Content & Attributes'),
(u'/third.html', u'Another Menu Item'),
]
model = Model(menu=menu, page=2)
template = content.overlay(menu_widget)
template.render(model)
The code deals with three different template objects:
- The content template, which contains just a small “placeholder” for the menu to mark where it should go.
- The menu widget template, which contains the whole menu template
- The finally rendered template (the result of
content.overlay(menu_widget)
)
If it’s clear whether a tdi:overlay
node is an overlay source or a
target, it’s recommended to flag them as such:
- target-only overlays can be flagged with the
>
character (the content flows into the overlay node) (example:tdi:overlay=">menu"
) - source-only overlays can be flagged with the
<
character (the content flows away from the overlay node) (example:tdi:overlay="<menu"
)
Flagging properly is generally recommended, because the overlay result
is likely to be part of a bigger overlay chain (like
t1.overlay(t2).overlay(t3)
...). If you flag the nodes, there are no
ambiguities about the purpose of the particular node.
Note
If your editor doesn’t like the <
and >
characters inside an
HTML attribute, you can write them as <
and >
as well.
The example code emits the following:
<html>
<body>
<h1>doc title goes here</h1>
<ul>
<li><a href="/some/">Some Menu Item</a></li>
<li>Editing Content & Attributes</li>
<li><a href="/third.html">Another Menu Item</a></li>
</ul>
<p class="edit-intro">Intro goes here.</p>
<div class="list">
...
</div>
</body>
</html>
Overlay Overrides¶
By default a source overlay replaces the target node completely. But
there are exceptions: If the target overlay specifies tdi
or
tdi:scope
attributes, these are kept and override the ones specified
in the source overlay.
While this probably doesn’t make much sense for navigational elements like the menu widget above, it has been proven useful for widgets included more than once in a page, because it allows to put the overlay into a different context.
The following (meaningless) example shows the principle:
from tdi import html
content = html.from_string("""
<html>
<body>
<h1 tdi="doctitle">doc title goes here</h1>
<ul tdi:overlay="menu" tdi="mymenu"></ul>
<p tdi="intro" class="edit-intro">Intro goes here.</p>
<div class="list" tdi="list">
...
</div>
</body>
</html>
""")
menu_widget = html.from_string("""
<html>
<body>
<ul tdi:overlay="menu" tdi="menucontainer">
<li tdi="menu"><a tdi="link">some menu item</a></li><li tdi=":-menu">
</li>
</ul>
</body>
</html>
""")
class Model(object):
def render_menucontainer(self, node):
print "menucontainer"
def render_mymenu(self, node):
print "mymenu"
model = Model()
template = content.overlay(menu_widget)
template.render_string(model)
And that’s the output:
mymenu