Bra*_*him 2 ruby-on-rails ruby-on-rails-5
假设我有一个包含名称字段的配方表。表格看起来像这样:
<%= form_with(model: recipe, local: true) do |form| %>
<%= form.text_field :name %>
<%= form.submit %>
<% end %>
Run Code Online (Sandbox Code Playgroud)
但如果配方与成分(包含名称字段)相关,则通过包含成分数量的中间表。我应该如何做表格来创建食谱,选择成分并输入成分的数量。如果配方有不止一种成分,还可以选择生成更多字段。所有这一切都在一种形式中。类似的东西:
<%= form_with(model: recipe, local: true) do |form| %>
<%= form.text_field :name %>
(select field to choose an ingredient)
(field for recipe_ingredient to ingress the amount of the ingredient)
(button to generate more fields for other ingredients)
<%= form.submit %>
<% end %>
Run Code Online (Sandbox Code Playgroud)
导轨版本:5.2.2
这是 Rails 中最难做的事情之一,因为它需要在服务器上构建表单,然后在用户添加或删除字段时使用 js 在浏览器上编辑表单。我将尝试展示最低限度的工作示例。
让我们首先使用fields_for在服务器上构建表单,它允许您为模型添加嵌套字段,accepts_nested_fields_for其中一个关系。在你的情况,你需要嵌套表单两次(第一次为Recipe的DoseS和第二每个Dose的Ingredient)。用户不会真正看到Dose模型,因为它只是作为中间表存在。
假设您已经这样设置您的应用程序:
rails g scaffold Ingredient name:string
rails g scaffold Recipe name:string
rails g scaffold Dose ingredient:references recipe:references amount:decimal
Run Code Online (Sandbox Code Playgroud)
然后将其添加到Recipe模型中:
has_many :doses
has_many :ingredients, through: :doses
accepts_nested_attributes_for :doses, allow_destroy: true
Run Code Online (Sandbox Code Playgroud)
这对于Dose模型:
accepts_nested_attributes_for :ingredient
Run Code Online (Sandbox Code Playgroud)
现在编辑app/views/recipes/_form.html.erb文件并添加字段Dose
<%= form.fields_for :doses do |dose_form| %>
<div class="field">
<%= dose_form.label :_destroy %>
<%= dose_form.check_box :_destroy %>
</div>
<div class="field">
<%= dose_form.label :ingredient_id %>
<%= dose_form.select :ingredient_id, @ingredients %>
</div>
<div class="field">
<%= dose_form.label :amount %>
<%= dose_form.number_field :amount %>
</div>
<% end %>
Run Code Online (Sandbox Code Playgroud)
这不会做太多,因为如果关系被填充, fields_for 只会在其块内生成代码。因此,让我们在文件的和操作中填充recipe的doses关系。当我们在那里时,让我们将所有成分添加到我们的变量中,并允许将我们的嵌套属性添加到strong_parameters哈希中。neweditapp/controllers/recipes_controller.rb@ingredients permitted
def new
@recipe = Recipe.new
@ingredients = Ingredient.all.pluck(:name, :id)
1.times{ @recipe.doses.build }
end
def edit
@ingredients = Ingredient.all.pluck(:name, :id)
end
...
def recipe_params
params.require(:recipe).permit(:name, doses_attributes: [:id, :ingredient_id, :amount, :_destroy])
end
Run Code Online (Sandbox Code Playgroud)
您可以根据需要构建任意数量的剂量,一旦我们设置了 js 部分,我们就可以在前端“构建”它们。1 现在很好,只是为了显示我们的剂量场。
运行迁移并启动服务器并创建一些成分,然后您将在创建新配方时在下拉列表中看到它们
现在您有一个有效的嵌套字段解决方案,但我们必须在后端构建剂量并将渲染的表单发送到浏览器,并带有一定数量的构建剂量。让我们添加一些 js 以允许用户动态创建和销毁嵌套字段。
销毁字段很容易,因为我们已经设置好了。如果_destroy复选框打开,我们只需要隐藏该字段。为此,让我们安装 Stimulus
bundle exec rails webpacker:install:stimulus
Run Code Online (Sandbox Code Playgroud)
让我们创建一个新的刺激控制器 app/javascript/controllers/fields_for_controller.js
import {Controller} from "stimulus"
export default class extends Controller {
static targets = ["fields"]
hide(e){
e.target.closest("[data-target='fields-for.fields']").style = "display: none;"
}
}
Run Code Online (Sandbox Code Playgroud)
并更新我们app/views/recipes/_form.html.erb以使用控制器:
<div data-controller="fields-for">
<%= form.fields_for :doses do |dose_form| %>
<div data-target="fields-for.fields">
<div class="field">
<%= dose_form.label :_destroy %>
<%= dose_form.check_box :_destroy, {data: {action: "fields-for#hide"}} %>
</div>
<div class="field">
<%= dose_form.label :ingredient_id %>
<%= dose_form.select :ingredient_id, @ingredients %>
</div>
<div class="field">
<%= dose_form.label :amount %>
<%= dose_form.number_field :amount %>
</div>
</div>
<% end %>
</div>
Run Code Online (Sandbox Code Playgroud)
太好了,现在我们在用户单击复选框时隐藏该字段,后端会因为复选框被选中而销毁剂量。
现在让我们看看 htmlnested_fields生成,以了解如何让用户添加和删除它们:
<div data-target="fields-for.fields">
<div>
<label for="recipe_doses_attributes_0__destroy">Destroy</label>
<input name="recipe[doses_attributes][0][_destroy]" type="hidden" value="0" /><input data-action="fields-for#hide" type="checkbox" value="1" name="recipe[doses_attributes][0][_destroy]" id="recipe_doses_attributes_0__destroy" />
</div>
<div class="field">
<label for="recipe_doses_attributes_0_ingredient_id">Ingredient</label>
<select name="recipe[doses_attributes][0][ingredient_id]" id="recipe_doses_attributes_0_ingredient_id"><option value="1">first ingredient</option>
<option selected="selected" value="2">second ingredient</option>
<option value="3">second ingredient</option></select>
</div>
<div class="field">
<label for="recipe_doses_attributes_0_amount">Amount</label>
<input type="number" value="2.0" name="recipe[doses_attributes][0][amount]" id="recipe_doses_attributes_0_amount" />
</div>
</div>
<input type="hidden" value="3" name="recipe[doses_attributes][0][id]" id="recipe_doses_attributes_0_id" />
Run Code Online (Sandbox Code Playgroud)
感兴趣的是recipe[doses_attributes][0][ingredient_id]具体的[0]事实证明fields_for增量转让child_index给每个建成的doses。后端使用它child_index来知道要删除哪些孩子或更新哪个孩子的哪些属性。
所以现在答案很清楚了,我们只需要插入相同的<div> fields_forcreated 并将child_index这个新插入<div>的值设置为比表单中以前的最高值更高的值。请记住,这只是 an index,而不是 an id,这意味着我们可以将其设置为一个非常大的数字,因为 Rails 只会使用它来将嵌套字段属性保留在同一组中,并id在保存记录时实际分配s。
所以现在我们必须做出两个选择:
fields_for <div>来自对于第一选择,通常的答案是获取当前时间并将其用作 child_index
对于第二个,通常的方法是将 html 块移动到它自己的部分 inapp/views/doses/_fields.html.erb然后在app/bies/recipes/_form.htm.erb. 一旦进入form.fields_for循环。第二次在 a 的 data 属性中button创建一个新表单只是为了生成一个field_for:
<div data-controller="fields-for">
<%= form.fields_for :doses do |dose_form| %>
<%= render "doses/fields", dose_form: dose_form %>
<% end %>
<%= button_tag("Add Dose", {data: { action: "fields-for#add", fields: form.fields_for(:doses, Dose.new, child_index:"new_field"){|dose_form| render("doses/fields", dose_form: dose_form)}}}) %>
</div>
Run Code Online (Sandbox Code Playgroud)
然后使用js从按钮的数据标签中获取部分更新child_index并将更新后的html插入到表单中。由于按钮已经有了,data-action='fields-for#add'我们只需要将添加操作添加到我们的app/javascript/controllers/fields_for_controller.js
add(e){
e.preventDefault()
e.target.insertAdjacentHTML('beforebegin', e.target.dataset.fields.replace(/new_field/g, new Date().getTime()))
}
Run Code Online (Sandbox Code Playgroud)
现在我们不需要doses预先构建。为此使用 gem 简单得多,但这样做的好处是您可以完全根据需要进行设置,并且不会向您的应用程序添加任何不需要的代码。
我也Portion突然想到,这本来是一个更好的名字Dose
| 归档时间: |
|
| 查看次数: |
2469 次 |
| 最近记录: |