Turbo response to render javascript alert?
Asked Answered
C

3

2

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.

Corelli answered 4/11, 2023 at 9:1 Comment(0)
L
2

In addition to other answers, you can make a custom Turbo Stream action to make alert action nice and reusable:

// app/javascript/application.js

Turbo.StreamActions.alert = function () {
  alert(this.templateContent.textContent)
};
# config/initializers/turbo_stream_actions.rb

# this is optional but makes it much cleaner
module CustomTurboStreamActions
  def alert text
    action(:alert, "#", text)
  end

  ::Turbo::Streams::TagBuilder.include(self)
end

Use it in your controllers as a response to TURBO_STREAM requests:

respond_to do |format|
  format.turbo_stream do
    # without CustomTurboStreamActions module
    # render turbo_stream: turbo_stream.action(:alert, "#", @versions)

    # with CustomTurboStreamActions
    render turbo_stream: turbo_stream.alert(@versions)
  end
end

https://turbo.hotwired.dev/handbook/streams#custom-actions

Lactary answered 6/11, 2023 at 21:33 Comment(7)
This is elegant! I'm missing stuff like this in the documentation. It's elemental, but trying to imagine how Turbo might do this, I kept thinking "no way". Maybe I'll propose that Hotwire add a "migration FAQ" with examples like this.Corelli
Wish I could vote this up more. Very helpful, for example in hiding a modal after the turbo_stream response has updated the page.Corelli
@Corelli you might want to look into these, if not to use them, at least to see how things are done: github.com/marcoroth/turbo_power github.com/stimulusreflex/stimulus_reflex github.com/stimulusreflex/cable_ready . i don't use any of these, it's a little too much for most of my needs, but I implemented some of the things these guys do for myself and it's very helpful to see the source code.Lactary
for example i totally stole the "morph" action from stimulus_reflex https://mcmap.net/q/1923548/-how-to-use-stimulus-and-turbo_stream-on-items-which-are-often-being-changedLactary
Wow. That stuff is bonkers, VERY useful. Hope I get that far with this 20 y o app!Corelli
Can i calll another TurboStream::Action within the definition of my custom action? Like def init_modal identifier if modal_exists action(:show_modal, "#", identifier) else action(:append, "#" + identifier, something) endCorelli
(too late to edit the above) Sorry, I'm going to make that a separate question with a better explanation, after playing with it, if im still stumpedCorelli
R
4

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.

Rectangle answered 4/11, 2023 at 14:55 Comment(0)
B
4

I think you can return javascript like so:

<turbo-stream action="append" target="some_target">
  <template>
    <script>
      alert('<%= @versions %>');
    </script>
  </template>
</turbo-stream>
Bullshit answered 4/11, 2023 at 15:9 Comment(1)
it worked for me without the <template> tagTshombe
L
2

In addition to other answers, you can make a custom Turbo Stream action to make alert action nice and reusable:

// app/javascript/application.js

Turbo.StreamActions.alert = function () {
  alert(this.templateContent.textContent)
};
# config/initializers/turbo_stream_actions.rb

# this is optional but makes it much cleaner
module CustomTurboStreamActions
  def alert text
    action(:alert, "#", text)
  end

  ::Turbo::Streams::TagBuilder.include(self)
end

Use it in your controllers as a response to TURBO_STREAM requests:

respond_to do |format|
  format.turbo_stream do
    # without CustomTurboStreamActions module
    # render turbo_stream: turbo_stream.action(:alert, "#", @versions)

    # with CustomTurboStreamActions
    render turbo_stream: turbo_stream.alert(@versions)
  end
end

https://turbo.hotwired.dev/handbook/streams#custom-actions

Lactary answered 6/11, 2023 at 21:33 Comment(7)
This is elegant! I'm missing stuff like this in the documentation. It's elemental, but trying to imagine how Turbo might do this, I kept thinking "no way". Maybe I'll propose that Hotwire add a "migration FAQ" with examples like this.Corelli
Wish I could vote this up more. Very helpful, for example in hiding a modal after the turbo_stream response has updated the page.Corelli
@Corelli you might want to look into these, if not to use them, at least to see how things are done: github.com/marcoroth/turbo_power github.com/stimulusreflex/stimulus_reflex github.com/stimulusreflex/cable_ready . i don't use any of these, it's a little too much for most of my needs, but I implemented some of the things these guys do for myself and it's very helpful to see the source code.Lactary
for example i totally stole the "morph" action from stimulus_reflex https://mcmap.net/q/1923548/-how-to-use-stimulus-and-turbo_stream-on-items-which-are-often-being-changedLactary
Wow. That stuff is bonkers, VERY useful. Hope I get that far with this 20 y o app!Corelli
Can i calll another TurboStream::Action within the definition of my custom action? Like def init_modal identifier if modal_exists action(:show_modal, "#", identifier) else action(:append, "#" + identifier, something) endCorelli
(too late to edit the above) Sorry, I'm going to make that a separate question with a better explanation, after playing with it, if im still stumpedCorelli

© 2022 - 2024 — McMap. All rights reserved.