|
| 1 | +--- |
| 2 | +version: 2 |
| 3 | +chapter: 6 |
| 4 | +title: Actionable |
| 5 | +subtitle: Binding Events |
| 6 | +permalink: /guide-v2/actions |
| 7 | +--- |
| 8 | + |
| 9 | +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`. |
| 10 | + |
| 11 | +{% capture callout %} |
| 12 | +Remember! Actions are _automatically_ bound using the `@controller` decorator. There's no extra JavaScript code needed. |
| 13 | +{% endcapture %}{% include callout.md %} |
| 14 | + |
| 15 | +### Example |
| 16 | + |
| 17 | +<div class="d-flex my-4"> |
| 18 | + <div class=""> |
| 19 | + |
| 20 | +<!-- annotations |
| 21 | +data-action "click.*": Will call `greetSomeone()` when clicked |
| 22 | +--> |
| 23 | + |
| 24 | +```html |
| 25 | +<hello-world> |
| 26 | + <input |
| 27 | + data-target="hello-world.name" |
| 28 | + type="text" |
| 29 | + > |
| 30 | + |
| 31 | + <button |
| 32 | + data-action="click:hello-world#greetSomeone"> |
| 33 | + Greet Someone |
| 34 | + </button> |
| 35 | + |
| 36 | + <span |
| 37 | + data-target="hello-world.output"> |
| 38 | + </span> |
| 39 | +</hello-world> |
| 40 | +``` |
| 41 | + |
| 42 | + </div> |
| 43 | + <div class="ml-4"> |
| 44 | + |
| 45 | +<!-- annotations |
| 46 | +greetSomeone: All public methods can be called with `data-action` |
| 47 | +--> |
| 48 | + |
| 49 | +```js |
| 50 | +import { controller, target } from "@github/catalyst" |
| 51 | + |
| 52 | +@controller |
| 53 | +class HelloWorldElement extends HTMLElement { |
| 54 | + @target name: HTMLElement |
| 55 | + @target output: HTMLElement |
| 56 | + |
| 57 | + greetSomeone() { |
| 58 | + this.output.textContent = |
| 59 | + `Hello, ${this.name.value}!` |
| 60 | + } |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | + </div> |
| 65 | +</div> |
| 66 | + |
| 67 | +### Actions Syntax |
| 68 | + |
| 69 | +The actions syntax follows a pattern of `event:controller#method`. |
| 70 | + |
| 71 | + - `event` must be the name of a [_DOM Event_](https://developer.mozilla.org/en-US/docs/Web/Events), e.g. `click`. |
| 72 | + - `controller` must be the name of a controller ascendant to the element. |
| 73 | + - `method` (optional) must be a _public_ _method_ attached to a controller's prototype. Static methods will not work. |
| 74 | + |
| 75 | +If method is not supplied, it will default to `handleEvent`. |
| 76 | + |
| 77 | +Some examples of Actions Syntax: |
| 78 | + |
| 79 | +- `click:my-element#foo` -> `click` events will call `foo` on `my-element` elements. |
| 80 | +- `submit:my-element#foo` -> `submit` events will call `foo` on `my-element` elements. |
| 81 | +- `click:user-list` -> `click` events will call `handleEvent` on `user-list` elements. |
| 82 | +- `click:user-list#` -> `click` events will call `handleEvent` on `user-list` elements. |
| 83 | +- `click:top-header-user-profile#` -> `click` events will call `handleEvent` on `top-header-user-profile` elements. |
| 84 | +- `nav:keydown:user-list` -> `navigation:keydown` events will call `handleEvent` on `user-list` elements. |
| 85 | + |
| 86 | +### Multiple Actions |
| 87 | + |
| 88 | +Multiple actions can be bound to multiple events, methods, and controllers. For example: |
| 89 | + |
| 90 | +<!-- annotations |
| 91 | +data-action: Fires all of these methods depending on the event |
| 92 | +--> |
| 93 | + |
| 94 | +```html |
| 95 | +<analytics-tracking> |
| 96 | + <hello-world> |
| 97 | + <input |
| 98 | + data-target="hello-world.name" |
| 99 | + data-action=" |
| 100 | + input:hello-world#validate |
| 101 | + blur:hello-world#validate |
| 102 | + focus:analytics-tracking#focus |
| 103 | + " |
| 104 | + type="text" |
| 105 | + > |
| 106 | + |
| 107 | + <button |
| 108 | + data-action=" |
| 109 | + click:hello-world#greetSomeone |
| 110 | + click:analytics-tracking#click |
| 111 | + mouseover:analytics-tracking#hover |
| 112 | + " |
| 113 | + > |
| 114 | + Greet Someone |
| 115 | + </button> |
| 116 | + </hello-world> |
| 117 | +</analytics-tracking> |
| 118 | +``` |
| 119 | + |
| 120 | +### Custom Events |
| 121 | + |
| 122 | +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: |
| 123 | + |
| 124 | +<!-- annotations |
| 125 | +data-action "loaded: Calls enable() on the `loaded` custom event |
| 126 | +--> |
| 127 | + |
| 128 | +```html |
| 129 | +<hover-card disabled> |
| 130 | + <lazy-loader data-url="/user/1" data-action="loaded:hover-card#enable"> |
| 131 | + <loading-spinner> |
| 132 | + </lazy-loader> |
| 133 | +</hover-card> |
| 134 | +``` |
| 135 | + |
| 136 | +<!-- annotations |
| 137 | +this . dispatchEvent . new CustomEvent . . loaded . . : Dispatches custom "loaded" event |
| 138 | +enable: All public methods can be called with `data-action` |
| 139 | +--> |
| 140 | + |
| 141 | +```js |
| 142 | +import {controller} from '@github/catalyst' |
| 143 | + |
| 144 | +@controller |
| 145 | +class LazyLoader extends HTMLElement { |
| 146 | + |
| 147 | + connectedCallback() { |
| 148 | + this.innerHTML = await (await fetch(this.dataset.url)).text() |
| 149 | + this.dispatchEvent(new CustomEvent('loaded')) |
| 150 | + } |
| 151 | + |
| 152 | +} |
| 153 | + |
| 154 | +@controller |
| 155 | +class HoverCard extends HTMLElement { |
| 156 | + |
| 157 | + enable() { |
| 158 | + this.disabled = false |
| 159 | + } |
| 160 | + |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +### Targets and "ShadowRoots" |
| 165 | + |
| 166 | +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. |
| 167 | + |
| 168 | +### What about without Decorators? |
| 169 | + |
| 170 | +If you're using decorators, then the `@controller` decorator automatically handles binding of actions to a Controller. |
| 171 | + |
| 172 | +If you're not using decorators, then you'll need to call `bind(this)` somewhere inside of `connectedCallback()`. |
| 173 | + |
| 174 | +```js |
| 175 | +import {bind} from '@github/catalyst' |
| 176 | + |
| 177 | +class HelloWorldElement extends HTMLElement { |
| 178 | + connectedCallback() { |
| 179 | + bind(this) |
| 180 | + } |
| 181 | +} |
| 182 | +``` |
| 183 | + |
| 184 | +### Binding dynamically added actions |
| 185 | + |
| 186 | +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. |
| 187 | + |
| 188 | +```js |
| 189 | +import {listenForBind} from '@github/catalyst' |
| 190 | + |
| 191 | +@controller |
| 192 | +class HelloWorldElement extends HTMLElement { |
| 193 | + @target iframe: HTMLIFrameElement |
| 194 | + |
| 195 | + connectedCallback() { |
| 196 | + // listenForBind(this.ownerDocument) is automatically called. |
| 197 | + |
| 198 | + listenForBind(this.iframe.document.body) |
| 199 | + } |
| 200 | +} |
| 201 | +``` |
0 commit comments