Catalyst Components automatically bind actions upon instantiation. Automatically as part of the connectedCallback
, a component will search for any children with the data-action
attribute, and bind events based on the value of this attribute. Any public method on a Controller can be bound to via data-action
.
Remember! Actions are automatically bound using the @controller
decorator. There's no extra JavaScript code needed.
<hello-world>
<input
data-target="hello-world.name"
type="text"
>
<button
data-action="click:hello-world#greetSomeone">
Greet Someone
</button>
<span
data-target="hello-world.output">
</span>
</hello-world>
import { controller, target } from "@github/catalyst"
@controller
class HelloWorldElement extends HTMLElement {
@target name: HTMLElement
@target output: HTMLElement
greetSomeone() {
this.output.textContent =
`Hello, ${this.name.value}!`
}
}
The actions syntax follows a pattern of event:controller#method
.
event
must be the name of a DOM Event, e.g. click
.:
is the required delimiter between the event
and controller
.controller
must be the name of a controller ascendant to the element.#
is the required delimieter between the controller
and method
.method
(optional) must be a public method attached to a controller's prototype. Static methods will not work.If method is not supplied, it will default to handleEvent
.
Some examples of Actions Syntax:
click:my-element#foo
-> click
events will call foo
on my-element
elements.submit:my-element#foo
-> submit
events will call foo
on my-element
elements.click:user-list
-> click
events will call handleEvent
on user-list
elements.click:user-list#
-> click
events will call handleEvent
on user-list
elements.click:top-header-user-profile#
-> click
events will call handleEvent
on top-header-user-profile
elements.nav:keydown:user-list
-> navigation:keydown
events will call handleEvent
on user-list
elements.Multiple actions can be bound to multiple events, methods, and controllers. For example:
<analytics-tracking>
<hello-world>
<input
data-target="hello-world.name"
data-action="
input:hello-world#validate
blur:hello-world#validate
focus:analytics-tracking#focus
"
type="text"
>
<button
data-action="
click:hello-world#greetSomeone
click:analytics-tracking#click
mouseover:analytics-tracking#hover
"
>
Greet Someone
</button>
</hello-world>
</analytics-tracking>
A Controller may emit custom events, which may be listened to by other Controllers using the same Actions Syntax. There is no extra syntax needed for this. For example a lazy-loader
Controller might dispatch a loaded
event, once its contents are loaded, and other controllers can listen to this event:
<hover-card disabled>
<lazy-loader data-url="/user/1" data-action="loaded:hover-card#enable">
<loading-spinner>
</lazy-loader>
</hover-card>
import {controller} from '@github/catalyst'
@controller
class LazyLoader extends HTMLElement {
connectedCallback() {
this.innerHTML = await (await fetch(this.dataset.url)).text()
this.dispatchEvent(new CustomEvent('loaded'))
}
}
@controller
class HoverCard extends HTMLElement {
enable() {
this.disabled = false
}
}
Custom elements can create encapsulated DOM trees known as "Shadow" DOM. Catalyst actions support Shadow DOM by traversing the shadowRoot
, if present, and also automatically watching shadowRoots for changes; auto-binding new elements as they are added.
If you're using decorators, then the @controller
decorator automatically handles binding of actions to a Controller.
If you're not using decorators, then you'll need to call bind(this)
somewhere inside of connectedCallback()
.
import {bind} from '@github/catalyst'
class HelloWorldElement extends HTMLElement {
connectedCallback() {
bind(this)
}
}
Catalyst automatically listens for elements that are dynamically injected into the DOM, and will bind any element's data-action
attributes. It does this by calling listenForBind(controller.ownerDocument)
. If for some reason you need to observe other documents (such as mutations within an iframe), then you can call the listenForBind
manually, passing a Node
to listen to DOM mutations on.
import {listenForBind} from '@github/catalyst'
@controller
class HelloWorldElement extends HTMLElement {
@target iframe: HTMLIFrameElement
connectedCallback() {
// listenForBind(this.ownerDocument) is automatically called.
listenForBind(this.iframe.document.body)
}
}