A stimulus controller for adding / removing Rails nested form dynamically.
- @hotwired/stimulus 3.0+
Install from npm:
$ npm install @kanety/stimulus-nested-form --save
Register controller:
import { Application } from '@hotwired/stimulus';
import NestedFormController from '@kanety/stimulus-nested-form';
const application = Application.start();
application.register('nested-form', NestedFormController);
Build nested form using Rails fields_for
:
<%= form_with do |f| %>
<div data-controller="nested-form">
<%= f.fields_for :assocs do |assoc_form| %>
<div data-nested-form-target="form">
<%= assoc_form.text_field :text %>
<button type="button" data-action="nested-form#remove">Remove</button>
</div>
<% end %>
<button type="button" data-action="nested-form#add">Add</button>
</div>
<% end %>
New nested form will be added when the Add
button is clicked.
The last element of the nested forms is used as a template to be added.
The index written in id
and name
attributes are incremented automatically as the following example:
<form>
<div data-controller="nested-form">
<div data-nested-form-target="form">
<input type="text" name="model[assocs_attributes][0][text]" id="model_assocs_attributes_0_text">
<button type="button" data-action="nested-form#remove">Remove</button>
</div>
<div data-nested-form-target="form">
<input type="text" name="model[assocs_attributes][1][text]" id="model_assocs_attributes_1_text">
<button type="button" data-action="nested-form#remove">Remove</button>
</div>
<button type="button" data-action="nested-form#add">Add</button>
</div>
</form>
The added form is handled as follows:
- The values of
input
andtextarea
elements are cleared. - The selected options of
select
elements are cleared. - The first element of radio buttons is selected.
You can specify name of associations by associations
option.
If the nested form consists of multiple associations such as assocsA
and assocsB
,
you can set the name of associations as the following example:
<div data-controller="nested-form"
data-nested-form-associations-value='["assocsA", "assocsB"]'>
</div>
You can also specify name of primary keys as follows:
<div data-controller="nested-form"
data-nested-form-associations-value='["assocsA", "assocsB"]'
data-nested-form-primary-keys-value='["id", "id"]'>
</div>
If you want to disable Add
button when the number of nested forms reached to the limit,
max
option is available:
<div data-controller="nested-form"
data-nested-form-max-value="10">
</div>
If you want to change start index of nested form, start
option is available:
<div data-controller="nested-form"
data-nested-form-start-value="10">
</div>
If you want to change the number of adding nested form at once, increment
option is available:
<div data-controller="nested-form"
data-nested-form-increment-value="3">
</div>
You can customize tags and attributes which contains index value of nested form:
NestedFormController.tags = NestedFormController.tags.concat(['a']);
NestedFormController.attributes = NestedFormController.attributes.concat(['onclick']);
Default tags and attributes are:
static tags = ['input', 'textarea', 'select', 'label'];
static attributes = ['id', 'name', 'for'];
Following callbacks are available to manipulate nested form elements:
let element = document.querySelector('[data-controller="nested-form"]')
element.addEventListener('nested-form:before-add', (e) => {
console.log(e.detail.form); // form to be added
e.preventDefault(); // you can cancel form addition by preventDefault
});
element.addEventListener('nested-form:after-add', (e) => {
console.log(e.detail.form); // added form
});
element.addEventListener('nested-form:before-remove', (e) => {
console.log(e.detail.form); // form to be remove
e.preventDefault(); // you can cancel form removal by preventDefault
});
element.addEventListener('nested-form:after-remove', (e) => {
console.log(e.detail.form); // removed form
});
The library is available as open source under the terms of the MIT License.