Closed ernsheong closed 6 years ago
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.
as of today
export default class extends Controller {
static targets = [ "thing" ]
thingTargetConnected(event) {
console.log(event.target) // the element that was just connected
}
}
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