Simple HTML Generation https://minihtml.trendels.name/
at main 206 lines 6.1 kB view raw
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.