1 | # Body content
|
2 |
|
3 | We'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 |
|
12 | This 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 |
|
23 | Custom 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 |
|
27 | When 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 |
|
37 | If 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 |
|
47 | The rendered output would be:
|
48 |
|
49 | _HTML Output:_
|
50 |
|
51 | ```html
|
52 | <div class="container fancy"><p>Content goes here...</p></div>
|
53 | ```
|
54 |
|
55 | This 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 |
|
62 | When 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 |
|
71 | These 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 |
|
84 | You 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 |
|
97 | Like 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 |
|
116 | It 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 |
|
131 | In 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 |
|
144 | We 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 |
|
162 | We 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 |
|
196 | If 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 |
|
211 | Now, 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 |
|
234 | We'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 |
|
257 | Continuing 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 |
|
282 | Now 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 |
|
307 | Our 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 |
|
330 | The 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 | ```
|