The Component
class extends the standard EventTarget
class so
components can dispatch (aka "fire" or "raise") events.
Here is a custom button component dispatching a "click" event:
// A component that raises "click" events
class MyButton extends Component Component extends EventTarget
{
onClick()
{
this.dispatchEvent(new Event("click")); Raise event
}
static template = {
type: "button",
text: "MyButton",
on_click: c => c.onClick(),
}
}
In some of the samples, like the one above, we've hidden some of the supporting code to help emphasize the point being made.
These snipped sections of code are indicated with the scissor icon:
To view the unabridged code open the sample in the Lab by clicking the Edit button.
Components will often need to load data from external sources. This is commonly referred to as "async data loading" and often a spinner will be shown while the data is being fetched.
Since this is a common pattern for many components, the Component
class includes a couple of helpers: a load
method, a loading
and
loadError
property and some associated events.
The load
method does the following:
loading
property to trueloadError
propertyinvalidate
method to mark the component for updateloading
eventawait
s the supplied callbackloadError
propertyinvalidate
method againloaded
eventFor example:
class Main extends Component
{
loadData()
{
this.load(async () => {
// Load data here
});
}
static template = [
{
if: c => c.loading, Show spinner while loading
type: "div .spinner",
},
{
else: true, Otherwise show loaded data
$: [
// Display data here
]
}
]
}
Using this approach provides a couple of benefits:
load
method is async
, so you can await
it to know
when the load has finished.loading
flag is cleared on success or failure.The load
method also supports a second parameter silent
. Silent
mode allows refreshing the data without showing spinners or other
feedback:
Components have the following life-cycle states:
Constructed: Immediately after the JavaScript object is constructed but before the DOM elements have been created.
Created: After the DOM elements have been created. Usually this
happens automatically just before the component is mounted. If you
need to access the DOM elements beforehand, call the create()
method.
Mounted: After the component's DOM is attached to the document DOM.
The component's onMount()
method is called when it's mounted.
Unmounted: After a component is removed from the document DOM.
The component's onUnmount()
method is called when it's unmounted.
Destroyed: After a parent component that constructed this component
no longer needs the component it will call the component's destroy()
method which releases the DOM elements associated with the component.
In general a component should connect to external resources in the
onMount()
and disconnect from them in onUnmount()
. These are
the most reliable mechanism to know if a component is still in use.
A component will never be destroyed while it's mounted.
A destroyed component is effectively reset to the "constructed" state and can re-created if necessary.
When a component needs to listen to external events care must be taken to ensure the event handlers are removed otherwise dangling references to the component may prevent it from being garbage collected by the JavaScript runtime.
The correct way to handle this is for the component to add event listeners when the component is mounted and remove them when the component is unmounted.
This can be done manually by overriding onMount()
and onUnmount()
but a simpler method is to use the Component.listen
method.
listen(target, event, handler)
where:
target
- any object that supports add/removeEventListener()
event
- the event to listen forhandler
- a handler function for the event.The listen
function automatically adds the event listener when
the component is mounted and removes the event listener when the
component is unmounted.
See listen() for more.
Normally components use the template declared by the static template
property but this can be changed by overriding the onProvideTemplate()
function.
class MyComponent extends Component
{
// Called by Component to get the template to be compiled
static onProvideTemplate()
{
return {
// ... template definition ...
}
}
}
Consider, for example, a dialog class where every dialog has the same frame but different content in the main body.
class Dialog extends Component
{
// Override to wrap template in dialog frame
static onProvideTemplate()
{
return {
type: "dialog",
class: "dialog",
id: this.template.id, From the derived class template
$: {
type: "form",
method: "dialog",
$: [
{
type: "header",
$: this.template.title, From the derived class template
},
{
type: "main",
$: this.template.content, From the derived class template
},
{
type: "footer",
$: {
type: "button",
$: "Close",
}
},
]
}
};
}
}
class MyDialog extends Dialog extending Dialog, not Component
{
// This template will be "re-templated" by the base Dialog class
// to wrap it in <dialog>, <form> etc...
static template = {
title: "My Dialog's Title",
id: "my-dialog",
content: {
type: "p",
$: "Hello World! This is the dialog's content",
}
}
}
In the above example, anything not directly related to template handling has been omitted. Click the "Edit" link above to see the full code.
Notice how the enclosing dialog
, form
, header
, main
and footer
elements
are provided automatically by the base Dialog
class, but the content of the header
and main
elements is provided by the derived MyDialog
class
<dialog class="dialog" id="my-dialog">
<form method="dialog">
<header>
My Dialog
</header>
<main>
<p>Hello World</p>
</main>
<footer>
<button>Close</button>
</footer>
</form>
</dialog>
A component's domTree
is the object responsible for managing the DOM
elements associated with the component.
The domTree
is usually the object created by a compiled template
and has methods to update the tree, get the root node of the tree etc...
The domTree
is created on demand when the component is first mounted, but
can be manually created by calling the component's create()
method.
When a component is destroyed, its domTree
is released, and the component
reverts from a "created" state to a "constructed" state. Remounting the
component, or calling its create()
method again will create a new domTree
.
The setMounted
method is an internal method used to notify a component
and its template that it has been mounted or unmounted.
When a component's setMounted
method is called, it calls onMount()
or
onUmount()
method to notify the component of the new state. It then calls
setMounted()
on the component's domTree
so the notification is reflected
recursively through all domTree
s.
You can override the setMounted
method however it's extremely important
that you also call super.setMounted(mounted)
so all other nested components
receive the notification.
When overriding onMount()
and onUnmount()
calling super.onMount()
and
super.onUnmount()
isn't required (unless you're extending another class
that expects these notification).