You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 10:32:31