I have a stimulus controller that allows users to load a file into the browser and inspect its metadata, so they can decide which parts they want to upload to the server. The metadata is displayed in a complicated template that i want to keep on the server.
So my stimulus controller is using requestjs-rails
to fetch the template from Rails. But it does not render the html response "as is" - it has to populate it with data that is still only on the client.
In my JS, i'm manually creating a DOM template
element with the response, and modifying that template with the data from the client's file. Then, i append the element to the page.
const response = await get(this.get_template_uri,
{ contentType: "text/html" });
if (response.ok) {
const html = await response.text
if (html) {
// the text returned is the raw HTML template
this.addTemplateToDom(item, html)
// extract the EXIF data (async) and then place in the DOM element
this.getExifData(item);
// uses FileReader to load image as base64 async and set the src
this.fileReadImage(item);
}
}
It occurred to me maybe i can do this more economically by requesting a turbo-stream response with requestjs
.
const response = await get(this.get_template_uri,
{
contentType: "text/html",
responseKind: "turbo-stream"
}
But when i tried it i discovered that requestjs already calls renderStreamMessage
on the response, before I have a chance to modify the returned stream with the client data. If i catch the text, modify and append it, it's appended twice.
Is there a way to "intercept" the fetched turbo-stream response and modify it, then call renderStreamMessage
on my modified response, without getting a double render?
Should i create a custom populate
turbo action - can i scope it only for the use of this one stimulus controller? It seems it is possible to intercept the response message before rendering, but the manipulation methods are not available outside this one controller which has already stored the metadata in FileStores for each file. (In other words they would not be available where the custom Turbo action is probably defined.)
Or should I just keep this as an HTML request and append it manually, and not involve Turbo.
It seems to me the advantage of manipulating the template in the response handler is it's safer, because I know exactly what HTML i'm manipulating.
Seems like the only Turbo option is to let Turbo append the blank template and make thee template also a stimulus target. Then i could manipulate the template html on a "target connected" callback. But there could be many of these templates being sent to the page at once, from the user uploading multiple client files.
To know which template I'm manipulating after it's been appended asynchronously, I would have to look up the relevant client data in a FileStore by some identifier in the element, which does not seem fool-proof.
What worked for me was letting requestjs
go ahead and render the blank template response, and then populate it.
To make sure i populate with the right data, I had to add two attributes to the template:
target
data attribute that lets the stimulus controller know when the template has been rendered to the DOM by Turbo. I use the {item}TargetConnected
callback (as suggested by @Alex in the first comment above) to fire the methods that start populating. This happens immediately after render.I was nervous about letting go of the initial unrendered fetch response - i wanted to catch the response programmatically, populate it and then render it - but populating the template after render also works, and most importantly it takes less code.