はじめに
cocoonで実装していたnested formを、cocoonを使わずにstimulusで実装した際の記録です。
環境
- Rails 7.0.8
- ruby 3.3.5
- propshaft
- importmap-rails
cocoon関連のライブラリ/パッケージ
手順
1. cocoonをアンインストールする
2. フォームを変更する
3. stimulusコントローラを作成する
1. cocoonをアンインストールする
- gemのcocoon
$ gem uninstall cocoon
$ bundl install # Gemfileから削除後に実行
- npmパッケージのcocoon-vanilla-js
$ ./bin/importmap unpin @oddcamp/cocoon-vanilla-js
application.js
import "@oddcamp/cocoon-vanilla-js" /*この行を削除する
2. フォームを変更する
構造
-
_form.html.erb
内で_fuga_fields.html.erb
をレンダリング - 親モデルがHoge、子モデルがFuga
_form.html.erb
- 変更前
<%= form_with model: @hoge do |form| %>
<%= form.fields_for :fuga do |f| %>
<%= render 'fuga_fields', f: f %>
<% end %>
<div id="detail-association-insertion-point"></div>
<%= link_to_add_association "追加", form, :fuga,
class: 'btn btn-info',
data: { association_insertion_node: '#detail-association-
insertion-point', association_insertion_method: 'append'} %>
<%= form.submit '保存', class:'btn btn-success' %>
<% end %>
- 変更後
<%= form_with model: @hoge do |form| %>
<div data-controller="nested-form">
<template data-nested-form-target="template">
<%= form.fields_for :fugas, Fuga.new, child_index: 'TEMPLATE_RECORD' do |f| %>
<%= render 'fuga_fields', f: f %>
<% end %>
</template>
<%= form.fields_for :fugas do |f| %>
<%= render 'fuga_fields', f: f %>
<% end %>
<div data-nested-form-target="addLicense">
<%= link_to "追加", "#", data: {action: "click->nested-form#addAssociation"}, class: 'btn btn-info'%>
</div>
<%= form.submit '保存', class:'btn btn-success' %>
</div>
<% end %>
解説
-
<div "data-controller="nested-form">...</div>
:divタグ内の範囲をnested_form_controller.jsのファイルに紐づける - templateタグ:JavaScriptを使用する前提のタグ(単体では表示されない)
-
data-nested-form-target="xxx"
:nested_form_controller.js内のstatic targets = ["xxx"]と紐づける -
child_index: TEMPLATE_RECORD
:新しく生成されたFugaオブジェクトを識別するために用意 -
data: {action: "click->nested-form#addAssociation"}
:clickでnested_form_controller.jsのaddAssociationメソッドを呼び出す
_fuga_fields.html.erb
- 変更前
<%= link_to_remove_association "削除", f, class:"btn btn-outline-danger" %>
- 変更後
<%= link_to "削除", "#", data: {action: "click->nested-form#removeAssociation"}, class:"btn btn-outline-danger" %></div>
<%= f.hidden_field :_destroy %>
解説
-
data: {action: "click->nested-form#removeAssociation"}
:clickでnested_form_controller.jsのremoveAssociationメソッドを呼び出す -
_destroy
:accepts_nested_attributes_for
にallow_destroy: true
が指定されている場合にtrueが返ると関連付けられているオブジェクトが削除される
3. stimulusのコントローラを作成する
$ rails generate stimulus nested_form
create app/javascript/controllers/nested_form_controller.js
nested_form_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["addLicense", "template"]
addAssociation(event){
event.preventDefault()
var content = this.templateTarget.innerHTML.replace(/TEMPLATE_RECORD/g, Math.floor(Math.random()*100))
this.addLicenseTarget.insertAdjacentHTML('beforebegin', content)
}
removeAssociation(event){
event.preventDefault()
let item = event.target.closest(".nested-fields")
item.querySelector("input[name*='_destroy']").value = 1
item.style.display = 'none'
}
}
解説
-
this.templateTarget.innerHTML.replace(/TEMPLATE_RECORD/g, Math.floor(Math.random()*100))
:TEMPLATE_RECORD
を、オブジェクトを識別できるようにランダムな数字で置き換える
おわりに
stimulusを使用することで、JavaScriptをあまり書かずに実装することができました。
nested formを実装できるstimulus componentも用意されています。こちらを利用すると記述量がさらに少なくできるようです。
参考