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