I would put a sidebar controller on the body tag.
As long as the sidebar controller is not reused it’s totally fine. It wouldn’t work for a generic toggler controller.
<body data-controller="sidebar">
<header class="sm:hidden">
<button data-action="sidebar#toggle">Toggle sidebar</button>
</header>
<section data-sidebar-target="sidebar">
Some content in the sidebar
</section>
<main>
<button class="hidden sm:inline-block" data-action="sidebar#toggle">Toggle sidebar</button>
</main>
</body>
I would recommend avoiding controllers with too much scope where practical. Having a large section of the DOM with multiple controllers on it could risk some performance issues.
An alternative approach to having a sidebar
& sidebar-toggle
controller is to have a generic ‘fire an event’ controller.
We can even take inspiration from Alpine.js and its x-on
directive. We could have a smaller scoped ‘on’ action do something type controller that let’s us have a generic way to trigger any event.
HTML
Let’s start with the HTML, similar to the previous answer, we have a sidebar div and then two buttons that do basically the same thing (trigger an event).
We can leverage Stimulus’ approach to coordinating with DOM events to help us here but this time with a custom global event with the name 'sidebar:toggle'
.
Note that the sidebar controller listens to the event with @window
to ensure that it picks up any ‘bubbling’ events all the way up the DOM.
https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events
<body>
<header class="sm:hidden">
<button
data-controller="on"
data-action="click->on#go"
data-on-event-name-param="sidebar:toggle"
>
Toggle sidebar
</button>
</header>
<section data-controller="sidebar" data-action="sidebar-toggle@window->sidebar#layout">
Some content in the sidebar
</section>
<main>
<button
class="hidden sm:inline-block"
data-controller="on"
data-action="click->on#go"
data-on-event-name-param="sidebar:toggle"
>
Toggle sidebar
</button>
</main>
</body>
JavaScript (Controllers)
The Sidebar
controller will have whatever you want but also a method for toggle
that we can trigger with the action.
import { Controller } from '@hotwired/stimulus';
class Sidebar extends Controller {
static targets = [];
static values = { expanded: { default: false, type: Boolean } };
connect() {
// do the things
}
toggle() {
this.expandedValue = !this.expandedValue;
}
}
The On
controller leverages Stimulus action parameters to be able to receive any dynamic value set on the DOM as it’s event.
Note that the On
controller intentionally makes the events bubble (this is the default, but good to be explicit) so that any other event listeners on window can listen to this.
import { Controller } from '@hotwired/stimulus';
class On extends Controller {
go({ params: { eventName } }) {
this.dispatch(eventName, { prefix: '', bubbles: true, cancelable: false });
}
}
Say, we have a Stimulus controller that toggles a sidebar. The button that triggers the toggle action is located in different places, though, depending on the device. E.g. in the header when you are on a mobile device and in the main navigation when you are on a Desktop device.
What do you do in this situation? Is it better to initialise 2 Stimulus controllers (one in the div that belongs to the header and one that belongs to the main navigation) or to initialise just one Stimulus controller, for example in a wrapper div tag that encloses both the header as well as the main navigation?