HTMLのdialog要素とフォーム機能

こんにちは、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技術を語らいサービスへの活用を探る仲間を募集しています。

hatenacorp.jp