Railsではリンクやフォームに簡単に確認ダイアログを出すための記法があります。aタグやinputタグなどにdata-confirm="よろしいですか?"
みたいにするやつですね。
<%= link_to 'delete', item_path(item), data: { confirm: 'are you sure?' } %>
これをするとブラウザの確認ダイアログが出ます。ここでキャンセルした場合はリクエストは投げられません。
というものなのですが、これをかっこよくしたいとか「以後表示しない」みたいなことをさせたくないみたいな理由で、ブラウザのダイアログを使いたくないみたいなことが場合によっては発生します。
↑のような機能はrails-ujs
というjavascriptによって提供されています(他の処理もいろいろやっています)。元々はjquery-ujs
という名前でしたが、jQueryへの依存をなくすような形で書き換えられ、確かRails 5.1から標準に取り込まれました。
jquery-ujsの頃は↓のような方法で自前ダイアログに変更できたのですが、rails-ujsになってからはこの方法は使えません。
rails-ujs
を使用している場合はaタグのクリックイベントを単純に乗っ取るだけでは自前実装のダイアログに置き換えることはできません。また、data-confirm
以外にもrails-ujs
が色々やっている(たとえばremote: true
なリンクの処理など)ため、単純に乗っ取ってしまうと想定した機能が動かなくなることもあります。
そういうわけで必要な箇所だけを正しく乗っ取ってあげる必要があります。幸いコード例を見つけたのでそちらを参考に書いてみましょう。
こちらの場合はsweet-alertというJSライブラリを使ってダイアログを表示しています。またujsのdata-confirm
を上書きしないよう独自のdata-confirm-swal
という属性を使うふうにかいていますが、data-confirm
自体を乗っ取ってしまっても問題なく動きます。
まず、自前のイベントハンドラとクリックイベントへの紐づけの部分がこんな感じになります(元のコードの一部(data-confirm-swal
の変更、対象とする要素のセレクタinput\[data-confirm\]
のあたり)を修正)。
(function() {
var handleConfirm = function(element) {
if (!allowAction(this)) {
Rails.stopEverything(element)
}
}
var allowAction = function(element) {
if (element.getAttribute('data-confirm') === null) {
return true
}
showConfirmationDialog(element)
return false
}
document.addEventListener('rails:attachBindings', function(e) {
Rails.delegate(document, 'a[data-confirm], input[data-confirm], button[data-confirm]', 'click', handleConfirm)
})
}).call(this)
:delete
メソッドなリンクが正しく動くようにとか、Rails.start
後にこちらのやりたい処理を差し込めるように、といった工夫のためrails:attachbindings
のリスナーでイベントハンドラの登録を行います。
クリック後、allowAction
でconfirmを出すべき属性があるかないかを判断し、出すべきであればshowConfirmationDialog
を呼び出すという流れです。
その後は自分で出したいダイアログを表示するわけですが、1つポイントがあります。元記事(一部data-confirm-swal
を修正)の例を見てみます。
// Display the confirmation dialog
var showConfirmationDialog = function(element) {
var message = element.getAttribute('data-confirm')
var text = element.getAttribute('data-text')
swal({
title: message || 'Are you sure?',
text: text || '',
type: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'Cancel',
}).then(function(result) { // <= OKを押した際のコールバック
confirmed(element, result)
})
}
var confirmed = function(element, result) {
if (result.value) {
// User clicked confirm button
element.removeAttribute('data-confirm')
element.click()
}
}
JSライブラリによってダイアログを表示し、OKが押された場合にはdata-confirm
を消した上でJSからクリックします。すると今度は確認ダイアログなしでクリックされたことになり、元々想定していた通りのリクエストが送られる、というわけです。
ダイアログの実装自体はなんでもよくて、例えばjquery-confirmのようなものでもOKです。
元記事への補足として、remote: true
なリクエストの場合、画面全体が更新されないことが多いかと思います。場合によってはdata-confirm
を削除した状態のリンクがそのまま残っている場合もあり得ます。
この場合、該当のリンクをクリックすると今度は確認なしでリクエストを投げてしまいますのであまりよくありません。ということで元のダイアログの部分のコードを修正し、たとえばダイアログが閉じたらdata-confirm
を戻しておくなどの工夫が必要です。
element.setAttribute('data-confirm', message)