ruby-on-railsturborequestjs

Modify turbo-stream response on client before calling renderStreamMessage


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.


Solution

  • 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:

    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.