Component state: $
Each component gets a live state variable named $. This variable allows for fine-grained reactivity in a concise manner.
Note: Not yet comfortable with live variables? See live()
first to learn more!
The live variable is used both for internal logic as well as exposing properties and methods on the custom element. It is available in a component's <script> section as well as inside inline expressions in component's <template>.
Syntax
$.property;
$.method();
$.$attributes.name;
$.$other.$custom.properties;
Details
The live component state variable $ is crucial in writing components with Yozo. It is created once for each component instance, giving authors full control over internal component state. Its reactivity powers properties, attributes, methods, and other logic, such as {{ inline }}
expressions, #for
loops and more. It is also the primary (and only) way to share data between a component's <script>
and its <template>
. The following properties on $ are automatically created or looked for:
- $.attributes is an object representing the component's attributes, which are declared with
<meta attribute>
. If none are defined, then this is an empty object. For each attribute, a nested key with matching name (converted to camelCase) is created on said object. For example; declaring an attribute button-text on a component creates the property $.$attributes.buttonText.
- Each property defined with
<meta property>
is looked up under the given key in $ and then exposed on the custom element in question. This opens up a reactive API for properties that are traditionally just getters and setters.
- If an attribute declared through <meta attribute> has a given type, then an accompanying property is created. Unless overwritten with the as option, the property is a camelCase-transformed version of the attribute name. This property is reflected on $ as well as on the custom element itself.
- Each method defined through
<meta method>
is looked up under $
. If declared, the property must be set to a function (usually done in the <script>
section).
Of course, other properties beyond these may be set (including binding data with live.link()
at any point to retain any arbitrary live data and to share it with the <template>, where expressions always include $ into their scope.
Examples
Markdown editor
Let's say we have a function md.render() that takes a string of Markdown as input, and outputs HTML (as a string). We'll build a component that lets a used enter some Markdown-formatted text into a <textarea>, and it'll display the output below. To do this, we'll first need to import the md
variable into our component (we'll put it in $.md) using a dynamic import(), and in the mean time we should show a loader. Next, we link $.input to the value in the textarea using live.link()
, and lastly set up an effect()
to render the output.
<title>markdown-editor</title>
<template>
<div #if="!$.md">
loading…
</div>
<template #else>
<textarea></textarea>
<div id="output"></div>
</template>
</template>
<script>
live.link($.$input, textarea);
import('./md.js').then(({ md }) => $.md = md);
const output = query('#output');
connected(() => {
effect(() => {
if(!$.md) return;
output.innerHTML = $.md.render($.input);
});
});
</script>
At first, $.md is undefined, causing the loader to be show, and the effect has an early return for when the imported module is not yet ready. Once it becomes available, the effect re-runs and uses the value of $.input, which has been bound to the textarea to render the Markdown. Since $.input
is linked, it reactively updates based on changes in the textarea.
Animal sounds
For this example, we'll build a component that receives an animal in an animal attribute, and displays the noise (such as "meow") that animals makes. We'll have a boolean loud attribute, which causes the noise to be displayed in all-caps. Additionally, we'll expose an .alert() method on the element, and a read-only property .isMammal. Our component definition might look something like
<title>animal-sound</title>
<meta attribute="animal" type="string" default="cat">
<meta attribute="loud" type="boolean">
<meta method="alert">
<meta property="isMammal" readonly>
<template>
{{ $.loud ? $.noise.toUpperCase() : $.noise }}
</template>
<script>
const animals = {
cat: { noise: 'meow', isMammal: true },
dog: { noise: 'woof', isMammal: true },
fish: { noise: 'blub', isMammal: false },
frog: { noise: 'croak', isMammal: false },
};
live.link($.$data, () => animals[$.animal]);
live.link($.$noise, () => $.data?.noise ?? '');
live.link($.$isMammal, () => $.data?.isMammal ?? false);
$.alert = () => {
window.alert($.loud ? $.noise.toUpperCase() : $.noise);
}
</script>
Let's go through what's happening here, starting from the <script>. First, we define some data; this is our small database of sorts, and includes all the animals we want to support with the animal attribute. Next, we bind the relevant data from said database to the $.data variable. This stays up-to-date; if the component's .animal property or animal attribute changes, then $.data updates with it. This allows us to just read $.data from here on out instead of having to worry about which animal we need to use. As such, we can define $.noise and $.isMammal just from reading $.data. The latter is exposed to the custom element because we defined it through <meta property=…>. The former, $.noise is used only internally. Lastly, we define the $.alert() method, since we declared it using <meta method=…>. This means our custom element gets that .alert() method as well. Inside the <template>, similar to the logic in the .alert() method, we check if we need to use uppercase or not and display the animal sound.
See also