This page looks best with JavaScript enabled

【Rails × Turbo】renderを出力するJS scriptをViewに書く

 ·   ·  ☕ 3 分で読めます
✏️

概要

renderの中身をJSで増やす / 削除するようなJSをviewに直接書きました。
このやり方が調べても出てこなかったので、実装の一例として書いておきます。

ついでに、Turboのせいでハマったので、それも記録しておきます。

image

Image from Gyazo

実装

フォームそのものの細かい実装についてはこちら参照

 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を実行するようにする。

const mediaForm = document.getElementById(‘js-media-form’)

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を外した場合は、上記のことを考慮しなくてもいい。

Share on

END
END
@aiandrox

目次