A helper and Stimulus controller to make building nested forms easier.
Used 15 times
B
Brian Weaver
Usage
A helper and Stimulus controller to make building nested forms easier.
Full credit to user Alex on Stack Overflow for his incredibly thorough answer that includes this solution. https://stackoverflow.com/a/71715794
Full credit to user Alex on Stack Overflow for his incredibly thorough answer that includes this solution. https://stackoverflow.com/a/71715794
After running the template, use the helper in your forms like this.
<%= form_with model: foo do |form| %>
<%= dynamic_fields_for form, :bars do |bars_form| %>
# NOTE: this block will be rendered once for the <template> and once for every `bar`
<%= tag.div do %>
<%= bars_form.text_field :name %>
<%= bars_form.check_box :_destroy, title: "Check to delete" %>
<% end %>
# NOTE: double nested dynamic fields also work
<%# <%= dynamic_fields_for ff, :things do |things_form| %>
<%# <%= things_form.text_field :name %>
<%# <% end %>
<% end %>
<% end %>
Run this command in your Rails app directory in the terminal:
rails app:template LOCATION="https://railsbytes.com/script/x9Qs78"
Template Source
Review the code before running this template on your machine.
file "app/javascript/controllers/dynamic_fields_controller.js", <<-CODE
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["template"]
add(event) {
event.preventDefault()
event.currentTarget.insertAdjacentHTML(
"beforebegin",
this.templateTarget.innerHTML.replace(
/__CHILD_INDEX__/g,
new Date().getTime().toString()
)
)
}
}
CODE
inject_into_file "app/helpers/application_helper.rb", after: "ApplicationHelper" do
<<-CODE
def dynamic_fields_for(f, association, button_text = "Add")
# stimulus: controller v
tag.div data: {controller: "dynamic-fields"} do
safe_join([
# render existing fields
f.fields_for(association) do |ff|
yield ff
end,
# render "Add" button that will call `add()` function
# stimulus: `add(event)` v
button_tag(button_text, data: {action: "dynamic-fields#add"}),
# render "<template>"
# stimulus: `this.templateTarget` v
tag.template(data: {dynamic_fields_target: "template"}) do
f.fields_for association, association.to_s.classify.constantize.new,
child_index: "__CHILD_INDEX__" do |ff|
# ^ make it easy to gsub from javascript
yield ff
end
end
])
end
end
CODE
end