I am refactoring a Rails controller for turbo
and stimulus
, and trying to understand how to do things in Turbo that the app does currently with JS response templates.
On a things#show
page, I have a button to request “previous versions” of a thing
text record.
Currently, in Rails UJS, this button sends a GET JS request to a things/versions_controller#show
action. The action responds by setting an ivar for the @versions
(containing the text of the versions, pulled from the db), and renders a one-line js template:
# things/versions/show.js.erb
alert('<%= @versions %>');
With Turbo, i get it, the template has to simply render turbo_stream tags and update/append/replace page elements. An alert is outside that scope.
Is there maybe a way to pass the ruby ivar @versions
directly to a Stimulus controller, that could show the js alert? The only way I can imagine it, the Stimulus controller would handle the click, make a fetch request to the things/versions_controller#show
action (which would give an HTML response), and handle the response itself. No Turbo.
I guess I could have the turbo response print a hidden turbo_frame
containing the @versions
in the page, and attach that frame to a stimulus controller that would throw the text in an alert as soon as the frame is connected to the DOM. (?) But that seems overly contrived.
I’m really mostly looking to understand paradigms, to find the most idiomatic way to do things like this, if there is one. Also, I realize alerts are crude, and I could do this using a modal render.
I think you can return javascript like so:
<turbo-stream action="append" target="some_target">
<template>
<script>
alert('<%= @versions %>');
</script>
</template>
</turbo-stream>
If you are submitting a form, then you can have a create or update turbo response that renders many things, not just one.
So for example, create.turbo_stream.erb
could contain:
<%- # with a partial %>
<%= turbo_stream.replace("flash-container", partial: "/layouts/notices") %>
<%- # with embedded html %>
<%= turbo_stream.update("turbo-modal", "<turbo-frame id='turbo-modal'></turbo-frame>".html_safe) %>
<%- # with local variables %>
<%= turbo_stream.update("some-turbo-tag", partial: "path-to-partial", locals: { version: @version }) %>
<%- # remove page sections not needed %>
<%= turbo_stream.remove("some-turbo-tag"}) %>
If its not form, but a link, then to get a full turbo stream response, and not html, just add data-turbo-stream
or `data: { turbo_stream: ”} to your link_to like
<%= link_to "click me, its fun", show_something_path, data: { turbo_stream: true } %>
The benefit in having a stream render is that you can control multiple page turbo frames. One caveat, there’s been a long standing bug with doing this, so make sure you’re using @hotwired/turbo-rails
7.3 or greater.