在Ruby on Rails中使用Turbo实现无刷新页面更新的疑问
问题
我想在Ruby on Rails项目里用Turbo实现页面内容无刷新更新。当前我的索引页(index.html.erb)有个表单,提交到index方法:用户选州提交后,会显示对应城市的选择框和该州的设施列表;选城市提交后,设施列表会过滤成该城市的内容。
看过不少Turbo教程,但大多是表单提交到其他方法,通过Turbo ID替换对应区域。我想保留表单提交到index方法的现有逻辑,能不能实现Turbo无刷新更新城市选择框和设施表格?同时想确认Turbo是不是基于AJAX实现这个功能?
现有代码
视图文件(index.html.erb)
<% provide(:title, "Facilities") %> <div class="row"> <div class="col-md-8 col-md-offset-2"> <br> <%= form_with(url: facilities_path, method: :get, remote: true) do |f| %> <table class="table table-borderless"> <tr> <td> <div class="form-inline"> <%= f.label :state_id, "State" %> <%= f.collection_select :state_id, @states, :id, :state_long, {:selected => params[:state_id], include_blank: 'Select A State'}, {class: 'form-control'} %> </div> </td> <% if !params[:state_id].blank? %> <td> <div class="form-inline"> <%= f.label :city_id, "City" %> <%= f.collection_select :city_id, @cities, :id, :city_titlecase, {:selected => params[:city_id], include_blank: 'Select A City'}, {class: 'form-control'} %> </div> </td> <% end %> <td><%= f.submit "Update Search", name: nil, class: "btn btn-primary" %></td> </tr> </table> <% end %> </div> </div> <div class="row"> <div class="col-md-8 col-md-offset-2"> <% if @facilities.any? %> <table class="table table-hover table-striped table-bordered"> <thead> <tr> <th> </th> <th>Facility</th> <th>City</th> <th>State</th> </tr> </thead> <tbody> <%= render @facilities %> </tbody> </table> <span style="float:right"><%= will_paginate %></span> <% end %> </div> </div>
控制器方法(FacilitiesController#index)
def index @states = State.all #Get the state id from the database based on the state long name current_state_db = State.find_by(state_long: current_state) if params[:state_id].blank? @facilities = Facility.all.joins(:state, :city).order("states.state_short, cities.city, facility_name").paginate(page: params[:page]) else @cities = City.where(state_id: params[:state_id]) if params[:city_id].blank? @facilities = Facility.where(state_id: params[:state_id]).joins(:state, :city).order("states.state_short, cities.city, facility_name").paginate(page: params[:page]) else #Check to make sure the city_id is in the selected state -> if the user changed the state a city was selected, it would keep the old city in the params expected_state = City.find_by(id: params[:city_id]).state_id if params[:state_id].to_i == expected_state @facilities = Facility.where(state_id: params[:state_id], city_id: params[:city_id]).joins(:state, :city).order("states.state_short, cities.city, facility_name").paginate(page: params[:page]) else #The current selected city is not in the state @facilities = Facility.where(state_id: params[:state_id]).joins(:state, :city).order("states.state_short, cities.city, facility_name").paginate(page: params[:page]) end end end end
解决方案
Turbo与AJAX的关系
Turbo确实基于AJAX实现无刷新交互,它会自动拦截表单提交、链接点击等操作,通过AJAX请求获取后端响应,再根据规则更新页面指定区域,无需整页刷新。
保留index方法实现无刷新更新
完全能保留表单提交到index方法的逻辑,只需对视图和控制器做少量修改:
- 移除
remote: true
Turbo会自动处理表单的无刷新提交,不需要手动添加remote: true,修改form_with代码:
<%= form_with(url: facilities_path, method: :get) do |f| %>
- 为可更新区域添加标识ID
把表单区域和设施列表区域分别包裹在带唯一ID的容器中,让Turbo能精准定位更新:
- 表单区域(含州、城市选择框)添加ID
facility-search-form - 设施列表区域添加ID
facilities-list
修改后的视图结构如下:
<% provide(:title, "Facilities") %> <div class="row" id="facility-search-form"> <div class="col-md-8 col-md-offset-2"> <br> <%= form_with(url: facilities_path, method: :get) do |f| %> <!-- 原表单内容不变 --> <% end %> </div> </div> <div class="row" id="facilities-list"> <div class="col-md-8 col-md-offset-2"> <!-- 原设施列表内容不变 --> </div> </div>
- 拆分视图为局部模板(优化操作)
为了让Turbo流更新更简洁,把表单和设施列表拆成局部模板:
- 创建
app/views/facilities/_search_form.html.erb,把原表单的form_with代码块移进去 - 创建
app/views/facilities/_facilities_table.html.erb,把原设施列表的代码移进去
然后修改原index.html.erb,用局部模板渲染:
<% provide(:title, "Facilities") %> <div class="row" id="facility-search-form"> <div class="col-md-8 col-md-offset-2"> <br> <%= render 'search_form' %> </div> </div> <div class="row" id="facilities-list"> <div class="col-md-8 col-md-offset-2"> <%= render 'facilities_table' %> </div> </div>
- 控制器返回Turbo流响应
在index方法末尾添加响应处理,针对Turbo请求返回更新指令:
def index # 原有的查询逻辑完全保留,无需改动 respond_to do |format| format.html # 正常浏览器请求返回完整页面 format.turbo_stream do render turbo_stream: [ # 更新表单区域(含城市选择框) turbo_stream.update(:facility_search_form, render('search_form')), # 更新设施列表区域 turbo_stream.update(:facilities_list, render('facilities_table')) ] end end end
额外注意点
- Turbo会自动处理GET表单提交,同步更新浏览器URL,用户可以正常使用前进/后退按钮导航
- 分页功能无需额外改动,Turbo会自动拦截分页链接的点击,更新设施列表区域
内容的提问来源于stack exchange,提问作者djFuz
相关产品推荐
相关产品推荐

