Turbo-Rails: How can I add add a Turbo Frame to `application.html.erb`?

1.1k Views Asked by At

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

3

There are 3 best solutions below

3
Alex On BEST ANSWER

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>
0
Reed G. Law On

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
0
kimemmanuel On

In case you were trying to keep layout stuff (sidebar, footer etc.) from rerendering, you could have used data-turbo-permanent.

Permanent elements persist across page loads.

Then you wouldn´t have to wrap your main content in a turbo frame.

See documentation.