UNPKG

8.83 kBMarkdownView Raw
1# Body content
2
3We're used to passing body content to HTML tags. When you do this, the tag has control over where and when this content is rendered. A good example of this is the [HTML `<details>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details):
4
5```html
6<details>
7 <summary>Hello <strong>World</strong></summary> This is some
8 <em>content</em> that can be toggled.
9</details>
10```
11
12This is what it renders (try clicking it):
13
14---
15
16<details>
17 <summary>Hello <strong>World</strong></summary>
18 This is some <em>content</em> that can be toggled.
19</details>
20
21---
22
23Custom tags can also receive content in the same way. This allows a component to give its user full control over _how_ some section of the content is rendered, but control _where_, _when_, and with _what_ data it is rendered. This feature is necessary to build composable components like overlays, layouts, dropdowns, etc. Imagine a `<table>` that didn't give you control over how its cells were rendered. That would be pretty limited!
24
25## Rendering body content
26
27When a custom tag is passed body content, it is received as a special `renderBody` property on the component's `input`. You can include this content anywhere in your component by using the [`<${dynamic}>` syntax](./syntax.md#dynamic-tagname).
28
29_components/fancy-container.marko:_
30
31```marko
32<div class="container fancy">
33 <${input.renderBody}/>
34</div>
35```
36
37If we were to use this tag like this:
38
39_Marko Source:_
40
41```marko
42<fancy-container>
43 <p>Content goes here...</p>
44</fancy-container>
45```
46
47The rendered output would be:
48
49_HTML Output:_
50
51```html
52<div class="container fancy"><p>Content goes here...</p></div>
53```
54
55This is a pretty basic example, but you can imagine how this could be incorporated into a more advanced component to render passed content where/when needed.
56
57> **ProTip:**
58> Body content can be rendered multiple times. Or not at all.
59
60## Passing attributes to body content
61
62When rendering body content with `<${dynamic}>`, attributes may also be passed:
63
64_components/random-value.marko:_
65
66```marko
67<!-- heh, it's not actually random -->
68<${input.renderBody} number=1337 />
69```
70
71These attribute values can be received as a [tag parameter](./syntax.md#parameters):
72
73```marko
74<random-value|{ number }|>
75 The number is ${number}
76</random-value>
77```
78
79> **ProTip:**
80> Some tags (like the above tag) may not render anything except their body content with some data. This can be quite useful, just look at the `<for>` and `<await>` tags!
81
82## Named body content
83
84You can also pass named content sections to a tag using [attribute tags](./syntax.md#attribute-tag) which are denoted by the `@` prefix.
85
86```marko
87<layout>
88 <@heading>
89 <h1>Hello Marko</h1>
90 </@heading>
91 <@content>
92 <p>...</p>
93 </@content>
94</layout>
95```
96
97Like attributes, these attribute tags are received as `input.heading` and `input.content`, but they each have a `renderBody` property which we can now use:
98
99_components/layout.marko_
100
101```marko
102<!doctype html>
103<html>
104 <body>
105 <${input.heading.renderBody}/>
106 <hr/>
107 <${input.content.renderBody}/>
108 </body>
109</html>
110```
111
112> **ProTip:** The `renderBody` property can be omitted. You could use `<${input.heading}/>`, for example.
113
114### Repeated attribute tags
115
116It is sometimes useful to allow multiple of the same attribute tag to be passed. This would allow us to, for example, build a custom table component which would allow its user to specify any number of columns, while still giving ther user control over how each column is rendered:
117
118_Marko Source:_
119
120```marko
121<fancy-table data=people>
122 <@column|person|>
123 Name: ${person.name}
124 </@column>
125 <@column|person|>
126 Age: ${person.age}
127 </@column>
128</fancy-table>
129```
130
131In order to receive multiple of the same attribute tag, you need to specify that the attribute tag can be repeated in a [`marko-tag.json`](./marko-json.md#single-component-definition) file.
132
133_components/fancy-table/marko-tag.json:_
134
135```js
136{
137 "@data": "array",
138 "<column>": {
139 "is-repeated": true
140 }
141}
142```
143
144We can then use the `<for>` tag to render the body content into table, passing the row data to each column's body.
145
146_components/fancy-table/index.marko:_
147
148```marko{4-8}
149<table class="fancy">
150 <for|row| of=input.data>
151 <tr>
152 <for|column| of=input.column>
153 <td>
154 <${column.renderBody} ...row/>
155 </td>
156 </for>
157 </tr>
158 </for>
159</table>
160```
161
162We now have a working `<fancy-table>`. Let's see what it renders:
163
164_Example Data:_
165
166```js
167[
168 {
169 name: "Patrick",
170 age: 63
171 },
172 {
173 name: "Austin",
174 age: 12
175 }
176];
177```
178
179_HTML Output:_
180
181```html
182<table class="fancy">
183 <tr>
184 <td>Name: Patrick</td>
185 <td>Age: 63</td>
186 </tr>
187 <tr>
188 <td>Name: Austin</td>
189 <td>Age: 12</td>
190 </tr>
191</table>
192```
193
194### Attributes on attribute tags
195
196If you look at our previous example, we had to prefix each cell with the column label. It would be better if we could give a name to each column instead and only render that once.
197
198_Marko Source:_
199
200```marko
201<fancy-table>
202 <@column|person| heading="Name">
203 ${person.name}
204 </@column>
205 <@column|person| heading="Age">
206 ${person.age}
207 </@column>
208</fancy-table>
209```
210
211Now, each object in the `input.column` array will contain a `heading` property in addition to its `renderBody`. We can use another `<for>` and render the headings in `<th>` tags:
212
213_components/fancy-table/index.marko:_
214
215```marko{3-5}
216<table class="fancy">
217 <tr>
218 <for|column| of=input.column>
219 <th>${column.heading}</th>
220 </for>
221 </tr>
222 <for|row| of=input.data>
223 <tr>
224 <for|column| of=input.column>
225 <td>
226 <${column.renderBody} ...row/>
227 </td>
228 </for>
229 </tr>
230 </for>
231</table>
232```
233
234We'll now get a row of headings when we render our `<fancy-table>`
235
236_HTML Output:_
237
238```html
239<table class="fancy">
240 <tr>
241 <th>Name</th>
242 <th>Age</th>
243 </tr>
244 <tr>
245 <td>Patrick</td>
246 <td>63</td>
247 </tr>
248 <tr>
249 <td>Austin</td>
250 <td>12</td>
251 </tr>
252</table>
253```
254
255### Nested attribute tags
256
257Continuing to build on our example, what if we want to add some custom content or even components into the column headings? In this case, we can extend our `<fancy-table>` to use nested attribute tags. We'll now have `<@heading>` and `<@cell>` tags nested under `<@column>`. This gives users of our tag full control over how to render both column headings and the cells within the column!
258
259_Marko Source:_
260
261```marko{3-8}
262<fancy-table>
263 <@column>
264 <@heading>
265 <app-icon type="profile"/> Name
266 </@heading>
267 <@cell|person|>
268 ${person.name}
269 </@cell>
270 </@column>
271 <@column>
272 <@heading>
273 <app-icon type="calendar"/> Age
274 </@heading>
275 <@cell|person|>
276 ${person.age}
277 </@cell>
278 </@column>
279</fancy-table>
280```
281
282Now instead of rendering the heading as text, we'll render the heading's body content.
283
284_components/fancy-table/index.marko:_
285
286```marko{5}
287<table class="fancy">
288 <tr>
289 <for|column| of=input.column>
290 <th>
291 <${column.heading.renderBody}/>
292 </th>
293 </for>
294 </tr>
295 <for|row| of=input.data>
296 <tr>
297 <for|column| of=input.column>
298 <td>
299 <${column.cell.renderBody} ...row/>
300 </td>
301 </for>
302 </tr>
303 </for>
304</table>
305```
306
307Our headings can now include icons (and anything else)!
308
309_HTML Output:_
310
311```html
312<table class="fancy">
313 <tr>
314 <th><img class="icon" src="profile.svg" /> Name</th>
315 <th><img class="icon" src="calendar.svg" /> Age</th>
316 </tr>
317 <tr>
318 <td>Patrick</td>
319 <td>63</td>
320 </tr>
321 <tr>
322 <td>Austin</td>
323 <td>12</td>
324 </tr>
325</table>
326```
327
328### Dynamic attribute tags
329
330The flexibility of the `<fancy-table>` is great if you want to render columns differently or have columns that display the data in a special way (such as displaying an age derived from a date of birth). However, if all columns are basically the same, the user might feel they're repeating themselves. As you might expect, you can use `<for>` (and `<if>`) to dynamically render attribute tags.
331
332```marko
333$ const columns = [{
334 property: "name",
335 title: "Name",
336 icon: "profile"
337}, {
338 property: "age",
339 title: "Age",
340 icon: "calendar"
341}]
342
343<fancy-table>
344 <for|{ property, title, icon }|>
345 <@column>
346 <@heading>
347 <app-icon type=icon/> ${title}
348 </@heading>
349 <@cell|person|>
350 ${person[property]}
351 </@cell>
352 </@column>
353 </for>
354</fancy-table>
355```