I’ve successfully implemented a search form (i.e. a text input field) that changes the src
attribute value of a <turbo-frame>
element (which one automatically fetches and renders data immediately after the change). The search result rendered in the <turbo-frame>
is a <ul><li>
list.
Now, I want to add a custom attribute (i.e. setAttribute()) to the <li>
list elements each time after the search result is loaded/rendered.
I tried to use addEventListener() (on Stimulus controller, document and window with Turbo events and non-Turbo events), the built-in Stimulus function afterLoad and some other weird things (e.g. MutationObserver) without success.
How to modify the content present in the <turbo-frame>
after it is freshly loaded/rendered?
Here is a Stimulus controller code example using event listeners:
// app/javascript/controllers/search_controller.js
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="search"
export default class extends Controller {
static targets = ["field", "resultTurboFrame", "item"];
connect() {
// These event listeners do not fire at all:
this.resultTurboFrameTarget.addEventListener("turbo:frame-load", this.resultHandler);
document.addEventListener("turbo:frame-load", this.resultHandler);
window.addEventListener("turbo:frame-load", this.resultHandler);
this.resultTurboFrameTarget.addEventListener("turbo:frame-render", this.resultHandler);
document.addEventListener("turbo:frame-render", this.resultHandler);
window.addEventListener("turbo:frame-render", this.resultHandler);
this.resultTurboFrameTarget.addEventListener("turbo:before-frame-render", this.resultHandler);
document.addEventListener("turbo:before-frame-render", this.resultHandler);
window.addEventListener("turbo:before-frame-render", this.resultHandler);
this.resultTurboFrameTarget.addEventListener("turbo:before-render", this.resultHandler);
document.addEventListener("turbo:before-render", this.resultHandler);
window.addEventListener("turbo:before-render", this.resultHandler);
// These event listeners fire but I cannot modify the fresh
// loaded/rendered content because event.target.querySelectorAll("li")
// returns an empty NodeList which length is 0 i.e. <li> elements are not recognized
this.resultTurboFrameTarget.addEventListener("turbo:before-fetch-response", this.resultHandler);
document.addEventListener("turbo:before-fetch-response", this.resultHandler);
window.addEventListener("turbo:before-fetch-response", this.resultHandler);
this.resultTurboFrameTarget.addEventListener("turbo:before-fetch-request", this.resultHandler);
document.addEventListener("turbo:before-fetch-request", this.resultHandler);
window.addEventListener("turbo:before-fetch-request", this.resultHandler);
this.resultTurboFrameTarget.addEventListener("turbo:before-stream-render", this.resultHandler);
document.addEventListener("turbo:before-stream-render", this.resultHandler);
window.addEventListener("turbo:before-stream-render", this.resultHandler);
// Other non-working attempts that fire but I cannot modify the fresh
// for the above-mentioned reasons.
this.boundedResultHandler = this.resultTurboFrameHandler.bind(this)
this.searchResultFrameTarget.addEventListener("turbo:before-fetch-response", this.boundedResultHandler);
document.addEventListener("turbo:before-fetch-response", this.boundedResultHandler);
window.addEventListener("turbo:before-fetch-response", this.boundedResultHandler);
}
disconnect() {
this.resultTarget.removeEventListener("turbo:frame-render", this.resultHandler);
// ...
}
resultHandler(event) {
console.log("Should modify content after it is freshly loaded/rendered, but doesn't work");
event.target.querySelectorAll("li").forEach((li) => {
li.setAttribute("loaded", true); // <li loaded="true"></li>
});
}
}
The .erb file returned by the search request is like this (note: actually, if this can be the problem but I think it’s not, the response to the search fetch request is a <turbo-stream><template><ul><li>...</template></turbo-stream>
which properly loads/renders the content):
<turbo-stream action="update" target="search_turbo_frame_id"><template>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item ...</li>
</ul>
</template></turbo-stream>