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:

  1. Name the nodes you want to replace with a tdi:overlay attribute. These are the overlay targets.
  2. Name the replacement nodes (usually in a different template) as well. These are the overlay sources.
  3. 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:

The basic overlay mechanism

Basic Overlay Mechanism

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:

  1. The content template, which contains just a small “placeholder” for the menu to mark where it should go.
  2. The menu widget template, which contains the whole menu template
  3. 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 &lt; and &gt; 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 &amp; 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