The counterpart of the template is the rendering logic written in python. TDI expects a so-called “model” object here, which provides callback methods for the modifiable nodes.
from tdi import html
template = html.from_string("""
<html>
<body>
<h1 tdi="doctitle">doc title goes here</h1>
<p tdi="intro">Intro goes here.</p>
</body>
</html>
""")
class Model(object):
def render_doctitle(self, node):
node.content = u"Editing Content & Attributes"
def render_intro(self, node):
node.content = u"Modifying content and markup attributes is easy."
node['class'] = u"edit-intro"
model = Model()
template.render(model)
The model object can be anything you want, it is just expected to provide certain interfaces, if you want to modify nodes. It is not an error (by default) if a render_name method is missing. That way you can build up your logic step by step (or leave out methods intentionally).
Being so independent from the template source itself has interesting consequences:
Both of these items provide great flexibility and will influence the way how you reuse code – both on template and logic side.
Before we go on, here’s the output of the example above:
<html>
<body>
<h1>Editing Content & Attributes</h1>
<p class="edit-intro">Modifying content and markup attributes is easy.</p>
</body>
</html>
The content of an HTML element (or as presented in python: of a node object) is set by assigning it to the content attribute of the node. The content is defined as everything between the start tag and the end tag of the node’s element. It’s the same whether the content was simple text before or nested elements. It’s all wiped and replaced by the text you assign to it.
HTML attributes are accessed through the subscription operator, i.e. using square brackets. The render_intro method sets the class attribute that way.
Note the following mechanisms:
TDI escapes input automatically
It knows how to escape content properly for HTML and applies appropriate escaping by default. That way you are on the safe side except you explicitely instruct TDI otherwise (sometimes it’s inevitable to insert literal HTML code, for example). How to bypass the escape mechanism is described in the section further down below.
TDI is unicode aware [1]
That means if you pass unicode to content or attributes it will be encoded however the template is encoded (or rather what TDI knows about it). In the example above the template’s character encoding is not indicated anywhere, so TDI falls back to US-ASCII as the least common denominator.
It is also possible to pass byte strings to TDI (although not recommended). Those byte strings are still escaped [2] but not transcoded otherwise. Whether they do or do not fit into the template encoding is not checked and your responsibility only. You can learn what TDI knows about the template encoding from the template’s encoding attribute. Read the character encoding section for details.
In a sense shaping a template with TDI often works like etching. Often you have certain nodes available, pick one to render and throw away all the others.
Technically most of the time the content isn’t actually removed, but merely prevented from being written to the output stream. The difference is mostly not important, it’s just that preventing output is a lot faster than actually removing content (“removing” nodes for example is just a bit flip).
There are three to four ways to remove content:
from tdi import html
template = html.from_string("""
<html>
<body>
<h1 tdi="doctitle">doc title goes here</h1>
<ul class="menu">
<li><a href="menu1" tdi="menu1">some menu item</a></li>
<li><a href="menu2" tdi="menu2">Editing Content & Attributes</a></li>
<li><a href="menu3" tdi="menu3">Other menu item</a></li>
</ul>
<p tdi="intro" class="edit-intro">Intro goes here.</p>
<div class="list" tdi="list">
...
</div>
</body>
</html>
""")
class Model(object):
def __init__(self, possibilities, page):
self._possibilities = possibilities
self._page = page
def render_doctitle(self, node):
node.content = u"Editing Content & Attributes"
def render_menu1(self, node):
if self._page == 1:
node.hiddenelement = True
def render_menu2(self, node):
if self._page == 2:
node.hiddenelement = True
def render_menu3(self, node):
if self._page == 3:
node.hiddenelement = True
def render_intro(self, node):
if not self._possibilities:
del node['class']
node.content = u"There are no possibilities listed right now."
else:
node.content = u"Modifying content and markup attributes is easy."
def render_list(self, node):
if not self._possibilities:
node.remove()
return
# fill in possibilities here...
model = Model(possibilities=(), page=2)
template.render(model)
The example shows the possibilities (1), (2) and (3):
<html>
<body>
<h1>Editing Content & Attributes</h1>
<ul class="menu">
<li><a href="menu1">some menu item</a></li>
<li>Editing Content & Attributes</li>
<li><a href="menu3">Other menu item</a></li>
</ul>
<p>There are no possibilities listed right now.</p>
</body>
</html>
Warning
Be careful. Only bypass automatic escaping with content you trust.
There are certain use cases for pasting ready-to-use HTML into the output. Examples are (elsewhere) generated code, banner code etc. You need to circumvent TDI‘s automatic escaping mechanism in order to insert content literally.
from tdi import html
template = html.from_string("""
<div tdi="banner1"></div>
<div tdi="banner2"></div>
""")
class Model(object):
def render_banner1(self, node):
node.content = '<p>Banner!</p>'
def render_banner2(self, node):
node.raw.content = '<p>Banner!</p>'
template.render(Model())
The render_banner2 method passes the content in raw form to the output. The raw attribute is a small proxy object to the real node object and behaves as such (in a limited way), but assignments are just passed through. So you can assign content or attributes in raw form. That means:
render_banner1 assigns the same content to the regular node, and it’s properly escaped (just to show the difference):
<div><p>Banner!</p></div>
<div><p>Banner!</p></div>
[1] | If you don’t know about unicode, start here. |
[2] | Escaping byte strings assumes they’re ASCII compatible. For example, escaping UTF-16 encoded stuff that way will produce strange results. If you don’t know what UTF-16 is, don’t bother. Look up UTF-8 instead ;-). |