ruby-on-railsturboturbo-rails

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


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


Solution

  • 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>