Simple HTML Generation https://minihtml.trendels.name/
1.. currentmodule:: minihtml
2
3.. _templates:
4
5Templates
6=========
7
8A template is a helper for producing a complete HTML page.
9
10Using templates
11---------------
12
13Create a template by using the :deco:`template` decorator on a function that
14returns HTML content:
15
16>>> from minihtml import template
17>>> from minihtml.tags import html, body
18>>>
19>>> @template()
20... def my_page():
21... return html(body("hello, world!"))
22...
23>>> p = my_page()
24>>> p.render()
25'<!doctype html>\n<html>\n <body>hello, world!</body>\n</html>\n'
26
27A template function returns a :class:`Template` object, which wraps the
28original function. Calling the object's :meth:`Template.render` method calls
29the function and returns the template rendered as a string with a ``doctype``
30declaration.
31
32.. doctest::
33 :options: +NORMALIZE_WHITESPACE
34
35 >>> print(p.render())
36 <!doctype html>
37 <html>
38 <body>hello, world!</body>
39 </html>
40
41The doctype can also be disabled:
42
43.. doctest::
44 :options: +NORMALIZE_WHITESPACE
45
46 >>> print(p.render(doctype=False))
47 <html>
48 <body>hello, world!</body>
49 </html>
50
51Layout components
52-----------------
53
54Often, you will want to use a "base" template that defines a common page
55structure for your project. This can be accomplished with a layout component.
56Let's start with a regular component that defines an HTML page, with slots to
57set the title and the page content:
58
59>>> from minihtml import text, component
60>>> from minihtml.tags import head, title, h1, p
61>>>
62>>> @component(slots=("title", "content"), default="content")
63... def base_layout(slots):
64... with html as elem:
65... with head, title:
66... slots.slot("title")
67... with body:
68... with h1:
69... slots.slot("title")
70... slots.slot("content")
71... return elem
72
73We could use this component inside our template like this:
74
75.. doctest::
76 :options: +NORMALIZE_WHITESPACE
77
78 >>> @template()
79 ... def hello():
80 ... with base_layout() as comp:
81 ... with comp.slot("title"):
82 ... text("hello, world!")
83 ... p("Welcome to my website")
84 ... return comp
85 >>>
86 >>> print(hello().render())
87 <!doctype html>
88 <html>
89 <head>
90 <title>hello, world!</title>
91 </head>
92 <body>
93 <h1>hello, world!</h1>
94 <p>Welcome to my website</p>
95 </body>
96 </html>
97
98We can make our lives a little bit easier by assigning the ``base_layout``
99component as the template's *layout component* using the ``layout`` parameter:
100
101.. doctest::
102 :options: +NORMALIZE_WHITESPACE
103
104 >>> @template(layout=base_layout)
105 ... def hello(layout):
106 ... with layout.slot("title"):
107 ... text("hello, world!")
108 ... p("Welcome to my website")
109 >>>
110 >>> print(hello().render())
111 <!doctype html>
112 <html>
113 <head>
114 <title>hello, world!</title>
115 </head>
116 <body>
117 <h1>hello, world!</h1>
118 <p>Welcome to my website</p>
119 </body>
120 </html>
121
122The result is the same, but the body of our template function is now shorter
123and we saved a level of indentation.
124
125Note that when using a layout component, the template will receive an instance
126of the component as it's first positional argument (we called it ``layout``
127above), and does not need to return anything. Instead, the template function is
128executed in a ``with base_layout()`` block and all elements it creates will be
129added to the layout component's default slot. For this reason, a layout
130component must have a default slot and should not expect any additional
131arguments.
132
133.. _collecting:
134
135Collecting component styles and scripts
136---------------------------------------
137
138Behind the scenes, a template will collect and deduplicate all
139:ref:`component_resources` that have been associated with the components used
140in the template, including the layout component.
141
142In order to inject the collected script and style nodes into the document, you
143use the :func:`component_scripts` and :func:`component_styles` placeholders. They
144can appear anywhere in the template, but typically you will put
145:func:`component_styles` into the ``<head>`` section of the document, and
146:func:`component_scripts` either also inside ``<head>`` or at the very end of
147the ``<body>`` section:
148
149.. doctest::
150 :options: +NORMALIZE_WHITESPACE
151
152 >>> from minihtml import component_scripts, component_styles
153 >>> from minihtml.tags import style, script, div, html, head, title, body, p
154 >>>
155 >>> @component(
156 ... style=style(".my-card { border: 1px solid blue; }"),
157 ... script=script("console.log('welcome!');"),
158 ... )
159 ... def my_card(slots):
160 ... """A component with attached style and script."""
161 ... with div["my-card"] as elem:
162 ... slots.slot()
163 ... return elem
164 ...
165 >>> @component(style=style("body { background: #eee; }"))
166 ... def my_layout(slots):
167 ... """A layout component with attached style."""
168 ... with html as elem:
169 ... with head:
170 ... title("Welcome to my website")
171 ... component_styles() # collected styles will be inserted here
172 ... with body:
173 ... slots.slot()
174 ... component_scripts() # collected scripts will be inserted here
175 ... return elem
176 ...
177 >>> @template(layout=my_layout)
178 ... def my_template(layout):
179 ... with my_card():
180 ... p("First card")
181 ... with my_card():
182 ... p("Second card")
183 ...
184 >>> t = my_template()
185 >>> print(t.render())
186 <!doctype html>
187 <html>
188 <head>
189 <title>Welcome to my website</title>
190 <style>body { background: #eee; }</style>
191 <style>.my-card { border: 1px solid blue; }</style>
192 </head>
193 <body>
194 <div class="my-card">
195 <p>First card</p>
196 </div>
197 <div class="my-card">
198 <p>Second card</p>
199 </div>
200 <script>console.log('welcome!');</script>
201 </body>
202 </html>
203
204As you can see, the script and style resources of the layout component and the
205two card components have been collected, deduplicated and inserted into the
206correct places.