hotwired / turbo-rails

Use Turbo in your Ruby on Rails app
https://turbo.hotwired.dev
MIT License
2.07k stars 320 forks source link

Allowing usage of layout for turbo stream rendering #178

Closed rodrigoruas closed 2 years ago

rodrigoruas commented 3 years ago

Hello,

It seems currently not possible to use layouts for turbostream views rendering and it may be a nice addition to deal with cases like flash updates.

Example: application.turbo_stream.erb

<turbo-stream action="append" target="flash">
  <template>
    <%= render partial: 'layouts/flash' %>
  </template>
</turbo-stream>
<%= yield %>

To make the layout above works, we had to force it:

respond_to do |format|
      format.turbo_stream do
        render layout: 'application'
      end
   end

As @jduff pointed out to me, layout is always false when it's a turbo_stream request:

https://github.com/hotwired/turbo-rails/blob/2945dcb7c5fc634ad7d459e0872cf04cf8a3576d/app/controllers/turbo/frames/frame_request.rb#L16

For turbostream inline rendering we are using the following approach:

def turbo_flash
    turbo_stream.append('flash', partial: 'layouts/flash')
 end
render turbo_stream: turbo_flash + turbo_stream.update(.....)

As DHH suggested, we shouldn't do multiple inline turbostreams and we are looking for some better alternatives like the first example above. Do you think forcing the layout is the correct approach or we can have another option? Thanks!

swanson commented 2 years ago

I ran into this today as well. It is especially confusing since you can respond with a turbo-stream from outside a frame and it will use the application layout. But if you move the request into a frame, suddenly the flash messages stop working.

WriterZephos commented 2 years ago

I managed this by rendering the layout as the template and passing in a local value which is the actual template I wanted to render, and the layout rendered that internally where it normally would yield.

So render a layout where instead of

<%= yield %>

you have

<%= render template %>

and pass the template var as a local (or you can use @template and set it in the controller).

I agree there should be an easier way though.

Dreikantbrot commented 2 years ago

I was able to achieve this by setting layout -> { nil if turbo_frame_request? } in my ApplicationController. It's a quick-fire way to enable this by default and seems to work so far (at least for my project).

WriterZephos commented 2 years ago

@Octobread @swanson @rodrigoruas I created a plugin that is relevant and makes certain things easy to use: https://github.com/WriterZephos/turbo-router

alextakitani commented 2 years ago

I really think that having to manually set flash messages when using Hotwire is a step back from what we had with "regular" Rails.

Using the layout brings it back, why this is not the default behavior?

Does this cause any problems?

dhh commented 2 years ago

You can use layouts with frames by overwriting the layout prescription set in the FrameRequest concern: https://github.com/hotwired/turbo-rails/blob/main/app/controllers/turbo/frames/frame_request.rb#L16.

You can add layout -> { "framed" if turbo_frame_request? }, for example, instead.

Would be happy to take a doc PR to explain this.