了解如何将 Turbo 与 Ruby on Rails 配合使用

Understanding how to use Turbo with Ruby on Rails

提问人:djFuz 提问时间:4/9/2023 更新时间:4/11/2023 访问量:264

问:

我正在尝试了解如何使用 Turbo 以便在无需刷新页面的情况下刷新页面内容。我已经浏览了一些视频和网络教程,但它不适用于我的页面设置方式,我试图弄清楚我的设置是否设计不佳,我应该更改它,或者是否可以将 Turbo 功能添加到我的代码中。我是 Ruby on Rails 和 Turbo 的新手,所以可能还没有足够的理解来点击它。从本质上讲,我的索引页面上有一个表单,允许用户选择该州,提交后,显示一个新的选择框,其中包含该州的城市以及该州所有设施的列表。用户选择城市并提交表单后,设施列表将仅限于该城市。表单将提交回索引页。我想通过设施部分更新城市选择框和表格,该部分根据表单提交在索引页面上显示设施,但从我看到的每个教程来看,表单都会提交到不同的函数,该函数会调用一个新页面来替换当前部分。有没有办法从我当前的代码中做到这一点,或者我是否需要让表单调用不同的函数?

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" %> &nbsp;
              <%= 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" %> &nbsp;
                <%= 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>&nbsp;</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>

Facilities_Controller.rb

  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 视频和教程,但它们指的是调用不同函数和视图的表单,然后用相同的 turbo id 替换当前视图。我希望找到一种方法来使用提交给索引函数的表单来做到这一点。我仍在学习 Turbo 和 AJAX,但根据我对所读内容的理解,Turbo 在幕后使用 AJAX 来实现这一目标?

Ruby-on-Rails AJAX 涡轮增压器

评论

1赞 max 4/9/2023
你要问的是一个古老的问题 - 当用户更新一个选择时,你想在另一个选择中更改可用选项。基本上,您将事件侦听器添加到向后端发送 AJAX 请求的 select,然后使用响应来交换另一个 select 元素中的选项。这很简单 - 如果你真的掌握了基础知识,比如服务器端和客户端之间的区别,如何发送AJAX请求,以及如何呈现JSON响应或呈现HTML块。但你真的需要从头开始,先学习这些。
1赞 max 4/9/2023
MDN 有很多很好的资源来学习 JS。Turbo 可用于此目的,是的,它确实发送 AJAX 请求以及使用 WebSockets。但是,如果你对此完全陌生,它只会增加更多的抽象和复杂性。还有大量的教程(不一定是特定于 Turbo 或 Rails)以及无数质量参差不齐的库来处理这个问题。
0赞 djFuz 4/10/2023
谢谢@max,你是对的,我在这方面是全新的,需要先学习基础。我能够使用我发现的刺激示例来动态更新州和城市选择框。我可能会暂时把它留在这里,直到我对 JS 和 AJAX 有更好的理解。谢谢你的链接。

答:

0赞 Alex 4/10/2023 #1

如果您将其放在涡轮框架中,只要您使用匹配的框架进行响应,提交表单的位置就没有区别。也许这个例子有点复杂,但它显示了你可以做什么。它会自动提交到同一页面,控制器操作是空的,唯一不明显的是针对不同的字段更新了不同的帧:

<%= tag.div data: {controller: "selector"} do %>
  <%= form_with url: "/", data: {selector_target: "form"} do |f| %>
    <%= f.collection_select :state, State.all, :id, :name, {prompt: true},
      {data: {action: "selector#nextFrame", selector_frame_param: "cities"}} %>

    # ^ this will update v this frame

    <%= turbo_frame_tag :cities do %>
      <% if (current_state = State.find_by(id: params[:state])) %>
        <%= f.collection_select :city, current_state.cities, :id, :name, {prompt: true},
          {data: {action: "selector#nextFrame", selector_frame_param: "facilities"}} %>

        # ^ this will update v this frame

        <%= turbo_frame_tag :facilities do %>
          <% if (current_city = current_state.cities.find_by(id: params[:city])) %>
            Facilities for <%= current_city.name %>
            ...

          <% end %>
        <% end %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

请注意,我没有设置选项,因为目标框架之外的字段不会重置。selected

使用 stimulus 动态更新帧目标并提交表单:

bin/rails g stimulus selector
// app/javascript/controllers/selector_controller.js

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["form"];
  nextFrame(event) {
    const form = this.formTarget;
    form.dataset.turboFrame = event.params.frame; // load the next frame
    form.requestSubmit();                         // after submitting the form
  }
}