概要
renderの中身をJSで増やす / 削除するようなJSをviewに直接書きました。
このやり方が調べても出てこなかったので、実装の一例として書いておきます。
ついでに、Turboのせいでハマったので、それも記録しておきます。
実装
フォームそのものの細かい実装についてはこちら参照
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<%= form_with model: @product do |f| %>
<%# 省略 %>
<div id='js-media-form'>
<h2>メディア</h2>
<% @product.media.each do |medium| %>
<%= render 'medium_form', f: f, medium: medium %>
<% end %>
</div>
<span onclick='addMediumForm(event)'>フォームを追加</span>
<%= f.submit %>
<% if @product.errors.blank? %>
<script>
// フォームを追加する
const addMediumForm = (e) => {
const mediaForm = document.getElementById('js-media-form')
mediaForm.insertAdjacentHTML('afterend', '<%= render 'medium_form', f: f, medium: ProductMedium.new %>');
}
// フォームを削除する
const removeMediumForm = (e) => {
const mediumForm = e.currentTarget.closest('.js-medium-form')
mediumForm.remove();
}
</script>
<% end %>
<% end %>
|
DOM操作としてはシンプルです。
addMediumForm
でmedium_formのパーシャルをhtmlとして差し込む。
removeMediumForm
で、クリックした要素のフォーム部分を削除する。
1
2
3
4
5
6
|
.js-medium-form
= f.fields_for 'media_attributes[]', ProductMedium.new, {} do |ff|
= ff.label :title
= ff.text_field :title, value: medium.title
span onclick='removeMediumForm(event)'
| 削除
|
ちなみに、erbとslimが入り混じっていて、非常に統一感がないが、slimにしないと<%= render 'medium_form', f: f, medium: ProductMedium.new %>
が一行目の<div id="js-medium-form">
だけになってしまう。
(slimはhtmlコンパイルの過程で改行を削除するので、ちゃんと一行になってくれる。……んだと思う)
Turboによるハマリポイント
<% if @product.errors.blank? %>
バリデーションエラー時に、Turboが効いている状態では、script部分が残ったまま(いい表現が思い浮かばない)再度読み込まれるため、二度目の実行がされることになる。
Uncaught SyntaxError: Identifier 'addMediumForm' has already been declared
なので、if @product.errors.blank?
で初回のみscriptを実行するようにする。
mediaForm
の定義を関数外で行った場合、なぜかinsertAdjacentHTML
が動作しない。insertBefore
なども同様。
1
2
3
4
5
6
|
<script>
const addMediumForm = (e) => {
const mediaForm = document.getElementById('js-media-form')
mediaForm.insertAdjacentHTML('afterend', '<%= render 'medium_form', f: f, medium: ProductMedium.new %>');
}
</script>
|
1
2
3
4
5
6
|
<script>
const mediaForm = document.getElementById('js-media-form')
const addMediumForm = (e) => {
mediaForm.insertAdjacentHTML('afterend', '<%= render 'medium_form', f: f, medium: ProductMedium.new %>');
}
</script>
|
誤のときのmediaForm
はもちろん関数内で呼び出すことはできるが、なぜかそれに対して作用することはできない。
ただし、フォームにdata: { turbo: false }
を付けてフォームからTurboを外した場合は、上記のことを考慮しなくてもいい。