javascript - 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) { }
})
})
});
解决方案
很抱歉把它堆在你身上,但这真的坏了。
让我们从控制器/路由开始。通过嵌套路由执行此操作的惯用方式 - 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 并在客户端上创建选项元素。
推荐阅读
- node.js - 从 Node 获取包含二进制文件的对象数组
- azure-devops - Azure devops 管道格式不正确的 cron 语法
- c# - dotnet core IdentityModel 使用范围保护端点
- django - 无法看到 chart.js 中的线条
- python - 如何通过 Java 中的 GET 请求按日期过滤 JSON 输入
- amazon-web-services - 是否可以使用 AWS KMS 进行密钥管理,但将密钥保存在内存中以在本地加密/解密(无需进一步的 api 调用)?
- javascript - 为几个对象制作灵活的文本
- javascript - 第二次触发onChange,自定义组件不更新
- python - 为 sklearn 的 SVC 使用自定义 rbf 内核函数比内置方法快得多
- c++ - 我应该在 C++ 中创建一个返回退出状态的函数的类型