Hey all, wondering if anyone could give me a sanity check on an approach I’ve been spiking out.
Context: Rails/Turbolinks enabled
Situation: when a user clicks on a button, I want to show a modal that renders content.
I found two approaches that I wasn’t really happy with:
- Render a hidden (with css) modal in my Rails templates directly, create a Stimulus controller to show/hide when a button fires an action – I dislike this approach because I don’t like shoving extra stuff into the DOM, imagine if you had 3-4 different modals you might need to show from a given page, maybe the modal needs to make different/extra queries to get content
- Try using some third-party javascript library (bootstrap-modal, tingle, etc) and wrap it with Stimulus – seems overkill (I don’t really need a whole library for a modal) and I don’t know of an easy way to pass in the modal content
A third approach is one I tried and wanted some input on: an injector controller.
Globally register the whole body for this controller and have the button trigger an action and store some arguments in dataset
<body data-controller="injector">
...
<button data-action="injector#inject" data-component="modal" data-url="/posts/new" />
</body>
Then the injector controller manages injecting components into the DOM – these components can and probably should be other Stimulus components
import { Controller } from "stimulus";
export default class extends Controller {
connect() {
this.injectedComponents = [];
document.addEventListener(
"turbolinks:before-cache",
() => {
this.teardown();
},
{ once: true }
);
}
inject(event) {
const data = event.currentTarget.dataset;
const node = document.createElement("div");
switch (data.component) {
case "modal":
node.innerHTML = `
<div class="modal">
<div class="modal-content">
<div data-controller="content-loader"
data-content-loader-url="${data.url}"></div>
</div>
</div>
`;
document.body.append(node);
this.injectedComponents.push(node);
break;
// repeat for other 'dynamic' components as necessary?
default:
throw ("Unknown injector component: ", data.component);
}
}
teardown() {
this.injectedComponents.forEach(i => {
document.body.removeChild(i);
});
}
}
(Note: content-loader
is from the docs: https://stimulusjs.org/handbook/working-with-external-resources)
Any feedback? Too much indirection? Too much magic? Am I not thinking in the “Stimulus mindset” and doing something really dumb?
Thanks!