-
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Controllers): New SignalDomChildrenController which emits signal…
…s when its children/descendants are added or removed
- Loading branch information
Showing
5 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
80 changes: 80 additions & 0 deletions
80
docs/docs/controllers/signal/signal_dom_children_controller.mdx
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,80 @@ | ||
--- | ||
id: SignalDOMChildrenController | ||
title: SignalDOMChildrenController | ||
--- | ||
|
||
|
||
import NoTargets from "../../_partials/no-targets.md"; | ||
import NoActions from "../../_partials/no-actions.md"; | ||
import NoSideEffects from "../../_partials/no-side-effects.md"; | ||
import NoEvents from "../../_partials/no-events.md"; | ||
import NoClasses from "../../_partials/no-classes.md"; | ||
|
||
## Purpose | ||
|
||
A controller that listens for changes to the elements that are children/contents/descendents of the element that it is mounted on, | ||
and emits a count of the number of elements that are in the child tree to any other Signal controllers that have subscribed by referencing | ||
the same `nameValue` as this controller. | ||
|
||
The emitted value can then be used in expressions in other `Signal*` controllers to enable more complex interactions. | ||
|
||
A common use case for this would be to use this controller in conjunction with the `SignalVisibilityController`to show | ||
a blank slate message like "You don't have any posts, create one now!" when there are no results on a page, and hide it again when items arrive. | ||
Particularly in contexts where contents of a container can arrive and be removed remotely VIA Server-Sent-Events, AJAX, | ||
TurboStreams, Websockets and even Third-Party JS interactions with the page. | ||
|
||
Other use cases might be to show or hide elements when there is a specific number of items in the child tree, or greater than a certain number. | ||
|
||
|
||
## [Actions](https://stimulus.hotwire.dev/reference/actions) | ||
<NoActions/> | ||
|
||
## [Targets](https://stimulus.hotwire.dev/reference/targets) | ||
<NoTargets/> | ||
|
||
## [Classes](https://stimulus.hotwire.dev/reference/classes) | ||
<NoClasses/> | ||
|
||
## [Values](https://stimulus.hotwire.dev/reference/values) | ||
|
||
| Value | Type | Description | Default | | ||
|----------------------------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------| | ||
| `name` | String | The name/key to send signals using. This name should be the same as the `nameValue` of the other SignalControllers you want to sync with. | `-` | | ||
| `scopeSelector` (Optional) | String | A CSS selector to pass to `querySelectorAll` to limit what elements are included in the count of empty/not-empty. You can use this to ignore certain elements from the count | All child elements of the controller root element | | ||
|
||
## Events | ||
<NoEvents/> | ||
|
||
## Side Effects | ||
<NoSideEffects/> | ||
|
||
## How to Use | ||
|
||
```html | ||
|
||
<div data-controller="signal-dom-children" data-demo-target="output" data-signal-dom-children-name-value="notifications"> | ||
<!-- Notifications are put into this element by websockets, or a third-party library. --> | ||
<!-- Notifications can be dismissed by clicking "X", which simply removes them from the DOM. --> | ||
<!-- We don't need to call any methods, fire any events, or reference any controllers directly --> | ||
<!-- The controller will emit a signal when elements are added or removed, and inform any listening controllers with the same `nameValue` of how many elements there are --> | ||
</div> | ||
|
||
<div | ||
class="alert alert-success my-4" | ||
data-controller="signal-visibility" | ||
data-signal-visibility-name-value="notifications" | ||
data-signal-visibility-show-value="<=0" | ||
> | ||
<!-- This message will only show when there are zero notifications --> | ||
Well done! You've reached inbox zero. | ||
Make yourself a cup of tea, you deserve it. | ||
</div> | ||
|
||
``` | ||
|
||
<iframe | ||
src="https://stimulus-library.netlify.app/controllers/signal_dom_children_controller.html" | ||
style={{width: "100%", height: "500px", border: "0", borderRadius: "4px", overflow: "hidden"}} | ||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" | ||
/> | ||
|
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,55 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"/> | ||
<link href="favicon.svg" rel="icon" type="image/svg+xml"/> | ||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/> | ||
<title>Signal DOM Children Controller</title> | ||
</head> | ||
<body> | ||
<div class="container p-4 text-center"> | ||
|
||
<div data-controller="demo"> | ||
<div data-controller="signal-dom-children" data-demo-target="output" data-signal-dom-children-name-value="dom-visibility"> | ||
</div> | ||
|
||
<a class="btn btn-primary" data-action="demo#addElement">Add Element</a> | ||
<a class="btn btn-primary" data-action="demo#removeElement">Remove Element</a> | ||
</div> | ||
|
||
<div | ||
class="alert alert-success my-4" | ||
data-controller="signal-visibility" | ||
data-signal-visibility-name-value="dom-visibility" | ||
data-signal-visibility-show-value=">0" | ||
> | ||
There are elements in the observed container. | ||
</div> | ||
|
||
<div | ||
class="alert alert-warning my-4" | ||
data-controller="signal-visibility" | ||
data-signal-visibility-name-value="dom-visibility" | ||
data-signal-visibility-show-value="<=0" | ||
> | ||
No elements in the observed container. | ||
Click a button to add one! | ||
</div> | ||
|
||
</div> | ||
|
||
<style> | ||
.hide { | ||
display: none; | ||
} | ||
|
||
.favourite-colour { | ||
padding: 2em; | ||
margin: 2em; | ||
border-radius: 5px; | ||
border: 1px solid white; | ||
} | ||
</style> | ||
<script src="/main.js" type="module"></script> | ||
</body> | ||
</html> |
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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './signal_action_controller'; | ||
export * from './signal_dom_children_controller'; | ||
export * from './signal_input_controller'; | ||
export * from './signal_visibility_controller'; |
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,51 @@ | ||
import {BaseController} from "../../utilities/base_controller"; | ||
import {SignalPayload} from "./signal_input_controller"; | ||
import {EventBus} from "../../utilities"; | ||
import {signalConnectEvent, signalValueEvent} from "./events"; | ||
import {useMutationObserver} from "../../mixins/use_mutation_observer"; | ||
import {useEventBus} from "../../mixins/use_event_bus"; | ||
|
||
export class SignalDomChildrenController extends BaseController { | ||
|
||
static values = { | ||
name: String, | ||
scopeSelector: String, | ||
}; | ||
declare nameValue: string; | ||
declare hasNameValue: boolean; | ||
declare hasScopeSelectorValue: boolean; | ||
declare scopeSelectorValue: string; | ||
|
||
get _children(): Element[] { | ||
if (this.hasScopeSelectorValue) { | ||
return Array.from(this.el.querySelectorAll(this.scopeSelectorValue)); | ||
} else { | ||
return Array.from(this.el.children); | ||
} | ||
} | ||
|
||
get _name() { | ||
if (this.hasNameValue) { | ||
return this.nameValue; | ||
} else { | ||
throw new Error("SignalEmptyDomController requires a nameValue to be provided"); | ||
} | ||
} | ||
|
||
connect() { | ||
useEventBus(this, signalConnectEvent(this._name), this.emitChildCount); | ||
EventBus.emit(signalConnectEvent(this._name)); | ||
useMutationObserver(this, this.el, this.mutate, {childList: true}); | ||
this.emitChildCount(); | ||
} | ||
|
||
mutate(entries: MutationRecord[]) { | ||
this.emitChildCount(); | ||
} | ||
|
||
emitChildCount() { | ||
let childCount = this._children.length; | ||
EventBus.emit(signalValueEvent(this._name), {element: this.el, value: childCount.toString()} as SignalPayload); | ||
} | ||
|
||
} |