如何在Rails 7.0.5应用中点击添加按钮动态添加步骤文本字段?
Rails 7 动态生成食谱步骤字段的实现方法
方法一:原生JavaScript实现(无需额外Gem)
1. 先修改Recipe模型,支持多步骤存储
首先生成迁移,把steps设为数组类型,用来存储多个步骤文本:
rails generate migration AddStepsToRecipes steps:text:array rails db:migrate
2. 修改表单代码
更新你的bootstrap_form表单,渲染现有步骤并添加添加/删除按钮:
<%= bootstrap_form_with(model: @recipe, local: true) do |f| %> <%= f.text_field :recipe_name, label: "食谱名称" %> <%= f.email_field :email, label: "邮箱" %> <div id="steps-container"> <% # 渲染已有的步骤 %> <% (@recipe.steps || []).each_with_index do |step, index| %> <div class="step-field mb-3"> <%= f.text_field :steps, name: "recipe[steps][]", value: step, label: "步骤 #{index + 1}", class: "form-control" %> <%= button_tag "删除", type: "button", class: "btn btn-danger mt-1 remove-step-btn" %> </div> <% end %> <% # 默认渲染一个空步骤 %> <% if @recipe.steps.blank? %> <div class="step-field mb-3"> <%= f.text_field :steps, name: "recipe[steps][]", label: "步骤 1", class: "form-control" %> <%= button_tag "删除", type: "button", class: "btn btn-danger mt-1 remove-step-btn", disabled: true %> </div> <% end %> </div> <%= button_tag "添加步骤", type: "button", id: "add-step-btn", class: "btn btn-secondary mb-3" %> <%= f.submit "提交食谱", class: "btn btn-primary" %> <% end %>
3. 添加JavaScript逻辑
在页面或JS控制器中添加以下代码,实现动态添加/删除步骤及序号更新:
document.addEventListener('DOMContentLoaded', function() { const addBtn = document.getElementById('add-step-btn'); const container = document.getElementById('steps-container'); // 添加步骤按钮逻辑 addBtn.addEventListener('click', function() { const stepCount = container.querySelectorAll('.step-field').length + 1; const newField = document.createElement('div'); newField.className = 'step-field mb-3'; newField.innerHTML = ` <label class="form-label" for="recipe_steps_${stepCount}">步骤 ${stepCount}</label> <input type="text" name="recipe[steps][]" id="recipe_steps_${stepCount}" class="form-control"> <button type="button" class="btn btn-danger mt-1 remove-step-btn">删除</button> `; container.appendChild(newField); bindRemoveBtn(newField.querySelector('.remove-step-btn')); checkRemoveBtnState(); }); // 绑定现有删除按钮事件 document.querySelectorAll('.remove-step-btn').forEach(btn => bindRemoveBtn(btn)); // 删除按钮逻辑 function bindRemoveBtn(btn) { btn.addEventListener('click', function() { this.parentElement.remove(); updateStepLabels(); checkRemoveBtnState(); }); } // 更新步骤序号标签 function updateStepLabels() { container.querySelectorAll('.step-field').forEach((field, index) => { const label = field.querySelector('.form-label'); label.textContent = `步骤 ${index + 1}`; label.setAttribute('for', `recipe_steps_${index + 1}`); }); } // 确保至少保留一个步骤,禁用单个步骤的删除按钮 function checkRemoveBtnState() { const removeBtns = container.querySelectorAll('.remove-step-btn'); if (removeBtns.length === 1) { removeBtns[0].disabled = true; } else { removeBtns.forEach(btn => btn.disabled = false); } } });
方法二:使用Cocoon Gem(适合复杂嵌套场景)
如果后续需要给步骤扩展更多属性(如时长、图片),Cocoon可以快速实现嵌套表单的动态管理。
1. 添加并配置Cocoon
在Gemfile中添加:
gem 'cocoon'
运行bundle install,若使用Importmap,执行:
bin/importmap pin cocoon
在app/javascript/application.js中导入:
import "cocoon"
2. 创建Step模型并关联Recipe
生成Step模型:
rails generate model Step recipe:references content:text rails db:migrate
在app/models/recipe.rb中添加关联和嵌套属性支持:
class Recipe < ApplicationRecord has_many :steps, dependent: :destroy accepts_nested_attributes_for :steps, allow_destroy: true end
3. 更新控制器
在Recipe控制器的new动作中构建一个默认步骤:
def new @recipe = Recipe.new @recipe.steps.build end
更新strong params,允许步骤的嵌套属性:
private def recipe_params params.require(:recipe).permit(:recipe_name, :email, steps_attributes: [:id, :content, :_destroy]) end
4. 修改表单代码
用Cocoon自带的方法实现动态字段:
<%= bootstrap_form_with(model: @recipe, local: true) do |f| %> <%= f.text_field :recipe_name, label: "食谱名称" %> <%= f.email_field :email, label: "邮箱" %> <div id="steps-container" class="mb-3"> <%= f.fields_for :steps do |step_form| %> <div class="nested-fields mb-3"> <%= step_form.text_field :content, label: "步骤", class: "form-control" %> <%= link_to_remove_association "删除", step_form, class: "btn btn-danger mt-1" %> </div> <% end %> <%= link_to_add_association "添加步骤", f, :steps, class: "btn btn-secondary" %> </div> <%= f.submit "提交食谱", class: "btn btn-primary" %> <% end %>
注意事项
- 原生JS方法适合简单的多文本字段场景,无需依赖额外Gem,代码可控性高。
- Cocoon方法适合需要扩展步骤属性的场景,自带成熟的添加/删除逻辑,减少手写JS的工作量。
内容的提问来源于stack exchange,提问作者wolf
相关产品推荐
相关产品推荐

