forked from hotwired/stimulus
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Action Descriptor Syntax: Support Outlet listeners
The original [idea][] for this change was outlined in a comment on [hotwired#576]. The problem --- Prior to this commit, any Outlet-powered references would need to manage event listeners from within the Stimulus Controller JavaScript. For example, consider the following HTML: ```html <dialog id="dialog" data-controller="element"> <span>This dialog is managed through a disclosure button powered by an Outlet.</span> <form method="dialog"> <button>Close</button> </form> </dialog> <button type="button" data-controller="disclosure" data-disclosure-element-outlet="#dialog" data-action="click->disclosure#expand"> Open dialog </button> ``` Clicking the `button[type="button"]` opens the `dialog` element by calling its [HTMLDialogElement.showModal()][] method. Consider the following `element` and `disclosure` controller implementations: ```js // element_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { showModal() { this.element.showModal() } } // disclosure_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static outlets = ["element"] elementOutletConnected(controller, element) { this.element.setAttribute("aria-controls", element.id) this.element.setAttribute("aria-expanded", element.open) element.addEventListener("close", this.collapse) } elementOutletDisconnected() { element.removeEventListener("close", this.collapse) this.element.removeAttribute("aria-controls") this.element.removeAttribute("aria-expanded") } collapse = () => { this.element.setAttribute("aria-expanded", false) this.element.focus() } expand() { for (const elementOutlet of this.elementOutlets) { elementOutlet.showModal() this.element.setAttribute("aria-expanded", elementOutlet.element.open) } } } ``` Note the mirrored calls to add and remove [close][] event listeners. Whenever the `dialog` element closes, it'll dispatch a `close` event, which the `disclosure` controller will want to respond to. Attaching and removing event listeners whenever an element connects or disconnects is one of Stimulus's core capabilities, and declaring event listeners as part of `[data-action]` is idiomatic. In spite of those facts, the `disclosure` controller is responsible for the tedium of managing its own event listeners. The proposal --- To push those declarations out of the JavaScript and back into the HTML, this commit extends the Action Descriptor syntax to support declaring actions with `@`-prefixed controller identifiers, in the same way that `window` and `document` are special-cased. With that support, the HTML changes: ```diff <dialog id="dialog" data-controller="element"> <span>This dialog is managed through a disclosure button powered by an Outlet.</span> <form method="dialog"> <button>Close</button> </form> </dialog> <button type="button" data-controller="disclosure" data-disclosure-element-outlet="#dialog" - data-action="click->disclosure#expand"> + data-action="click->disclosure#expand close@element->disclosure#collapse"> Open dialog </button> ``` And our `disclosure` controller has fewer responsibilities, and doesn't need to special-case the `collapse` function's binding: ```diff elementOutletConnected(controller, element) { this.element.setAttribute("aria-controls", element.id) this.element.setAttribute("aria-expanded", element.open) - element.addEventListener("close", this.collapse) } elementOutletDisconnected() { - element.removeEventListener("close", this.collapse) this.element.removeAttribute("aria-controls") this.element.removeAttribute("aria-expanded") } - collapse = () => { + collapse() { this.element.setAttribute("aria-expanded", false) this.element.focus() } ``` Risks --- Changing the action descriptor syntax has more long-term maintenance risks that other implementation changes. If we "spend" the syntax on this type of support, we're pretty stuck with it. Similarly, existing support for `window` and `document` as special symbols means that we'd need to make special considerations (or warnings) to support applications with `window`- and `document`-identified controllers. [hotwired#576]: hotwired#576 [idea]: hotwired#576 (comment) [HTMLDialogElement.showModal()]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal [close]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/close_event
- Loading branch information
1 parent
2ff5440
commit df8f748
Showing
17 changed files
with
255 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
|
||
export default class extends Controller { | ||
static outlets = ["element"] | ||
|
||
elementOutletConnected(controller, element) { | ||
this.element.setAttribute("aria-controls", element.id) | ||
this.element.setAttribute("aria-expanded", element.open) | ||
} | ||
|
||
elementOutletDisconnected() { | ||
this.element.removeAttribute("aria-controls") | ||
this.element.removeAttribute("aria-expanded") | ||
} | ||
|
||
expand() { | ||
for (const elementOutlet of this.elementOutlets) { | ||
elementOutlet.showModal() | ||
this.element.setAttribute("aria-expanded", elementOutlet.element.open) | ||
} | ||
} | ||
|
||
collapse() { | ||
this.element.setAttribute("aria-expanded", false) | ||
this.element.focus() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
|
||
export default class extends Controller { | ||
showModal() { | ||
this.element.showModal() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<%- include("layout/head") %> | ||
|
||
<dialog id="dialog" class="container" data-controller="element"> | ||
<span>This dialog is managed through a disclosure button powered by an Outlet.</span> | ||
|
||
<form method="dialog"> | ||
<button>Close</button> | ||
</form> | ||
</dialog> | ||
|
||
<button type="button" | ||
data-controller="disclosure" | ||
data-disclosure-element-outlet="#dialog" | ||
data-action="click->disclosure#expand close@element->disclosure#collapse">Open dialog</button> | ||
|
||
<%- include("layout/tail") %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.