I am looking for a way to listen to events for addition or removal of controller targets.
Use case: When new targets appear in the DOM, I want to trigger an action/DOM manipulation.
Why: My controller sits at the top level (document body). I suppose the recommended use case is to scope it to a specific DOM subtree, but in this case it is meant to manage DOM everywhere so it resides in the body
My approach would be to create a controller for this specific targets and let them emits an event in connect.
Also, you can pass any required data in CustomEvent.
Yes, but event emitted in connect() is only the first time, what if more targets are added later on, just targets mind you, no new controllers.
What I was saying in other words - let targets emit events.
Thanks for the suggestion! This is something that's on our radar.
For now, I would echo @skyksandr's recommendation to add a new data-controller annotation to each target element you want to observe. Those elements can still serve as targets for your outer controller. For example:
<body data-controller="outer">
...
<!-- Some newly inserted targets: -->
<div data-target="outer.thing" data-controller="inner">1...</div>
<div data-target="outer.thing" data-controller="inner">2...</div>
</body>
Each target has its own inner controller, which emits an inner-connected event when connected:
// inner_controller.js
export default class extends Controller {
connect() {
const event = document.createEvent("CustomEvent")
event.initCustomEvent("inner-connected", true, true, null)
this.element.dispatchEvent(event)
}
}
Add a data-action to the body element to call the thingConnected() method in response to those events:
<body data-controller="outer" data-action="inner-connected->outer#thingConnected">
...
// outer_controller.js
export default class extends Controller {
static targets = [ "thing" ]
thingConnected(event) {
console.log(event.target) // the element that was just connected
}
}
Closing this issue for now, but please feel free to continue discussing it over on the community forum.
@sstephenson I really like the above approach, and feel that its a very valuable Stimulus pattern. Perhaps it could become part of the official documentation? There are many places where custom events make a lot of sense.
Or perhaps it could be considered for inclusion into the core library, on the base Controller class?
protected emit(evtType: string, evtData?: object) {
let evt;
if (typeof CustomEvent === 'function') {
// Modern browsers
evt = new CustomEvent(evtType, {
bubbles: true,
detail: evtData,
});
} else {
// IE
evt = document.createEvent('CustomEvent');
evt.initCustomEvent(evtType, true, false, evtData);
}
this.element.dispatchEvent(evt);
}
I found that the outer listener doesn't catch the event if it's fired as part of the connect() function of the inner. The event gets caught just fine later on though.
This is why it wasn't catching the event. Wrapping the dispatch in a promise fixed it. Feels dirty :)
https://github.com/stimulusjs/stimulus/issues/201#issuecomment-435285227
This (https://github.com/stimulusjs/stimulus/issues/222) fixed my particular problem and I was able to remove the resolved promise.
Most helpful comment
Thanks for the suggestion! This is something that's on our radar.
For now, I would echo @skyksandr's recommendation to add a new
data-controllerannotation to each target element you want to observe. Those elements can still serve as targets for your outer controller. For example:Each target has its own
innercontroller, which emits aninner-connectedevent when connected:Add a
data-actionto the body element to call thethingConnected()method in response to those events:Closing this issue for now, but please feel free to continue discussing it over on the community forum.