首页 > 解决方案 > Rails 6级联选择列表

问题描述

在 Rails 6 中,我希望profiles/_form 有两个下拉列表,国家和城市。当我从国家/地区选择一个值时,这应该会改变城市的选择。我希望在不刷新页面的情况下发生这种情况。我的解决方案如下,它适用于新操作,但不适用于编辑操作。这是正确的方法还是我完全错过了惯用的 Rails 6 解决方案?

返回城市选择框的选项标签的路线:

# config/routes.rb
get 'cities_by_country/:id', to: 'profiles#cities_by_country'  

运行的动作

# profiles_controller
  def cities_by_country
    @city_list = Profile::CITY_LIST[params[:id].to_i]
    respond_to do |format|
      format.js { render :cities_by_country}
    end
  end

生成选项标签的js文件

#views/profiles/cities_by_country.js.erb
<%= options_for_select(@city_list) %>

在国家选择标签上附加“更改”事件的 javascript:

# app/javascript/packs/country_cities.js
import Rails from '@rails/ujs';
var country_select, city_select, selected_country;
window.addEventListener("load", () => {
  country_select = document.querySelector("select#profile_country");
  city_select = document.querySelector("select#profile_city");
  country_select.addEventListener('change', (event) => {
    selected_country = country_select.selectedIndex;
    Rails.ajax({
      url: "/cities_by_country/" + selected_country,
      type: "get",
      data: "",
      success: function (data) {
        city_select.innerHTML = data;
       },
      error: function (data) { }
    })
  })
});

标签: javascriptruby-on-rails

解决方案


很抱歉把它堆在你身上,但这真的坏了。

让我们从控制器/路由开始。通过嵌套路由执行此操作的惯用方式 - GET /countries/:country_id/cities. 你也不应该把它硬塞到你的 Profile 模型/ProfilesController 中。

您可以使用以下方式声明路线:

get '/countries/:country_id/cities', to: 'countries/cities#index'

或者通过使用resources块:

resources :countries, only: [] do
  resources :cities, only: [:index], module: :countries
end

控制器像这样:

module Countries
  class CitiesController < ApplicationController
    # GET /countries/:country_id/cities
    def index
      @cities = Profile::CITY_LIST[params[:city_id].to_i] 
    end
  end
end

不确定我是否真的能理解为什么你想在一个根本不应该对此负责的模型中使用一个常量,而不是实际创建 Country 和 City 模型。

JavaScript 最大的问题是它完全非幂等。这一切都在运行,window.addEventListener("load")以便它在初始页面加载时工作,然后当 Turbolinks 用 AJAX 替换页面内容时完全崩溃,因为这些事件处理程序直接附加到元素本身。

要编写适用于 Turbolinks 的 JavaScript,您需要换一种思路。创建幂等处理程序,以在事件冒泡 DOM 时捕获它。

# app/javascript/packs/country_cities.js
import Rails from '@rails/ujs';

document.addEventListener('change', (event) => {
  let input = event.target;
  if (input.matches('#profile_country')) {
    Rails.ajax({
      url: `/cities/${input.value}/country`,
      type: 'get',
      dataType : 'script'
    });
  }
});

如果您想使用js.erb模板,您还需要重写您的视图,以便它转换页面:

// app/views/countries/cities.js.erb
document.getElementById("#profile_city").innerHTML = "<%= j options_for_select(@cities) %>";

但是,如果您想避免让服务器负责客户端转换,您也可以只使用 JSON 并在客户端上创建选项元素。


推荐阅读