Skip to main content

NestedFormController

Purpose

Primarily for Rails accepts_nested_attributes_for associations, enabling a form interface that allows a user to added and remove sub-records.

Actions

ActionPurpose
addAdd a new record to the form using the given template
removeRemove the nearest parent record from the form. If the record is a new record, just removes it from the DOm, otherwise looks for a hidden input with name="_destroy" sets it to 1 (true) and hides the record using display: none

Targets

TargetPurposeDefault
templateThe <template> or <script type="text/x-template"> tag containing the HTML for a sub record.-
targetWhere to add the created sub-records.-

Classes

ClassPurposeDefault
---

Values

ValueTypeDescriptionDefault
wrapperClassStringThe class of the <div> that wraps each sub-record.nested-fields
insertStringHow to insert records around target. Accepts any one of the parameters accepted by insertAdjacentHTMLbeforeend (append)

Events

EventWhenDispatched onevent.detail
----

Side Effects

None

How to Use

This controller requires a bit of structured markup.

There should be a <template data-nested-form-target="template"> or a <script data-nested-form-target="template" type="text/x-template> tag that holds the markup that should be added every time the user adds a new record. If you want to use multiple levels of nested forms you should use <script data-nested-form-target="template" type="text/x-template>, as most browsers down allow <template> tags to be nested.

Ideally you should have a reusable partial for the nested-record's fields so that you can render it for the template that the controller uses to create new records, and for each of the existing records in the form.

Each distinct record in the form should be wrapped in a div with a class of either nested-fields or the value specified in data-nested-form-wrapper-class-value- this enables the controller to perform the operations it needs to do on all of the nested records.

When generating the markup for the template, the inputs should be named using the magic value NEW_RECORD where you want a unique identifier to be placed when the controller inserts a new record. i.e. lists[task_attributes][NEW_RECORD][id]. Using Rails fields_for helper, this is as easy as specifying f.simple_fields_for ..., child_index: 'NEW_RECORD'

HTML / ERB

<!-- FILE: tasks/form-->

<!--
.....
Parent association fields
.....
-->

<h4> Tasks</h4>
<div data-controller="nested-form" data-nested-form-wrapper-class-value="nested-fields">
<script type="text/x-template" data-nested-form-target="template">
<%= f.simple_fields_for :tasks, Task.new, child_index: 'NEW_RECORD' do |task| %>
<%= render "task_fields", f: task %>
<% end %>
</script>

<div data-nested-form-target="target">
<%= f.simple_fields_for :tasks do |task| %>
<%= render "task_fields", f: task %>
<% end %>
</div>

<%= link_to "Add Task", "#", class: "button green", data: { action: "click->nested-form#add" } %>
</div>
<!--
.....
Parent association fields
.....
-->

<!-- FILE: tasks/_task_fields -->
<div class="nested-fields" data-new-record="<%= f.object.new_record? %>">
<div class="form-group">
<%= f.label :description %>
<%= f.text_field :description, class: 'form-control' %>
</div>
<small><%= link_to "Remove", "#", data: { action: "click->nested-form#remove" } %></small>
<%= f.hidden_field :_destroy %>
</div>