如何在Rails中实现基于模型的联动嵌套下拉菜单
问题原因
你当前的写法错误是因为Site.where(study_id: :id)是ERB在服务端渲染页面阶段就执行的代码,此时用户还未在前端页面操作第一个下拉框,:id只是一个无意义的符号值,根本获取不到用户后续选中的动态ID,所以无法实现联动过滤。
推荐实现方案
根据你的数据量大小,可以选择以下两种方案:
方案1:AJAX异步加载(数据量较大时推荐)
不需要提前加载全量站点数据,选中研究后动态请求对应站点列表,性能更优。
- 修改ERB模板,给两个下拉框增加唯一ID,第二个下拉框先留空占位:
<div class="study-search"> <%= form_with(url: show_subjects_path(:site_id), method: :get, html: {style: 'font-size:16px'}) do |f| %> <%= f.label 'Select Study' %> <%= f.select :id, Study.all.collect {|study| [study.title, study.id] }, {:include_blank => "--Choose--"}, id: "study-select" %> <%= f.label 'Select Site' %> <%= f.select :site_id, [], {:include_blank => "--Choose--"}, id: "site-select" %> <div class="btn btn-sm" style="display: inline"> <%= f.submit "GO" %> </div> <% end %> </div>
- 新增JS代码监听第一个下拉框的选中事件,动态请求站点数据:
document.addEventListener('DOMContentLoaded', () => { const studySelect = document.getElementById('study-select'); const siteSelect = document.getElementById('site-select'); studySelect.addEventListener('change', async () => { const studyId = studySelect.value; // 清空原有选项 siteSelect.innerHTML = '<option value="">--Choose--</option>'; if (!studyId) return; // 请求对应研究的站点列表 const response = await fetch(`/studies/${studyId}/sites`); const sites = await response.json(); // 填充第二个下拉框 sites.forEach(site => { const option = document.createElement('option'); option.value = site.id; option.textContent = site.name; siteSelect.appendChild(option); }); }); });
- 新增接口路由和控制器方法,返回站点JSON数据
在config/routes.rb中添加路由:
resources :studies do get 'sites', on: :member end
在StudiesController中添加对应方法:
def sites @sites = Site.where(study_id: params[:id]) render json: @sites.select(:id, :name) end
方案2:前端预加载过滤(数据量较小时推荐)
无需额外开发接口,提前把全量站点数据注入前端,选中研究后直接本地过滤,实现更简单。
<div class="study-search"> <%= form_with(url: show_subjects_path(:site_id), method: :get, html: {style: 'font-size:16px'}) do |f| %> <%= f.label 'Select Study' %> <%= f.select :id, Study.all.collect {|study| [study.title, study.id] }, {:include_blank => "--Choose--"}, id: "study-select" %> <%= f.label 'Select Site' %> <%= f.select :site_id, [], {:include_blank => "--Choose--"}, id: "site-select" %> <div class="btn btn-sm" style="display: inline"> <%= f.submit "GO" %> </div> <% end %> </div> <script> // 提前按研究ID分组存储所有站点数据 const siteMap = <%= Site.all.group_by(&:study_id).transform_values {|sites| sites.map {|s| {id: s.id, name: s.name} }}.to_json.html_safe %>; document.addEventListener('DOMContentLoaded', () => { const studySelect = document.getElementById('study-select'); const siteSelect = document.getElementById('site-select'); studySelect.addEventListener('change', () => { const studyId = studySelect.value; siteSelect.innerHTML = '<option value="">--Choose--</option>'; if (!studyId || !siteMap[studyId]) return; // 直接用预加载数据填充下拉框 siteMap[studyId].forEach(site => { const option = document.createElement('option'); option.value = site.id; option.textContent = site.name; siteSelect.appendChild(option); }); }); }); </script>
内容的提问来源于stack exchange,提问作者Shikha Rajpoot
相关产品推荐
相关产品推荐

