Many frames, one page
In the junior-level course "Hotwire: Turbo Drive & Frames", each frame example had one job on its page: the lesson editor's image section, the discussion thread. Nothing says a page can have only one. This app's admin course list has four independent frames on the same page, one per level, and each paginates only itself.
Four frames, one per level
app/views/admin/catalog/courses/index.html.erb, trimmed: the table inside each frame is replaced by a comment:
<% @result.levels_data.each do |level_name, data| %>
<% pagy = admin_paginator(total_count: data.total_count, page_key: "#{level_name}_page", per_page: @per_page) %>
<%= turbo_frame_tag "level-#{level_name}", data: { turbo_action: "advance" } do %>
<div id="level-<%= level_name %>" class="mb-10 scroll-mt-24">
<!-- table of courses for this one level -->
<% end %>
<% end %>
One each loop over four levels (intern, junior, mid, senior) produces four separate <turbo-frame> elements: level-intern, level-junior, level-mid and level-senior. Each id is built from the level name, and each frame wraps its own paginated table. page_key: "#{level_name}_page" means that clicking "next page" on the intern table doesn't touch the junior table's page number.
Why a dynamic id
Writing turbo_frame_tag "level-intern", turbo_frame_tag "level-junior" and the rest by hand would work the same. The dynamic "level-#{level_name}" inside a loop is just DRY, the same reason you loop over any collection instead of unrolling it. What matters is what the id buys. A pagination link generated inside the level-mid frame targets level-mid by default (frame-scoped targeting, the rule from that junior-level course). So clicking "next page" on the mid-level table swaps only that frame's content. The intern, junior and senior tables next to it don't reload, don't flicker and keep their own pagination state.
What breaks if two frames share an id
A <turbo-frame id="..."> must be unique on the page, like any HTML id. Turbo relies on that in two places. When a response comes back, it copies the content of the first turbo-frame#id it finds in the new HTML. When a link names a frame with data-turbo-frame, Turbo looks it up with document.getElementById, which returns the first element with that id. So if two frames share an id, through a bug in a loop or a copied partial with a hardcoded id, the second frame receives the first one's content, and a link aimed at the second updates the first. Nothing raises. The page just shows the wrong table.
The quoted view has a smaller case of this. Each frame wraps a <div id="level-<%= level_name %>"> with the same id as the frame. That's invalid HTML, but here it breaks nothing. A pagination link inside a frame targets that frame without any id lookup, and the response lookup asks for turbo-frame#level-mid, which skips the div. The per-level ids still matter: four levels never collide with each other, because the level name is part of each id.
Key takeaways
- Nothing limits a page to one turbo frame. This app's admin course index renders four, one per level, from a single loop.
turbo_frame_tag "level-#{level_name}"builds a distinct id on each pass. Each frame's pagination (page_key: "#{level_name}_page") is tracked on its own, so paginating one level's table never touches another's.- The payoff of a per-level id is default targeting: a pagination link rendered inside a level's frame targets that frame and leaves every other frame on the page alone.
- Frame ids must be unique on the page. With two frames sharing an id, Turbo uses the first match, so one frame silently gets the other's content. The inner
<div>in this view repeats its frame's id, which is invalid HTML but harmless here.
Keep going with lesson 2
Breaking out with turbo-frame: _top
How the reserved _top target sends a link or form inside a frame to a full page.
A free account also gives you the quizzes, your progress and notes, and the discussion.