概要
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_with
にlocal
オプションを付けないでください。
参考:【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のこの機能は、ほかにも色々な用途で使えると思います。