Templates¶
A template is a helper for producing a complete HTML page.
Using templates¶
Create a template by using the @template
decorator on a function that
returns HTML content:
>>> from minihtml import template
>>> from minihtml.tags import html, body
>>>
>>> @template()
... def my_page():
... return html(body("hello, world!"))
...
>>> p = my_page()
>>> p.render()
'<!doctype html>\n<html>\n <body>hello, world!</body>\n</html>\n'
A template function returns a Template
object, which wraps the
original function. Calling the object’s Template.render()
method calls
the function and returns the template rendered as a string with a doctype
declaration.
>>> print(p.render())
<!doctype html>
<html>
<body>hello, world!</body>
</html>
The doctype can also be disabled:
>>> print(p.render(doctype=False))
<html>
<body>hello, world!</body>
</html>
Layout components¶
Often, you will want to use a “base” template that defines a common page structure for your project. This can be accomplished with a layout component. Let’s start with a regular component that defines an HTML page, with slots to set the title and the page content:
>>> from minihtml import text, component
>>> from minihtml.tags import head, title, h1, p
>>>
>>> @component(slots=("title", "content"), default="content")
... def base_layout(slots):
... with html as elem:
... with head, title:
... slots.slot("title")
... with body:
... with h1:
... slots.slot("title")
... slots.slot("content")
... return elem
We could use this component inside our template like this:
>>> @template()
... def hello():
... with base_layout() as comp:
... with comp.slot("title"):
... text("hello, world!")
... p("Welcome to my website")
... return comp
>>>
>>> print(hello().render())
<!doctype html>
<html>
<head>
<title>hello, world!</title>
</head>
<body>
<h1>hello, world!</h1>
<p>Welcome to my website</p>
</body>
</html>
We can make our lives a little bit easier by assigning the base_layout
component as the template’s layout component using the layout
parameter:
>>> @template(layout=base_layout)
... def hello(layout):
... with layout.slot("title"):
... text("hello, world!")
... p("Welcome to my website")
>>>
>>> print(hello().render())
<!doctype html>
<html>
<head>
<title>hello, world!</title>
</head>
<body>
<h1>hello, world!</h1>
<p>Welcome to my website</p>
</body>
</html>
The result is the same, but the body of our template function is now shorter and we saved a level of indentation.
Note that when using a layout component, the template will receive an instance
of the component as it’s first positional argument (we called it layout
above), and does not need to return anything. Instead, the template function is
executed in a with base_layout()
block and all elements it creates will be
added to the layout component’s default slot. For this reason, a layout
component must have a default slot and should not expect any additional
arguments.
Collecting component styles and scripts¶
Behind the scenes, a template will collect and deduplicate all Component styles and scripts that have been associated with the components used in the template, including the layout component.
In order to inject the collected script and style nodes into the document, you
use the component_scripts()
and component_styles()
placeholders. They
can appear anywhere in the template, but typically you will put
component_styles()
into the <head>
section of the document, and
component_scripts()
either also inside <head>
or at the very end of
the <body>
section:
>>> from minihtml import component_scripts, component_styles
>>> from minihtml.tags import style, script, div, html, head, title, body, p
>>>
>>> @component(
... style=style(".my-card { border: 1px solid blue; }"),
... script=script("console.log('welcome!');"),
... )
... def my_card(slots):
... """A component with attached style and script."""
... with div["my-card"] as elem:
... slots.slot()
... return elem
...
>>> @component(style=style("body { background: #eee; }"))
... def my_layout(slots):
... """A layout component with attached style."""
... with html as elem:
... with head:
... title("Welcome to my website")
... component_styles() # collected styles will be inserted here
... with body:
... slots.slot()
... component_scripts() # collected scripts will be inserted here
... return elem
...
>>> @template(layout=my_layout)
... def my_template(layout):
... with my_card():
... p("First card")
... with my_card():
... p("Second card")
...
>>> t = my_template()
>>> print(t.render())
<!doctype html>
<html>
<head>
<title>Welcome to my website</title>
<style>body { background: #eee; }</style>
<style>.my-card { border: 1px solid blue; }</style>
</head>
<body>
<div class="my-card">
<p>First card</p>
</div>
<div class="my-card">
<p>Second card</p>
</div>
<script>console.log('welcome!');</script>
</body>
</html>
As you can see, the script and style resources of the layout component and the two card components have been collected, deduplicated and inserted into the correct places.