diff --git a/app/controllers/turbo/frames/frame_request.rb b/app/controllers/turbo/frames/frame_request.rb
index 11ca08eb..e9399e63 100644
--- a/app/controllers/turbo/frames/frame_request.rb
+++ b/app/controllers/turbo/frames/frame_request.rb
@@ -1,19 +1,27 @@
# Turbo frame requests are requests made from within a turbo frame with the intention of replacing the content of just
# that frame, not the whole page. They are automatically tagged as such by the Turbo Frame JavaScript, which adds a
-# Turbo-Frame header to the request. When that header is detected by the controller, we ensure that any
-# template layout is skipped (since we're only working on an in-page frame, thus can skip the weight of the layout), and
-# that the etag for the page is changed (such that a cache for a layout-less request isn't served on a normal request
-# and vice versa).
+# Turbo-Frame header to the request.
#
-# This is merely a rendering optimization. Everything would still work just fine if we rendered everything including the layout.
-# Turbo Frames knows how to fish out the relevant frame regardless.
+# When that header is detected by the controller, we substitute our own minimal layout in place of the
+# application-supplied layout (since we're only working on an in-page frame, thus can skip the weight of the layout). We
+# use a minimal layout, rather than avoid the layout entirely, so that it's still possible to render content into the
+# head.
+#
+# Accordingly, we ensure that the etag for the page is changed, such that a cache for a minimal-layout request isn't
+# served on a normal request and vice versa.
+#
+# This is merely a rendering optimization. Everything would still work just fine if we rendered everything including the
+# full layout. Turbo Frames knows how to fish out the relevant frame regardless.
+#
+# The layout used is turbo_rails/frame.html.erb. If there's a need to customize this layout, an application can
+# supply its own (such as app/views/layouts/turbo_rails/frame.html.erb) which will be used instead.
#
# This module is automatically included in ActionController::Base.
module Turbo::Frames::FrameRequest
extend ActiveSupport::Concern
included do
- layout -> { false if turbo_frame_request? }
+ layout -> { "turbo_rails/frame" if turbo_frame_request? }
etag { :frame if turbo_frame_request? }
end
diff --git a/app/views/layouts/turbo_rails/frame.html.erb b/app/views/layouts/turbo_rails/frame.html.erb
new file mode 100644
index 00000000..0171671e
--- /dev/null
+++ b/app/views/layouts/turbo_rails/frame.html.erb
@@ -0,0 +1,8 @@
+
+
+ <%= yield :head %>
+
+
+ <%= yield %>
+
+
diff --git a/test/dummy/app/views/trays/show.html.erb b/test/dummy/app/views/trays/show.html.erb
index 125250cb..9ab284b8 100644
--- a/test/dummy/app/views/trays/show.html.erb
+++ b/test/dummy/app/views/trays/show.html.erb
@@ -1,3 +1,7 @@
+<% content_for :head do %>
+
+<% end %>
+
This is a tray!
<%= @frame_id %>
diff --git a/test/frames/frame_request_controller_test.rb b/test/frames/frame_request_controller_test.rb
index 3ba6fa83..d3c8d0fa 100644
--- a/test/frames/frame_request_controller_test.rb
+++ b/test/frames/frame_request_controller_test.rb
@@ -1,7 +1,7 @@
require "test_helper"
class Turbo::FrameRequestControllerTest < ActionDispatch::IntegrationTest
- test "frame requests are rendered without a layout" do
+ test "frame requests are rendered with a minimal layout" do
get tray_path(id: 1)
assert_select "title", count: 1
@@ -9,6 +9,22 @@ class Turbo::FrameRequestControllerTest < ActionDispatch::IntegrationTest
assert_select "title", count: 0
end
+ test "frame request layout includes `head` content" do
+ get tray_path(id: 1), headers: { "Turbo-Frame" => "true" }
+
+ assert_select "head", count: 1
+ assert_select "meta[name=test][content=present]"
+ end
+
+ test "frame request layout can be overridden" do
+ with_prepended_view_path "test/frames/views" do
+ get tray_path(id: 1), headers: { "Turbo-Frame" => "true" }
+ end
+
+ assert_select "meta[name=test][content=present]"
+ assert_select "meta[name=alternative][content=present]"
+ end
+
test "frame requests get a unique etag" do
get tray_path(id: 1)
etag_without_frame = @response.headers["ETag"]
@@ -28,4 +44,13 @@ class Turbo::FrameRequestControllerTest < ActionDispatch::IntegrationTest
get tray_path(id: 1), headers: { "Turbo-Frame" => turbo_frame_request_id }
assert_match /#{turbo_frame_request_id}/, @response.body
end
+
+ private
+ def with_prepended_view_path(path, &block)
+ previous_view_paths = ApplicationController.view_paths
+ ApplicationController.prepend_view_path path
+ yield
+ ensure
+ ApplicationController.view_paths = previous_view_paths
+ end
end
diff --git a/test/frames/views/layouts/turbo_rails/frame.html.erb b/test/frames/views/layouts/turbo_rails/frame.html.erb
new file mode 100644
index 00000000..0fbebe4b
--- /dev/null
+++ b/test/frames/views/layouts/turbo_rails/frame.html.erb
@@ -0,0 +1,9 @@
+
+
+
+ <%= yield :head %>
+
+
+ <%= yield %>
+
+
\ No newline at end of file