LoginSignup
1
1

More than 3 years have passed since last update.

【Rails】フォームにフォーム外のファイルを追加しようとしたらJavaScriptをめっちゃ書くことになった

Posted at

概要

Railsのフォーム送信は色々いい感じにやってくれるが、少し変えたい時もある。
僕はフォーム外からファイルを追加したかった。

そういう時は、rails-ujsのajaxイベントハンドラを使うとフォーム送信に要所で割り込める。
Working with JavaScript in Rails#3.5 Rails-ujs event handlers ※ Rails 5.1以降の機能

以下、これを使ってフォーム外のファイルをフォーム内容と同時送信する例を紹介する。

やりたいことの例

本来フォーム内のnameだけ送信されるところ、フォーム外からファイルを追加送信したい。
ここでは仮に、フォーム外のnewImageFilesに画像ファイルを入れてあり、フォーム内容と一緒に送信したいという前提で話を進める。

<%= form_with model: [:user, @config] do |f| %>
  <div>
    <%= f.label :name, 'お名前' %>
    <div>
      <%= f.text_field :name %>
    </div>
  </div>
  <div>
    <%= f.submit '保存' %> <%#= 通常、フォームの内容(今回はname)のみ送信される %>
  </div>
<% end %>
<script>
  var newImageFiles = [] // ← フォーム外のファイルを送信に含めたい
</script>

form_withlocalオプションを付けないでください。
参考:【Rails】form_withのlocalオプション

実装

// ...前略
<script>
  var newImageFiles = [] // ← フォーム外のファイルを送信に含めたい
  document.body.addEventListener('ajax:beforeSend', function(event) { // [1]
    var detail = event.detail
    var xhr = detail[0], options = detail[1]
    // [2]コールバック
    options.success = function() {}
    options.error = function() {}
    options.complete = function() {}
    newImageFiles.forEach(image => {
      options.data.append('config[images][]', image, image.name) // [3]
    })
    xhr.onreadystatechange = function () { // [4]
      if (this.readyState === XMLHttpRequest.DONE) {
        if (this.status === 200) { // [4-1] 200の時、リロード
          window.location.href = location.href
        } else { // [4-1] 200以外の時、失敗表示
          alert(`フォーム送信に失敗しました`)
        }
      }
    }
    xhr.send(options.data) // [5]
    event.preventDefault() // [6] その後の通常処理を行わない
  })

[1] ajax:beforeSendイベントから、フォームから送信予定のxhrとoptionsがとれる。これをカスタマイズする

  document.body.addEventListener('ajax:beforeSend', function(event) { // [1]
    var detail = event.detail
    var xhr = detail[0], options = detail[1]

[2] optionsにコールバック設定がある。そのまま利用してもよいが今回は雑に全部削除

    // [2]コールバック
    options.success = function() {}
    options.error = function() {}
    options.complete = function() {}

[3] options.dataにフォーム外から含めたいデータをappend。

    newImageFiles.forEach(image => {
      options.data.append('config[images][]', image, image.name) // [3]
    })

[4] onreadystatechangeを改めて書く。これも今回は最低限。

    xhr.onreadystatechange = function () { // [4]
      if (this.readyState === XMLHttpRequest.DONE) {
        if (this.status === 200) { // [4-1] 200の時、リロード
          window.location.href = location.href
        } else { // [4-1] 200以外の時、失敗表示
          alert(`フォーム送信に失敗しました`)
        }
      }
    }

[5] xhrを送信。

    xhr.send(options.data) // [5]

[6] その後の通常処理をキャンセル。Rails5以前のjquery-ujsではこういうところでreturn falseと書くらしいので注意

    event.preventDefault() // [6] その後の通常処理を行わない

感想

Railsの話だけどJavaScriptしか書いていない。
rails-ujsのこの機能は、ほかにも色々な用途で使えると思います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1