1 | # Syntax
|
2 |
|
3 | Marko is HTML _re-imagined_ as a language for building dynamic and reactive user interfaces.
|
4 | Just about any valid HTML is valid Marko, but Marko extends the HTML language to allow building modern applications in a declarative way.
|
5 |
|
6 | > **ProTip:** Marko also supports a [beautiful concise syntax](./concise.md). If you'd prefer to see the documentation using this syntax, just click the `switch syntax` button in the corner of any Marko code sample.
|
7 |
|
8 | > **Note:** Text at the root of a template (outside any tags) must be prefixed with the [concise syntax's `--`](./concise.md#text) to denote it is text. The parser starts in concise mode and would otherwise try to parse what you meant to be text as a concise tag declaration.
|
9 | >
|
10 | > ```marko
|
11 | > -- Root level text
|
12 | > ```
|
13 |
|
14 | ## Tags
|
15 |
|
16 | As you might expect, Marko supports all native HTML/SVG/whatever tags and attributes. In addition to these, it also comes with a set of useful [core tags](./core-tags.md). Beyond this, you can also build your own [custom tags](./custom-tags.md) and [install third-party tags](./custom-tags.md#using-tags-from-npm) from `npm`.
|
17 |
|
18 | All of these types of tags use the same syntax:
|
19 |
|
20 | ```marko
|
21 | <my-tag-name/>
|
22 | ```
|
23 |
|
24 | You don't need to import tags. Marko discovers them based on the folder structure—similar to how you don't specify a full path when referencing a module in `node_modules/`. Marko looks in [`components/`](./custom-tags.md#how-tags-are-discovered) by default and this directory can be configured in [`marko.json`](./marko-json.md).
|
25 |
|
26 | ## Dynamic text
|
27 |
|
28 | You can use placeholders (`${}`) to insert a value into the template:
|
29 | Placeholders accept any JavaScript expression and the result of the expression will be inserted into the HTML output:
|
30 |
|
31 | ```marko
|
32 | <div>
|
33 | Hello ${"world".toUpperCase()}
|
34 | </div>
|
35 | ```
|
36 |
|
37 | These values are automatically escaped so you don't accidentally insert malicious code. If you do need to pass unescaped HTML, you can use `$!{}`:
|
38 |
|
39 | ```marko
|
40 | <div>
|
41 | Hello $!{"<b>World</b>"}
|
42 | </div>
|
43 | ```
|
44 |
|
45 | > **ProTip:** If necessary, you can escape `$` using a backslash to have it be treated as text instead of a placeholder token:
|
46 | >
|
47 | > ```marko
|
48 | > <div>
|
49 | > Placeholder example: <code>\${someValue}</code>
|
50 | > </div>
|
51 | > ```
|
52 |
|
53 | ## Attributes
|
54 |
|
55 | In marko attributes are parsed as JavaScript expressions (instead of just strings).
|
56 |
|
57 | ```marko
|
58 | <div class=myClassName/>
|
59 | <input type="checkbox" checked=isChecked/>
|
60 |
|
61 | <custom-tag string="Hello"/>
|
62 | <custom-tag number=1/>
|
63 | <custom-tag template-string=`Hello ${name}`/>
|
64 | <custom-tag boolean=true/>
|
65 | <custom-tag array=[1, 2, 3]/>
|
66 | <custom-tag object={ hello: "world" }/>
|
67 | <custom-tag variable=name/>
|
68 | <custom-tag function-call=user.getName()/>
|
69 | ```
|
70 |
|
71 | Attributes that are passed to a custom tag are received as it's [`input`](https://markojs.com/docs/class-components/#input).
|
72 |
|
73 | > **Note:** Although in most cases you won't see a difference, strings are parsed as JavaScript strings, not HTML strings. Where this comes up most often is using the `pattern` attribute with the `<input>` tag: you need to "double escape" your regex escape sequences much like you were passing a string to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) (or you can use a literal `/regex/`).
|
74 | >
|
75 | > _Marko Source:_
|
76 | >
|
77 | > ```marko
|
78 | > <input pattern="\\w+" type="text"/>
|
79 | > <input pattern=/\w+/ type="text"/>
|
80 | > ```
|
81 | >
|
82 | > _HTML Output:_
|
83 | >
|
84 | > ```marko
|
85 | > <input pattern="\w+" type="text"/>
|
86 | > ```
|
87 |
|
88 | ### Complex expressions
|
89 |
|
90 | Any JavaScript expression is a valid attribute value, provided it meets the following criteria:
|
91 |
|
92 | _It does not contain any spaces_
|
93 |
|
94 | _It does not contain any right angle brackets (`>`)_
|
95 |
|
96 | ```marko
|
97 | <custom-tag sum=1+2 difference=3-4/>
|
98 | ```
|
99 |
|
100 | ```marko
|
101 | custom-tag sum=1+2 difference=3-4
|
102 | ```
|
103 |
|
104 | _Spaces and `>` are contained within matching `()`, `[]`, `{}`, strings and regexps_
|
105 |
|
106 | ```marko
|
107 | <custom-tag sum=(1 + 2) difference=(3 - 4) greater=(1 > 2)/>
|
108 | ```
|
109 |
|
110 | ```marko
|
111 | custom-tag sum=(1 + 2) difference=(3 - 4) greater=(1 > 2)
|
112 | ```
|
113 |
|
114 | ### Boolean attributes
|
115 |
|
116 | HTML defines the following rules for [boolean attributes](https://www.w3.org/TR/2008/WD-html5-20080610/semantics.html#boolean):
|
117 |
|
118 | > The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.
|
119 |
|
120 | In Marko when an attribute value evaluates to `false`, `null`, or `undefined`, the attribute is not included in the output. If an attribute value is `true`, only the attribute name is included in the output.
|
121 |
|
122 | _Marko Source:_
|
123 |
|
124 | ```marko
|
125 | <input type="checkbox" checked=true>
|
126 | <input type="checkbox" checked=false>
|
127 | ```
|
128 |
|
129 | Renders the following HTML:
|
130 |
|
131 | _HTML Output:_
|
132 |
|
133 | ```html
|
134 | <input type="checkbox" checked /> <input type="checkbox" />
|
135 | ```
|
136 |
|
137 | Similarly, when only an attribute name is defined, it is equivalent to specifying the attribute with a value of `true`:
|
138 |
|
139 | ```marko
|
140 | <!-- These are equivalent -->
|
141 | <custom-menu expanded>
|
142 | <custom-menu expanded=true>
|
143 | ```
|
144 |
|
145 | > **ProTip:**
|
146 | > You can take advantage of the way Marko handles boolean attributes to conditionally render attributes:
|
147 | >
|
148 | > _Marko Source:_
|
149 | >
|
150 | > ```marko
|
151 | > <div class=(active && "tab-active")>Hello</div>
|
152 | > ```
|
153 | >
|
154 | > With a value of `true` for `active`, the output would be the following:
|
155 | >
|
156 | > _HTML Output:_
|
157 | >
|
158 | > ```html
|
159 | > <div class="tab-active">Hello</div>
|
160 | > ```
|
161 | >
|
162 | > With a value of `false` for `active`, the output would be the following:
|
163 | >
|
164 | > _HTML Output:_
|
165 | >
|
166 | > ```html
|
167 | > <div>Hello</div>
|
168 | > ```
|
169 |
|
170 | ### Dynamic attributes
|
171 |
|
172 | The spread syntax (`...`) can be used to merge in an object as attributes to a tag:
|
173 |
|
174 | _Marko Source:_
|
175 |
|
176 | ```marko
|
177 | <a ...attrs target="_blank">eBay</a>
|
178 | ```
|
179 |
|
180 | With `attrs` as the following value:
|
181 |
|
182 | ```js
|
183 | {
|
184 | class: "active",
|
185 | href: "https://ebay.com/"
|
186 | }
|
187 | ```
|
188 |
|
189 | would output the following HTML:
|
190 |
|
191 | _HTML Output:_
|
192 |
|
193 | ```html
|
194 | <a class="active" href="https://ebay.com/" target="_blank">eBay</a>
|
195 | ```
|
196 |
|
197 | > **ProTip:**
|
198 | > With spread attributes order matters.
|
199 | > You can take advantage of this to implement both default attributes, and enforced attributes.
|
200 | >
|
201 | > ```marko
|
202 | > <custom-tag ...defaults ...userSupplied class="overridden"/>
|
203 | > ```
|
204 |
|
205 | > **ProTip:**
|
206 | > You can provide `undefined` to a spread attribute which will output nothing.
|
207 |
|
208 | ### Style attribute
|
209 |
|
210 | You can pass a string as the value of `style` just as you would in HTML, in addition Marko supports passing an object or array as the value of the `style` attribute:
|
211 |
|
212 | _Marko Source:_
|
213 |
|
214 | ```marko
|
215 | <!-- string: -->
|
216 | <div style="display:block;margin-right:16px"/>
|
217 |
|
218 | <!-- object: -->
|
219 | <div style={ display: "block", color: false, marginRight: 16 }/>
|
220 |
|
221 | <!-- array: -->
|
222 | <div style=["display:block", null, { marginRight: 16 }]/>
|
223 | ```
|
224 |
|
225 | In all cases, the output will be the same:
|
226 |
|
227 | _HTML Output:_
|
228 |
|
229 | ```html
|
230 | <div style="display:block;margin-right:16px;"></div>
|
231 | ```
|
232 |
|
233 | ### Class attribute
|
234 |
|
235 | The `class` attribute also supports receiving an object or array (in addition to a string) as shown below:
|
236 |
|
237 | _Marko Source:_
|
238 |
|
239 | ```marko
|
240 | <!-- string: -->
|
241 | <div class="a c"/>
|
242 |
|
243 | <!-- object: -->
|
244 | <div class={ a:true, b:false, c:true }/>
|
245 |
|
246 | <!-- array: -->
|
247 | <div class=["a", null, { c:true }]/>
|
248 | ```
|
249 |
|
250 | In all cases, the output will be the same:
|
251 |
|
252 | _HTML Output:_
|
253 |
|
254 | ```html
|
255 | <div class="a c"></div>
|
256 | ```
|
257 |
|
258 | ### Shorthand attributes
|
259 |
|
260 | Marko provides a shorthand for declaring classes and ids on an element:
|
261 |
|
262 | _Marko Source:_
|
263 |
|
264 | ```marko
|
265 | <div.my-class/>
|
266 | <span#my-id/>
|
267 | <button#submit.primary.large/>
|
268 | ```
|
269 |
|
270 | Renders the following HTML:
|
271 |
|
272 | _HTML Output:_
|
273 |
|
274 |
|
275 | ```html
|
276 | <div class="my-class"></div>
|
277 | <span id="my-id"></span>
|
278 | <button id="submit" class="primary large"></button>
|
279 | ```
|
280 |
|
281 | ## Parameters
|
282 |
|
283 | When a tag renders its body content, it may provide data which can be received by defining parameters after the tagname. Parameters are available to the tag's body content.
|
284 |
|
285 | This is a powerful feature that allows components to provide functionality and data while giving you full control over what gets rendered.
|
286 |
|
287 | In the following example, `<mouse>` provides a parameter which we have named `position`:
|
288 |
|
289 | ```marko
|
290 | <mouse|position|>
|
291 | The mouse is at ${position.x}, ${position.y}!
|
292 | </mouse>
|
293 | ```
|
294 |
|
295 | > `<mouse>` would [render its body](./body-content.md) and provide the position similar to this: `<${input.renderBody} x=0 y=0/>`.
|
296 |
|
297 | > **ProTip:** Tag `|parameters|` are treated as regular JavaScript function parameters. This means you can destructure, set default values, etc.
|
298 | >
|
299 | > ```marko
|
300 | > <mouse|{ x, y }|>
|
301 | > The mouse is at ${x}, ${y}!
|
302 | > </mouse>
|
303 | > ```
|
304 |
|
305 | > **Note:** Parameters are not available to attributes, only to the tag body.
|
306 | >
|
307 | > ```marko
|
308 | > <mouse|position| something=position>
|
309 | > ReferenceError when setting the "something" attribute
|
310 | > </mouse>
|
311 | > ```
|
312 |
|
313 | Parameters are used by some of Marko's [core tags](./core-tags.md) like the [`<for>`](./core-tags.md#for) and [`<await>`](./core-tags.md#await) tags.
|
314 |
|
315 | ## Arguments
|
316 |
|
317 | Some tags and attributes accept javascript style `arguments`. Arguments are denoted by parenthesis following the tag or attribute name. Arguments provide a way to pass unnamed data to a tag.
|
318 |
|
319 | ```marko
|
320 | <if(true)>
|
321 | <strong>Marko is awesome</strong>
|
322 | </if>
|
323 |
|
324 | <h1 body-only-if(skipHeading)>
|
325 | Conditional display heading, but always show content!
|
326 | </h1>
|
327 | ```
|
328 |
|
329 | Arguments are used by some of Marko's [core tags](./core-tags.md) like the [`<if>`](./core-tags.md#if-else-if-else) tag and [`body-only-if`](./core-tags.md#body-only-if) attribute displayed above.
|
330 |
|
331 | Previously you could also use them in your own [custom tags](./custom-tags.md) however it is now recommended to use [dynamic attributes](#dynamic-attributes).
|
332 |
|
333 | ## Dynamic tagname
|
334 |
|
335 | The `<${dynamic}>` syntax is used to render a tag or component that isn't determined until runtime. It can also be used within a [custom tag](./custom-tags.md) to render body content that was passed to that tag.
|
336 |
|
337 | _Marko Source:_
|
338 |
|
339 | ```marko
|
340 | <${href ? 'a' : 'button'} href=href>
|
341 | Click me!
|
342 | </>
|
343 | ```
|
344 |
|
345 | With `href` as `https://ebay.com` would output the following HTML:
|
346 |
|
347 | _HTML Output:_
|
348 |
|
349 | ```html
|
350 | <a href="https://ebay.com">Click me!</a>
|
351 | ```
|
352 |
|
353 | And with `href` as `undefined` would output the following HTML:
|
354 |
|
355 | _HTML Output:_
|
356 |
|
357 | ```html
|
358 | <button>Click me!</button>
|
359 | ```
|
360 |
|
361 | > **ProTip:**
|
362 | > If you find that you have a wrapper element that is conditional, but whose body should always be rendered then you can use a null dynamic tag. For example, to only render a wrapping `<a>` tag if there is a valid URL then you could do the following:
|
363 | >
|
364 | > _Marko Source:_
|
365 | >
|
366 | > ```marko
|
367 | > <${input.linkUrl ? "a" : null} href=input.linkUrl >
|
368 | > Some body content
|
369 | > </>
|
370 | > ```
|
371 | >
|
372 | > Given a value of `"http://localhost/"` for the `input.linkUrl` variable: , the output would be the following:
|
373 | >
|
374 | > _HTML Output:_
|
375 | >
|
376 | > ```html
|
377 | > <a href="http://localhost/"> Some body content </a>
|
378 | > ```
|
379 | >
|
380 | > Given a value of `undefined` for the `input.linkUrl` variable: , the output would be the following:
|
381 | >
|
382 | > _HTML Output:_
|
383 | >
|
384 | > ```html
|
385 | > Some body content
|
386 | > ```
|
387 |
|
388 | ### Dynamic components
|
389 |
|
390 | Instead of just strings, the dynamic tagname can also be a component:
|
391 |
|
392 | ```marko
|
393 | import componentA from "<component-a>";
|
394 | import componentB from "<component-b>";
|
395 |
|
396 | <${useA ? componentA : componentB}/>
|
397 | ```
|
398 |
|
399 | > **ProTip:**
|
400 | > You can also switch between a normal HTML tag and a component:
|
401 | >
|
402 | > ```marko
|
403 | > import FancyButton from "./path/to/fancy-button.marko";
|
404 | >
|
405 | > <${isFancy ? FancyButton : 'button'}>
|
406 | > Button text
|
407 | > </>
|
408 | > ```
|
409 |
|
410 | > **Note:** You cannot reference a Marko custom tag using a name string:
|
411 | >
|
412 | > _Marko Source:_
|
413 | >
|
414 | > ```marko
|
415 | > <${isFancy ? 'fancy-button' : 'button'}>
|
416 | > Button text
|
417 | > </>
|
418 | > ```
|
419 | >
|
420 | > With `isFancy` as `true` would output the following HTML:
|
421 | >
|
422 | > _HTML Output:_
|
423 | >
|
424 | > ```marko
|
425 | > <fancy-button>Button text</fancy-button>
|
426 | > ```
|
427 |
|
428 | ### Dynamic body content
|
429 |
|
430 | When a custom tag receives [body content](./body-content.md), it is passed as a `renderBody` property. To render this content you can pass the `renderBody` as the dynamic tagname.
|
431 |
|
432 | ```marko
|
433 | <div class="container">
|
434 | <${input.renderBody}/>
|
435 | </div>
|
436 | ```
|
437 |
|
438 | ## Attribute Tag
|
439 |
|
440 | As the name implies, `<@attribute-tags>` are special attributes that take the form of tags. They allow you to pass named body sections to a [custom tag](./custom-tags.md).
|
441 |
|
442 | The core `<await>` tag allows you to pass multiple body sections that it will conditionally render based on the state of the promise.
|
443 |
|
444 | ```marko
|
445 | <await(somePromise)>
|
446 | <@then|result|>
|
447 | The promise resolved: ${result}
|
448 | </@then>
|
449 | <@catch|error|>
|
450 | The promise rejected: ${error.message}
|
451 | </@catch>
|
452 | </await>
|
453 | ```
|
454 |
|
455 | These body sections are also commonly used to create layouts:
|
456 |
|
457 | ```marko
|
458 | <page-layout>
|
459 | <@heading>
|
460 | <h1>Hello</h1>
|
461 | </@heading>
|
462 | <@body>
|
463 | <p>Lorem ipsum....</p>
|
464 | </@body>
|
465 | </page-layout>
|
466 | ```
|
467 |
|
468 | These tags are passed to the custom tag as objects with a `renderBody`, it can then [render its body content](./body-content.md).
|
469 |
|
470 | > **Note:**
|
471 | > Attribute tags can have their own parameters, but like attributes, they cannot access the parameters of their parent tag:
|
472 | >
|
473 | > ```marko
|
474 | > <list|item|>
|
475 | > ${item.name}
|
476 | > <@separator>${item} (oops, ReferenceError)</@separator>
|
477 | > </list>
|
478 | > ```
|
479 |
|
480 | ## Inline JavaScript
|
481 |
|
482 | To execute JavaScript in your template you can insert a Javascript statement using the `$ <code>` syntax.
|
483 |
|
484 | A line that starts with a `$` followed by a space will execute the code that follows.
|
485 |
|
486 | ```marko
|
487 | $ const name = "World";
|
488 |
|
489 | <div>
|
490 | Hello, ${name}
|
491 | $ console.log("The value rendered was", name);
|
492 | </div>
|
493 | ```
|
494 |
|
495 | A statement may continue onto subsequent lines if new lines are bounded by `{}`, `[]`, `()`, ` `` `, or `/**/`:
|
496 |
|
497 | ```marko
|
498 | $ const person = {
|
499 | name: "Frank",
|
500 | age: 32
|
501 | };
|
502 | ```
|
503 |
|
504 | Multiple statements or an unbounded statement may be used by wrapping the statement(s) in a block:
|
505 |
|
506 | ```marko
|
507 | $ {
|
508 | const bgColor = getRandomColor();
|
509 | const textColor = isLight(bgColor)
|
510 | ? "black"
|
511 | : "white";
|
512 | }
|
513 | ```
|
514 |
|
515 | > **ProTip:** Any JavaScript statement can be used here, even `debugger`:
|
516 | >
|
517 | > ```marko
|
518 | > <div>
|
519 | > ${textColor}
|
520 | > $ debugger; // Quickly debug `textColor`
|
521 | > </div>
|
522 | > ```
|
523 |
|
524 | > **ProTip:** If necessary, you can escape `$` using a backslash to have it be treated as text instead of a placeholder token:
|
525 | >
|
526 | > ```marko
|
527 | > <p>You can run JS in a Marko template like this:</p>
|
528 | > <code>
|
529 | > \$ var num = 123;
|
530 | > </code>
|
531 | > ```
|
532 |
|
533 | > **ProTip:** If you find yourself writing a lot of inline JS, consider moving it out to an external file and then [`import`](#importing-external-files) it.
|
534 |
|
535 | ### Static JavaScript
|
536 |
|
537 | Inline JavaScript will run each time your template is rendered, but the JavaScript code that follows `static` will only run once when the template is loaded. It must be declared at the top level and does not have access to values passed in at render time.
|
538 |
|
539 | ```marko
|
540 | static var count = 0;
|
541 | static var formatter = new Formatter();
|
542 |
|
543 | static function sum(a, b) {
|
544 | return a + b;
|
545 | };
|
546 |
|
547 | <div>${formatter.format(sum(2, 3))}</div>
|
548 | ```
|
549 |
|
550 | Like inline Javascript, multiple statements or an unbounded statement may be used by wrapping the statement(s) in a block:
|
551 |
|
552 | ```marko
|
553 | static {
|
554 | var base = 2;
|
555 | function sum(a, b) {
|
556 | return base + a + b;
|
557 | };
|
558 | }
|
559 | ```
|
560 |
|
561 | ### Importing external files
|
562 |
|
563 | The `import` statement is used to access data and functions from external files. It follows the same syntax as the [JavaScript `import` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import).
|
564 |
|
565 | ```marko
|
566 | import sum from './utils/sum';
|
567 | <div>The sum of 2 + 3 is ${sum(2, 3)}</div>
|
568 | ```
|
569 |
|
570 | The `from` can also be wrapped in `<angle-brackets>` which will resolve the tag inside the angle brackets using Marko's [tag discovery](./custom-tags.md#how-tags-are-discovered).
|
571 | This can be useful when rendering a [dynamic component](#dynamic-components).
|
572 |
|
573 | ```marko
|
574 | import FancyLink from '<fancy-link>';
|
575 |
|
576 | <${input.isFancy ? FancyLink : "a"} href=input.href>
|
577 | Open Link
|
578 | </>
|
579 | ```
|
580 |
|
581 | > **ProTip:**
|
582 | > As a s
|
583 | >
|
584 | > _Marko Source:_
|
585 | >
|
586 | > ```marko
|
587 | > <div class=(active && "tab-active")>Hello</div>
|
588 | > ```
|
589 | >
|
590 | > With a value of `true` for `active`, the output would be the following:
|
591 | >
|
592 | > _HTML Output:_
|
593 | >
|
594 | > ```html
|
595 | > <div class="tab-active">Hello</div>
|
596 | > ```
|
597 | >
|
598 | > With a value of `false` for `active`, the output would be the following:
|
599 | >
|
600 | > _HTML Output:_
|
601 | >
|
602 | > ```html
|
603 | > <div>Hello</div>
|
604 | > ```
|
605 |
|
606 | ## Comments
|
607 |
|
608 | Standard HTML comments can be used and will be stripped out of the rendered output.
|
609 | At the top level of the template JavaScript comments (`// comment` and `/** comment */`) can also be used.
|
610 |
|
611 | ```marko
|
612 | <!-- This is a comment that will not be rendered -->
|
613 |
|
614 | <h1>Hello</h1>
|
615 | ```
|
616 |
|
617 | If you would like for your HTML comment to show up in the final output then you can use the [`html-comment` core tag](./core-tags.md#html-comment).
|