Rails 7 全栈开发:全页使用 Turbo Frame
需求背景
如果说因为某种原因,需要在整个页面地方使用 Turbo Frame,采用这种结构有哪些需要注意的地方呢?本篇文章就是讨论这种情况的。
我给出一个基本布局,这在 Rails 中往往名为 layout/application.html.erb
:
1 | <body> |
一个基本的想法是,当在页面内部请求新的页面时,只要刷新 #container
部分的内容。这时,Turbo Frame 技术就用得上了。如果不考虑 #modal
和 #overlay
的刷新,只需要将 #container
用 Turbo Frame 包裹起来就可以了(layout/application.html.erb
的对应部分修改为):
1 | <body> |
这样 <turbo-frame>
中的链接默认只会刷新 <turbo-frame>
内部的内容。当然,这要求所有可视页面的模板需要包含 <turbo-frame id="pageContent">
部分。这里给个示例(articles/show.html.erb
):
1 | <turbo-frame id="pageContent"> |
遇坑一二三
我先来说第一个注意事项,其实也是一个坑。注意到 application
布局的 <%= yield %>
部分,说明即使是 #pageContent
内部的 Turbo Frame,仍然是有布局的。所以,以上的 articles/show.html.erb
实际上应该写成这样:
1 | <turbo-frame id="pageContent"> |
如果每个页面都这样写就好冗长哦,DRY 哪里去了?第一想法是利用默认的 application
布局,在页面里去掉 <turbo-frame>
。这样 articles/show.html.erb
只用写成这样:
1 | <%= render @article %> |
我们希望是能够用上 application
布局的,但事与愿违,最后页面仅渲染了 articles/show.html.erb
的内容,似乎完全没用上 application
布局。
我的初步猜想是布局内的 Turbo Frame 必须在顶层(这是 Rails 的处理机制,我也不知道是为什么),所以为视图 articles/show.html.erb
另写了一个布局文件(layout/page_content.html.erb
):
1 | <turbo-frame id="pageContent"> |
然后 GET /articles/:id
请求使用布局 page_content
.
基于是否是 Turbo Frame 请求使用动态布局
但这样引来一个问题,当我在浏览器中直接输入 /articles/:id
这样的链接时,它只会加载 page_content
布局的内容。而这样的首次加载需要使用 application
布局才是。所以,我使用了动态布局加以解决:
1 | class ApplicationController |
可以看到,如果是 Turbo Frame 请求,使用 page_content
布局,否则使用 application
布局。完美解决!
Turbo Frame 中支持更新地址栏以及历史跳转记录
还有另一个问题,就是 #pageContent
内点击链接只是一个表单请求,不再更新地址栏了。我们只是用 Turbo Frame 结构解决我们的一个页面布局问题,而并不是把它当作一个局部区域。(我们能够看到,完整的页面显示都在 <turbo-frame id="pageContent">
中)所以,地址栏不更新不符合我们的要求。
解决这个问题只用一招就搞定了,添加一个属性 turbo-action="advance"
即可:
1 | <turbo-frame id="pageContent" data-turbo-action="advance"> |
Turbo Frame 内部不再监听到 turbo:before-visit
事件
如果我们想在 Turbo Frame 内部监听点击链接,监听 turbo:before-visit
事件办不到了。Turbo Frame 的链接点击和表单提交都放到了 turbo:before-fetch-request
事件了。所以,应当监听的事件是 turbo:before-fetch-request
:
1 | addEventListener('turbo:before-fetch-request', listener, false) |