Skip to content

Complex Template Inheritance

Alice Zoë Bevan–McGregor edited this page Dec 6, 2015 · 3 revisions

Cinje so far has avoided adding an over-abundance of magical inheritance, capture, placeholder, and other processes common to other engines. When your templates are Python standard functions, there's no need for magic as you can just pass template generators around and consume them as desired.

An example of a "complex template" might be component-based approach towards page generation. Because emitting HTML pages is the typical use for a template engine like this some standardized HTML templates are provided built-in under the cinje.std namespace. To demonstrate use, we'll be dissecting the cinje.std.html:page template.

Basic HTML Page

To get started, let's define a basic HTML5 page template:

# encoding: cinje

: def page
<!DOCTYPE html>
<html>
	<head>
	</head>
	<body>
		: yield
	</body>
</html>
: end

While this will suffice, clearly there are a few things missing. We'll expand this as we go, but to quickly illustrate how you would now use this "wrapping template" (note the presence of a bare : yield statement) here is another template that makes use of the page template:

# encoding: cinje

: from cinje.std.html import page

: def my_page
	: using page
		<p>Hello.</p>

Running cinje.flatten(my_page()) would result in the following HTML:

<!DOCTYPE html>
<html>
	<head>
	</head>
	<body>
		<p>Hello.</p>
	</body>
</html>

Header and Footer

An HTML page generally contains references to external assets in the header, and usually JavaScript links are added to the bottom. Pages also have titles and other metadata (such as social sharing details), so our generic page wrapping template should support these. However, we're likely not going to cover every possible case, so we should provide some way to override the generation of these sections.

To accomplish this, let's define two new template functions to go with the page one, that accept some arguments for the data they'll emit:

: def default_header title, metadata=[], styles=[]
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1">

		: for name, value in metadata
		<meta&{name=name, content=value}>
		: end

		<title>${title}</title>

		: for href in styles
		<link&{href=href, rel="stylesheet"}>
		: end
: end

: def default_footer scripts=[]
		: for href in scripts
		<script&{src=href}></script>
		: end
: end

As a note, defining default lists like that has consequences, but it's okay in this instance because we only ever read those values.

Modular Page

Let's re-define the page wrapper template definition to allow passing values through.

: def page title, header=default_header, footer=default_footer, metadata=[], styles=[], scripts=[], **attributes

You can see that we're now passing in the placeholder templates, as well as lists of metadata, styles, scripts, and any other keyword argument the user of the template wishes. To update the page template to make use of this, change the <html> declaration to:

<html&{lang=attributes.pop('lang', 'en')}>

Within the <head> section, add:

	: if header
		: use header metadata=metadata, styles=styles
	: end

Update the <body> declaration thusly:

	<body&{attributes, role='document'}>

Then to use the footer, add the following after the : yield:

	: if footer
		: use footer scripts=scripts
	: end

Now the keyword values will be applied as HTML attributes on the <body>, excepting lang which is applied to the <html> tag, the default body role is "document", and if a header and footer are supplied (they are by default), then metadata, styles, and scripts will be generated as appropriate.

Overloading

Now you can easily use the page template, and easily override the header or footer generation. Interestingly, you can : use the default ones within your overridden ones if you only wish to add more data:

: from functools import partial
: from cinje.std.html import default_header, page

: def my_header title, metadata=[], styles=[]
	# Added before...

	: use default_header title, metadata, styles

	# Added after...
: end

: my_page = partial(page, header=my_header)

First class functions for life.