こんにちは、id:nanto_viです。この記事ははてなエンジニアAdvent Calendarの1日目の分です。
Webアプリケーションでモーダルダイアログを実現しようとして苦戦したことはないでしょうか? 自前でHTML、CSS、JavaScriptを組み合わせて実装していくと、フォーカスやスクロールの制御が大変ですよね。そんな悩みを解決してくれるのがHTMLのdialog
要素、Webブラウザ組み込みのモーダルダイアログ実装が利用できるという優れものです(モードレスダイアログとしても利用できます)。
dialog
要素を使うことで、モーダルダイアログに要求されるJavaScript機能をブラウザが肩代わりしてくれるので、アクセシビリティの確保も簡単になります。
2021年12月現在、ChromeやEdgeはすでにdialog
要素に対応しています。FirefoxやSafariの開発版でも対応が進んでおり、主要ブラウザでdialog
要素を利用できる日も近いでしょう。
モーダルダイアログを表示する
モーダルダイアログを表示する場合、dialog
要素に対するDOMオブジェクト(HTMLDialogElement
オブジェクト)のshowModal
メソッドを呼び出します。ダイアログを閉じる場合はclose
メソッドを呼び出します。
<p><button type="button" onclick="document.getElementById('ex-dialog-1').showModal()">詳細を表示する</button></p> <dialog id="ex-dialog-1" aria-labelledby="ex-dialog-1-title"> <h3 id="ex-dialog-1-title">詳細</h3> <p>詳細は○○です。</p> <p><button type="button" onclick="this.closest('dialog').close();">閉じる</button></p> </dialog>
なお、showModal
メソッドではなくshow
メソッドを使うと、モードレスダイアログとして表示されます。
dialog
要素とform
要素を組み合わせる
ダイアログを閉じるのにHTMLのフォーム機能を使うこともできます。form
要素のmethod
属性にdialog
という値を指定すると、フォーム送信の挙動が「送信先に移動する」のではなく「ダイアログを閉じる」になります(HTTP要求は発生しません)。
<p><button type="button" onclick="document.getElementById('ex-dialog-2').showModal()">詳細を表示する</button></p> <dialog id="ex-dialog-2" aria-labelledby="ex-dialog-2-title"> <form method="dialog"> <h3 id="ex-dialog-2-title">詳細</h3> <p>詳細は○○です。</p> <p><button type="submit">閉じる</button></p> </form> </dialog>
HTMLのフォーム検証機能を使う
フォーム機能を使えるということは、フォーム検証機能も使えるということです。次の例では、入力欄に何か入力しないと「確定する」ボタンからダイアログを閉じられません。
<p><button type="button" onclick="document.getElementById('ex-dialog-3').showModal()">名前を入力する</button></p> <dialog id="ex-dialog-3" aria-labelledby="ex-dialog-3-title"> <form method="dialog"> <h3 id="ex-dialog-3-title">名前を入力</h3> <p><label>名前: <input type="text" required></label></p> <p><button type="submit">確定する</button></p> </form> </dialog>
なお、ブラウザによってはダイアログ上でEscキーを押下するとキャンセル操作として扱われ、何も入力していなくてもダイアログを閉じられます。
フォーム検証機能を迂回する
フォーム検証をせずにダイアログを閉じたいというときには、いくつか手段があります。
formnovalidate
属性を使う
送信ボタンにformnovalidate
属性を指定すると、フォーム検証を行わずにフォーム送信が実行されます。
<p><button type="button" onclick="document.getElementById('ex-dialog-4').showModal()">名前を入力する</button></p> <dialog id="ex-dialog-4" aria-labelledby="ex-dialog-4-title"> <form method="dialog"> <h3 id="ex-dialog-4-title">名前を入力</h3> <p><label>名前: <input type="text" required></label></p> <p> <button type="submit" formnovalidate>キャンセル</button> <button type="submit">確定する</button> </p> </form> </dialog>
この場合は送信ボタンの順序が重要になってきます。ブラウザによってはテキスト入力欄でEnterキーを押下したときに、最初に出現する送信ボタンが実行されたものとみなされるからです(暗黙的なフォーム送信)。上の例では「キャンセル」ボタンがデフォルトボタンとみなされ、「名前」入力欄で何も入力せずにEnter
キーを押下したときもダイアログが閉じてしまいます。
ちなみに、個人的にはどの送信ボタンがデフォルトボタンなのか指定できるようになると嬉しいのですが、あまり議論は進んでいません。
close
メソッドを使う
「キャンセル」ボタンをデフォルトボタンにしたくなければ、送信ボタン(type="submit"
)ではなく汎用的なボタン(type="button"
)にするという手があります。この場合はJavaScriptでダイアログを閉じることになります。
<button type="button" onclick="this.closest('dialog').close();">キャンセル</button> <button type="submit">確定する</button>
submit
メソッドを使う
HTMLFormElement
オブジェクトのsubmit
メソッドを呼び出すことでも、フォーム検証をせずにフォーム送信できます。
<button type="button" onclick="this.form.submit();">キャンセル</button> <button type="submit">確定する</button>
どのボタンが実行されたか判別する
送信ボタンを使ってダイアログを閉じた場合、その送信ボタンのvalue
プロパティの値が、ダイアログのreturnValue
プロパティの値として設定されます。
<p><button type="button" onclick="document.getElementById('ex-dialog-5').showModal()">名前を入力する</button></p> <p id="ex-dialog-5-output"></p> <dialog id="ex-dialog-5" aria-labelledby="ex-dialog-5-title" onclose="document.getElementById('ex-dialog-5-output').textContent = `「${this.returnValue}」ボタンが実行されました。`;"> <form method="dialog"> <h3 id="ex-dialog-5-title">名前を入力</h3> <p><label>名前: <input type="text" required></label></p> <p> <button type="submit" value="cancel" formnovalidate>キャンセル</button> <button type="submit" value="enter">確定する</button> </p> </form> </dialog>
ダイアログのclose
メソッドの引数に値を指定すると、ダイアログを閉じることとreturnValue
プロパティにその値を設定することが一度にできます。
<button type="button" value="cancel" onclick="this.closest('dialog').close(this.value);">キャンセル</button> <button type="submit" value="enter">確定する</button>
終わりに
以上、HTML仕様とWebブラウザの進歩により、様々な機能を簡単に利用できるようになるという話でした。
はてなでは、Web技術を語らいサービスへの活用を探る仲間を募集しています。