2

I ran the following in the command line:

> rails new test-turbo
> rails g controller Home index

Added the following route to config/routes.rb:

resources :home, only: [:index]

Wrapped the <%= yield %> in app/views/layouts/application.html.erb in a turbo frame tag:

  <body>
    <%= turbo_frame_tag :foobar do %>
      <%= yield %>
    <% end %>
  </body>

Added a link in app/views/home/index.html.erb:

<%= link_to "Link to this page", home_index_path %>

Then, when clicking on that link, I get a turbo frame error: content missing

If I move the turbo frame tag into the home/index page, it works fine:

app/views/home/index.html.erb

<%= turbo_frame_tag :foobar do %>
  <h1>Home#index</h1>
  <p>Find me in app/views/home/index.html.erb</p>
  <%= link_to "Link to this page", home_index_path %>
<% end %>

no error

Why does this work, but putting it in app/views/layouts/application.html.erb doesn't work? It's generating the same HTML at the end of the day, right?

In case its helpful, here's the GitHub repo. Commit 537c40a has the error, commit b42ca66 works fine.

Update: here's a gif displaying a solution that works with two turbo frames, one that is persistent across pages: top bar

a-lavis
  • 33
  • 4

2 Answers2

2

When navigating in a frame your application layout isn't rendered. Which you can see from the logs:

Rendered home/index.html.erb within layouts/turbo_rails/frame (Duration: 0.1ms | Allocations: 38)

You're looking at the inspector and <turbo-frame id="foobar">, when you click the link, current page html is not replaced with the response html, that's really the whole point of a frame. Only content inside the frame is updated. You can see the actual response from the network tab:

No turbo frame in the response ^

To render your layout, you can explicitly set it in the controller to override turbo frame's layout:

layout "application"

Just FYI, there is very little need for turbo frame in the layout, especially, one that wraps around everything. <body> element already acts like a frame, and another frame directly under it doesn't really gain you anything.


Update

Maybe having a whole controller is bit too much. I'd start with something simple. The simplest way is to always render application layout. Skipping layout rendering is only an optimization to speed things up, anything outside of the frame gets discarded anyway.

You can add some if..else to the layout:

...
<body>
  <% unless request.headers["Turbo-Frame"] == "main_content" %>
    <%= turbo_frame_tag :side_bar do %>
      # TODO: sidebar
    <% end %>
  <% end %>

  <%= turbo_frame_tag :main_content do %>
    <%= yield %>
  <% end %>
</body>
Alex
  • 16,409
  • 6
  • 40
  • 56
  • Thank you @alex for the detailed answer, especially those links to the `turbo-rails` code! What if I'm doing more in the `application` layout, for example rendering a sidebar that also uses turbo. I want this sidebar to be on every page, but I don't want Turbo to re-render the sidebar when re-rendering the main content, and vice-versa. What would be a good way to go about that? See [here](https://github.com/a-lavis/test-turbo/commit/09676e4869ee88e6443f6be66d9c86d18ef1577c#diff-f43fe075643e681b2c01c2f853bb0c4299d135b47fcbd4da96890d521c49e3ebR13-R21) for an example. – a-lavis Apr 03 '23 at 13:19
  • Update: I found a way to do this, but I wonder if anyone knows of a better way to do it. See the update in the main question. – a-lavis Apr 03 '23 at 17:26
  • 1
    @a-lavis see update. i'd say try to keep it simple, just render the `application` layout, you can optimize later and render just the part you need. – Alex Apr 03 '23 at 17:53
0

Another workaround is adding

  layout -> { 'turbo_frame' if turbo_frame_request? }

to ApplicationController and then creating app/views/layouts/turbo_frame.html.slim with

= turbo_frame_tag :foobar
  = yield
Reed G. Law
  • 3,897
  • 1
  • 41
  • 78