Simple HTML Generation https://minihtml.trendels.name/
1.. currentmodule:: minihtml
2
3.. _components:
4
5Components
6==========
7
8A component is a re-usable snippet of HTML.
9
10Defining components
11-------------------
12
13You create components with the :deco:`component` decorator on a function.
14
15The function always receives a :class:`Slots` objects as its first positional
16argument, but can have other arguments as well.
17
18Here is a simple component that returns a div with a configurable "name" attribute:
19
20>>> from minihtml import component
21>>> from minihtml.tags import div
22>>>
23>>> @component()
24... def my_component(slots, name):
25... return div["my-component"](name=name)
26
27You create an instance of the component by calling it. Like elements, a
28component called inside an element context will add content to the parent
29element:
30
31>>> comp = my_component("my-name")
32>>> print(comp)
33<div class="my-component" name="my-name"></div>
34
35>>> with div["container"] as elem:
36... my_component("my-name")
37<...>
38>>> print(elem)
39<div class="container">
40 <div class="my-component" name="my-name"></div>
41</div>
42
43Component slots
44---------------
45
46In addition to arguments, components can have *slots* that can be filled by the
47caller with arbitrary content. So, instead of passing elements as arguments to
48a component, you would use a slot. If you do not configure slots explicitly,
49your component has a *default slot*.
50
51To refer to the contents of the slot within the component definition, use the
52:meth:`Slots.slot` method inside an element context. Here is a component that
53wraps it's content in a ``div`` with ``class="greeting"``:
54
55>>> @component()
56... def my_greeting(slots):
57... with div["greeting"] as elem:
58... slots.slot()
59... return elem
60
61To fill the slot of a component, use it as a context manager. Elements created
62within the context are added to the default slot.
63
64>>> from minihtml.tags import p
65>>> with my_greeting() as comp:
66... p("Hello, world!")
67... p("More content.")
68<...>
69>>> print(comp)
70<div class="greeting">
71 <p>Hello, world!</p>
72 <p>More content.</p>
73</div>
74
75Named slots
76-----------
77
78A component can also be declared with one or more *named slots*:
79
80>>> @component(slots=("header", "footer"))
81... def two_slots(slots):
82... with div["two-slots"] as elem:
83... with div["header"]:
84... slots.slot("header")
85... with div["footer"]:
86... slots.slot("footer")
87... return elem
88
89To interact with a named slot, pass the slot name to :meth:`Slots.slot`, as
90shown above. To fill a slot by name, use the :meth:`Component.slot`
91method of the component returned by the context manager:
92
93>>> with two_slots() as comp:
94... with comp.slot("header"):
95... p("header content")
96... with comp.slot("footer"):
97... p("footer content")
98<...>
99>>> print(comp)
100<div class="two-slots">
101 <div class="header">
102 <p>header content</p>
103 </div>
104 <div class="footer">
105 <p>footer content</p>
106 </div>
107</div>
108
109Specifying a default slot
110-------------------------
111
112If your component has multiple slots, it's a good idea to define one to be the
113*default slot*:
114
115>>> from minihtml.tags import h4
116>>>
117>>> @component(slots=("title", "content"), default="content")
118... def my_card(slots):
119... with div["my-card"] as elem:
120... with h4:
121... slots.slot("title")
122... with div["content"]:
123... slots.slot("content")
124... return elem
125
126The default slot can always be referred to (both inside and outside of the
127component definition) via it's name (as shown above), or as the unnamed default
128slot:
129
130>>> from minihtml import text
131>>> with my_card() as comp:
132... with comp.slot("title"):
133... text("card title")
134... p("card content") # this goes into the default slot ("content")
135<...>
136>>> print(comp)
137<div class="my-card">
138 <h4>card title</h4>
139 <div class="content">
140 <p>card content</p>
141 </div>
142</div>
143
144Checking if a slot is filled
145----------------------------
146
147Inside a component, you can find out whether or not a slot has been filled
148using :meth:`Slots.is_filled`:
149
150>>> from minihtml.tags import span, img
151>>>
152>>> @component(slots=("icon", "message"), default="message")
153... def my_message(slots):
154... with div["my-message"] as elem:
155... if slots.is_filled("icon"):
156... with span["icon"]:
157... slots.slot("icon")
158... slots.slot("message")
159... return elem
160
161As a result, the ``span`` element will only be present if the ``image`` slot
162has been filled:
163
164>>> with my_message() as comp:
165... p("the message")
166<...>
167>>> print(comp)
168<div class="my-message">
169 <p>the message</p>
170</div>
171
172>>> with my_message() as comp:
173... with comp.slot("icon"):
174... img(src="warning.png", alt="warning icon")
175... p("the message")
176<...>
177>>> print(comp)
178<div class="my-message">
179 <span class="icon"><img src="warning.png" alt="warning icon"></span>
180 <p>the message</p>
181</div>
182
183Default content for slots
184-------------------------
185
186You can provide default content that will be inserted if a slot has not been
187filled. To do hat, use :meth:`Slots.slot` as a context manager. Elements
188created within the context will be used as the default content if the slot was
189not filled. If the slot *was* filled, the elements will be ignored and the slot
190content inserted in their place.
191
192>>> @component(slots=("title", "icon"), default="title")
193... def my_warning(slots):
194... with div["my-warning"] as elem:
195... with slots.slot("icon"):
196... img(src="warning.png", alt="warning icon")
197... with slots.slot("title"):
198... h4("Warning")
199... return elem
200
201>>> comp = my_warning()
202>>> print(comp)
203<div class="my-warning">
204 <img src="warning.png" alt="warning icon">
205 <h4>Warning</h4>
206</div>
207
208>>> with my_warning() as comp:
209... with comp.slot("icon"):
210... img(src="error.png", alt="error icon")
211... h4("Error")
212<...>
213>>> print(comp)
214<div class="my-warning">
215 <img src="error.png" alt="error icon">
216 <h4>Error</h4>
217</div>
218
219.. _component_resources:
220
221Component styles and scripts
222----------------------------
223
224A component can define style and/or script resources that should be included in
225the page where a component is used. This only has an effect when the component
226is used inside a :ref:`template <templates>`.
227
228To associate a style with a component, pass an element or list of elements to
229the ``style`` parameter of the :deco:`component` decorator. Typically, this
230will be one or more ``style`` or ``link`` elements. For scripts, use the
231``script`` parameter.
232
233>>> from minihtml.tags import link, script, style
234>>>
235>>> @component(
236... style=[
237... style(".my-component { background #ccc }"),
238... link(rel="stylesheet", href="/path/to/stylesheet.css"),
239... ],
240... script=script("alert('hello, world!);")
241... )
242... def my_component(slots):
243... return div["my-component"]
244
245See :ref:`collecting` for details on how to use component styles and scripts in
246a template.