🦓

[Rails]ブラウザのデフォルト確認ダイアログをカスタマイズする

2023/11/04に公開

はじめに

turbo_confirmを使う際に表示される確認ダイアログをカスタマイズしていきます。

app/views/posts/_crud.html.erb
<%= link_to "削除", @post, data: {
            turbo_method: :delete,
            turbo_confirm: "本当に削除しますか?" } %>

デフォルトのブラウザダイアログ:

https://developer.mozilla.org/ja/docs/Web/API/Window/confirm

環境

Ruby 3.2.1
Rails 7.0.8

tl;dr

  1. Turbo.setConfirmMethodをう
  2. <dialog>タグを使う
  3. ダイアログの表示をカスタマイズする

Turbo.setConfirmMethodを使ってブラウザのデフォルトの確認ダイアログをカスタマイズする

Turbo.setConfirmMethod(() => {
  return new Promise((resolve, reject) => {
    // ダイアログ
    // 確認, resolve(true)
    // キャンセル, resolve(false)
  })
})

Turbo.setConfirmMethodメソッドを使用して、カスタムの確認メソッドを設定します。このメソッドはモーダルダイアログを表示し、ユーザーが確認またはキャンセルするまでPromiseを解決しません。

確認ダイアログを表示する前に、新しいPromiseを作成されます。このPromiseは、確認またはキャンセルに基づいて解決されます。

ダイアログが確認された場合、resolve(true)を呼び出し、Promiseをtrueで解決します。これで確認が行われます。
ダイアログがキャンセルされた場合、resolve(false)を呼び出し、Promiseをfalseで解決します。これでキャンセルが行われます。

https://turbo.hotwired.dev/reference/drive#turbo.setconfirmmethod

<dialog>タグを使う

HTMLネイティブの <dialog> タグを使うことができます。<body>タグの直下に作成していきます。

app/views/layouts/application.html.erb
<dialog id="turbo-confirm">
   <form method="dialog">
      <h3>投稿の削除</h3>
      <p>削除しますか?</p>
      <button value="cancel">キャンセル</button>
      <button value="confirm">削除</button>
   </form>
</dialog>

method="dialog"は、 <form>の既定の GET メソッドを上書きします。

app/javascript/application.js
Turbo.setConfirmMethod(()=>{
    let dialog = document.getElementById("turbo-confirm");
    dialog.showModal();

    return new Promise((resolve, reject) =>{
        dialog.addEventListener("close", ()=>{
            resolve(dialog.returnValue == "confirm")
        }, { once: true })
    })
})

dialog.showModal()を呼び出して、モーダルダイアログを表示します。

新しいPromiseを作成し、そのPromiseはダイアログが閉じられたときに解決されます。dialog.addEventListener("close", ...)を使用して、ダイアログが閉じられたときに呼び出されるリスナーを登録します。

resolve(dialog.returnValue == "confirm")を呼び出してPromiseを解決します。ダイアログのreturnValueプロパティが "confirm" の場合、Promiseはtrueで解決され、それ以外の場合はfalseで解決されます。

{ once: true } は、JavaScriptのイベントリスナーに対してオプションとして指定できる設定です。この設定は、特定のイベントハンドラーが1度だけ実行されるようにするために使用されます。

通常、イベントハンドラーはイベントが発生するたびに実行されます。しかし、{ once: true } オプションを指定すると、指定されたイベントハンドラーは最初の一度だけ実行され、その後は自動的に削除されます。一度だけの処理を実装するには便利です。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/dialog

デフォルトのダイアログよりこちらのモーダルが表示されました。

ダイアログの表示をカスタマイズする

ダイアログの再利用性を向上するために中身やCSSをボタンによって動的に表示できるように設定していきます。

ダイアログの中身をそれぞれ取得することができます。

app/javascript/application.js
Turbo.setConfirmMethod((message, element,submitter)=>{
    console.log(message, element,submitter);
})

こうやってダイアログの中身を取得することができました。

# message
本当に削除しますか 

# element
<form class="button_to" method="post" action="/​posts/​12"><input type="hidden" name="_method" value="delete" autocomplete="off"><button data-turbo-confirm="本当に削除しますか" type="submit">​削除​</button><input type="hidden" name="authenticity_token" value="QmANbOvbyInCdNxBynW8tQWFTLT4iANE7Q2rs8b8WnqBy2RXkbiR6Kw_iB7mzaerxTFVjbpm4V522eEKCcy2XQ" autocomplete="off"></form># submitter
<button data-turbo-confirm="本当に削除しますか" type="submit">​削除​</button>

なので、ダイアログのタイトル、本文、ボタンなどをturbo属性で設定すれば動的に表示することができます。

<%= button_to '削除', @post, method: :delete, class: '', 
    data: { turbo_confirm: '本当に削除しますか?', // 本文
    turbo_confirm_title: '投稿の削除', // タイトル
    turbo_confirm_button_class_list: 'bg-red-500' // ボタンのCSS }
%>

getAttributeでそれぞれの属性の値を取得し、dialogのコンテンツに代入します。

app/javascript/application.js
Turbo.setConfirmMethod((message, element,submitter)=>{
    console.log(message, element,submitter);
    
    dialog.querySelector("p").textContent = message; // 本文
    dialog.querySelector("h3").textContent = submitter.getAttribute('data-turbo-confirm-title'); // タイトル
    dialog.querySelector('button[value="confirm"]').classList = submitter.getAttribute('data-turbo-confirm-button-class-list'); // ボタンのCSS
    dialog.showModal();
})

https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute

終わりに

ブラウザのデフォルトのダイアログをカスタマイズすることができましたー
CSSでスタイリングをすればアプリの雰囲気に合う確認ダイアログを簡単に作れるのでこれからも活用していきたいと思います!

https://github.com/hotwired/turbo/pull/525

Discussion