Palette Templates use standard HTML <template> elements as their
source while compiling into an efficient, reactive Template instance.
The templating language for Palette is HTML. In fact, you can just use
<template> tags in your HTML and reference them when defining Components if
you prefer. It's pretty clean!
<template id="counting-button-template">
<button id="btn">
<span ::swap="$count"></span>
</button>
</template>
class CountingButton extends Component {
static template = document.getElementById("counting-button-template");
}
But if you want a little more oomph, you can use the html template string
helper to create template elements on the fly with a little bit of added sugar:
import { html } from "@rusticarcade/palette";
const template = html`
<button id="btn">
${"$count"}
</button>
`;
When using the html helper, any string inside of an interpolated value is
converted into a placeholder tag with a ::swap directive (more on that soon.)
Additionally, you can reference previously defined Palette Components by value in templates to ensure their correct html tag is used. Great for structured apps importing other components:
import { html } from "@rusticarcade/palette";
import { UserProfile } from "#components";
const template = html`
<${UserProfile} :user-id="*user.id"></${UserProfile}>
`;
Templates use custom HTML attribute directives prefixed with one or two colon
characters (:) to describe how the template should render with data.
The values of these directives must be a Notation, which is a string that describes what value to use in its place when the template renders.
Notations always begin with a special character, followed by a dot-separated accessor path.
There are four types of Notations, based on where you want to pull data from:
| Notation Prefix | Data Target | Example |
|---|---|---|
@ |
Host element attributes | @class, @content |
$ |
Reactive component state | $hidden, $user.name |
* |
Computed properties | *items.2.done, *classname |
# |
List item context | #id, #message.content |
Here's an example of binding a reactive state value to the "title" attribute of an element:
<a href="..." :title="$linkTitle">Click me!</a>
If our component then looks like...
define("example-component", {
initialState = {
linkTitle: "I'm the link title!",
};
});
Then the rendered template content would be...
<a href="..." title="I'm the link title!">Click me!</a>
Whenever the Component updates it's state, the template will automatically update with the latest values.
In addition to the :attribute binding syntax, Palette supports several special
directives for more specific use cases:
| Directive | Use | Example |
|---|---|---|
::swap |
Swap the element for the target value | <span ::swap="$content" /> |
::each |
Render for each item in a list | <li ::each="*items" /> |
::key |
Define a key for list items | <li ::each="*items" ::key="#id" /> |
::tag |
Swap the tagname of this element | <h1 ::tag="*tagname">Title</h1> |
::if |
Conditional rendering | <div ::if="*visible"></div> |
::else-if |
Conditional rendering branch | <div ::else-if="@something"></div> |
::else |
Conditional fallback branch | <div ::else></div> |
Conditional rendering in Palette templates use the ::if, ::else-if, and
::else directives.
Both ::if and ::else-if must be provided a notation which is evaluated to
determine which conditional branch to display. ::else is a boolean attribute.
Elemnts in a conditional with multiple branches must be immediate siblings, and
must begin with an ::if, followed by zero or more ::else-if elements, and
finally zero or one ::else
<div ::if="*loggedIn" class="user-menu">
<p>Welcome, <span ::swap="$username" />!</p>
</div>
<div ::else-if="*isGuest" class="guest-menu">
<p>Welcome, guest!</p>
</div>
<div ::else class="register-menu">
<a href="...">Register</a>
</div>
Using ::swap="<notation>" causes the element with the directive to be replaced
with the value of the notation.
Strings, numbers, and booleans are cast to strings and rendered as text nodes.
HTMLElements are cloned and adopted to the DOM.
null and undefined render as an HTML comment.
Other values are unsupported.
define("example-component", {
template: html`
<div>
The current time is <span ::swap="*time"></span>
</div>
`,
computedProperties = {
time() {
return new Date().toISOString(),
}
}
})
Using ::each="<notation>" allows for iterating over a list of data. The notation
must resolve to a value that is an array of objects.
Properties on the array items can be accessed with the # notation prefix.
Along with ::each, you must specify a ::key directive. This directive must
contain a notation which resolves to a unique value for each element in the list.
Typically, the ::key will be something like #id, using a property called id
on the list item as the key.
class Example extends Component {
static template = html`
<ul>
<li ::each="*items" ::key="#id">
<span>The sky is ${"#color"}</span>
</li>
</ul>
`;
computedProperties = {
items: [
{
id: 1,
color: "red",
},
{
id: 2,
color: "blue",
}
]
}
}
Pretty simple: it just swaps out the html tag on the element with the directive.
It retains all child contents, but it does replace the target element itself.
describe("example-component", {
template: html`
<h1 ::tag="*tag">Dynamic Heading!</h1>
`,
computedProperties() {
const headingLevel = this.getAttribute("level", 1);
return {
tag: `h${headingLevel}`
}
}
})