首页 > 解决方案 > 带有 jQ​​uery 的 Rails 中的动态下拉(选择框)菜单不可逆

问题描述

在由 Rails 6.1 控制的网站表单上,我想实现一个“动态”或级联下拉菜单,以便第二个下拉菜单中的选项根据第一个下拉菜单中的所选项目而有所不同。

具体来说,我有一个与Person模型相关的模型。关系是 a a , which a in 。方法和定义。在网站上的新表单中,用户首先从下拉菜单(选择框)中选择一个国家,然后在第二个下拉菜单中选择一个城镇。CountryTownPerson belongs_toTownbelongs_toCountryhas_manyCountry#nameTown#namecreatePerson

我基本上遵循了#88 Dynamic Select Menus (revised)的过程,但是用标准的jQuery 重写了它,而不是Railcasts 中的Coffee。

简而言之,我使用 Rail 的表单为城镇创建了一个选择框grouped_collection_select辅助方法;生成的 HTML 中的部分包含很多OPTGROUP,每个对应一个国家有多个子城镇 belongs_to。关联的 jQuery 脚本过滤第二个(即Town)下拉菜单,比较Country下拉菜单中的选定项目和Town下拉菜单(选择框)LABEL中的每个OPTGROUP项目。

它有点工作,但有一个严重的缺陷。基本上,它在第一次点击时起作用。但是,一旦用户改变主意并重新选择不同的国家/地区,城镇的所有选项就会消失。换句话说,用户的第一选择是不可逆的。那是一个糟糕的界面。

如何解决这个问题,让用户的选择总是可逆的?

下面是表单视图 (hrb.erb) 和 Javascript jQuery 代码中的相关部分。这里,person是模型的一个新实例Person。它使用 Rails 6.1.4、Ruby 3.0.1 和 jQuery 3.5.1 进行了测试。

erb.html 的形式

<%= form_with(model: person, local: true) do |form| %>
  <div class="field">
    <%= form.label 'town_id.country_id', 'Country'%>
    <%= form.collection_select town_id.country_id', Country.all,
        :id, :name, include_blank: true %>
  </div>

  <div class="field">
    <%= form.label 'place.town_id' %>
    <%= form.grouped_collection_select 'place.town_id', Country.all,
        :towns, :name, :id, :name, include_blank: true %>
  </div>
<% end %>

Javascript jQuery :

 var contsel = "#"+$.escapeSelector('person_place.town_id.country_id');
 $(contsel).change(function(){
   var prefsel = "#"+$.escapeSelector('person_place.town_id');
   var contsel = "#"+$.escapeSelector('person_place.town_id.country_id');
   var country = $.escapeSelector($(contsel+' :selected').text());
   var towns = $(prefsel).html();
   var options = $(towns).filter("optgroup[label='"+country+"']").html();
   if (options) {
     $(prefsel).html(options);
   } else {
     $(prefsel).empty();
   }
 })

标签: javascriptjqueryruby-on-railsformsdrop-down-menu

解决方案


有很多方法可以使用 Web 应用程序框架来实现动态下拉菜单。至于使用 Rails 的方式,这个问题的答案似乎总结得特别好。

我认为 OP 基于OPTGROUP标签使用 jQuery 的方式具有优势,如果以任何方式并非没有缺点,

  1. Rails 端编码最少,
  2. 用户即使禁用 Javascript 也可以制定(并提交)有意义的选择(好吧,谁会?)。

OP 的 Javascript 代码使生成的 HTML 不可逆,因为它使用该filter()方法破坏性地修改 HTML。change()在用户选择提示的第一个动作中,HTML 被不可逆地修改。例如,一旦选择了 Country=Australia ,就 Javascript 代码而言,除澳大利亚以外的所有城镇的内容都将被删除。因此,由于任何后续操作都将基于修改后的 HTML,因此 Javascript 代码对任何其他国家/地区的城镇一无所知。

下面的 Javascript jQuery 代码应该可以解决这个问题,使用户的选择是可逆的。它基于 OP 的示例代码,前半部分是相同的,只是增加了两行。

$(function(){
 var contsel = "#" + $.escapeSelector('person_place.town_id.country_id');
 var townsel = "#" + $.escapeSelector('person_place.town_id');
 $(townsel).parent().hide();
 $(contsel).change(function() {
   var townsel = "#" + $.escapeSelector('person_place.town_id');
   var contsel = "#" + $.escapeSelector('person_place.town_id.country_id');
   var country = $.escapeSelector($(contsel + ' :selected').text());
   var towns = $(townsel).html();
   var options = $(towns).filter("optgroup[label='" + country + "']").html();
   if (options) {
     $(townsel).parent().show();
     $(townsel).show();
     $(townsel + " optgroup").hide();
     $(townsel + " optgroup[label='" + country + "']").show();
     $(townsel).find('option:selected').prop("selected", false);
   } else {
     $(townsel).hide();
     $(townsel).parent().hide();
   }
 })
})

这里有几点需要注意:

  1. 主要区别在于,这将hideshow,取决于用户的选择,并且永远不会删除或修改 HTML 中的任何其他部分。
  2. 为了使动作可逆,这明确的show部分,可能已被隐藏。
  3. 当所选国家/地区不包含城镇或未选择国家/地区时,城镇的整个选择框将被隐藏。
  4. change()发生时,即当用户选择一个新的国家时,用户先前的选择,如果存在,则被清除。如果没有这个,从技术上讲,用户可以使用相互矛盾的组合(例如 Country=Australia 和 Town=Nairobi)提交表单数据。

这是用于试验的示例代码的jsfiddle。完整的代码也可以在 Github Gist上找到。


推荐阅读