フィルタポップオーバー
filter-popover は、ネイティブの popover と、結果領域をフィルタする
htmx フォームの組み合わせです。ユーザーが Apply をクリックすると
htmx が結果をスワップし、installClosePopover ビヘイビアが
ポップオーバーを閉じます。
Filter を開いてステータスを選ぶか名前の一部を入力し、Apply を
押してみてください — htmx が api/recipes/filter-popover/ 配下のデモ
エンドポイント(実際のアプリでは /items など自前の URL)へ GET を
送ってリストをスワップし、レスポンスが 2xx なので
installClosePopover ビヘイビアがポップオーバーを閉じます。
基本の使い方
Section titled “基本の使い方”<button class="hc-button" type="button" popovertarget="status-filter"> Status</button>
<div id="status-filter" class="hc-popover" popover data-side="bottom" data-align="start"> <form data-hx-get="/items" data-hx-include="this" data-hx-target="#items-tbody" data-hx-swap="innerHTML" data-hx-push-url="true" data-hc-close-popover-on-success>
<fieldset> <legend>Status</legend> <label><input type="checkbox" name="status" value="active"> Active</label> <label><input type="checkbox" name="status" value="pending"> Pending</label> <label><input type="checkbox" name="status" value="failed"> Failed</label> </fieldset>
<div class="hc-toolbar"> <button class="hc-button" data-size="sm" type="reset">Reset</button> <span data-hc-spacer="true"></span> <button class="hc-button" data-size="sm" data-variant="primary" type="submit">Apply</button> </div> </form></div>
<table class="hc-table"> <thead>…</thead> <tbody id="items-tbody" data-hx-get="/items" data-hx-trigger="load"></tbody></table>何が起きるか
Section titled “何が起きるか”- ユーザーが Status をクリック → ブラウザがポップオーバーを開き
ます(
popovertarget+popover=auto)。 - ユーザーがチェックボックスを切り替えて Apply をクリック。
- フォームには
data-hc-close-popover-on-successとdata-hx-get="/items"があります。htmx がフォームデータつきの GET を 送信し、レスポンスを#items-tbodyへスワップします。 htmx:afterRequestが発火。installClosePopoverビヘイビアが オプトイン属性を確認し、リクエストが成功していれば、最寄りの[popover]のhidePopover()を呼びます。data-hx-push-url="true"が URL を更新するため、フィルタはページ 再読み込みを生き延び、共有可能です。(上のライブデモがdata-hx-push-urlを省いているのは意図的です — ドキュメントページが 自分の URL を書き換えるわけにはいきません。)
キャンセルは外側をクリック(popover ネイティブのライトディスミス) するか Escape を押すだけ — JavaScript のフックは不要です。
サーバレスポンス契約
Section titled “サーバレスポンス契約”フォームそのものがフィルタ状態です — すべてのコントロールがクエリ
パラメータとして送られます。チェックボックスグループは name を繰り返し
(?status=active&status=pending)、単一の <select>(ライブデモが
そうです)は status を 1 値で送り、フリーテキスト欄は q=… を
加えます。サーバはフォームが宣言したものをそのまま読みます。
レスポンスはターゲットの innerHTML なので、フォームがターゲットに
するコンテナに形を合わせる必要があります。上の
<tbody id="items-tbody"> に対しては行を返します:
<tr id="item-12">…</tr><tr id="item-15">…</tr>…空の状態では、フィルタが実行されたことがユーザーに伝わるよう、明示的な 空状態マークアップを返します:
<tr> <td colspan="3"> <p class="hc-field__message">No items match this filter.</p> </td></tr>ターゲットが素の <div> の場合(スキャフォールドとライブデモが
そうです)は、代わりにリストやメッセージのフラグメントを返して
ください — <tr> は table の外では HTML パーサに捨てられます。
適用そのもののトーストフィードバック(フィルタでは滅多に不要)には、
HX-Trigger: {"hc:toast":{…}} ヘッダーを含めます。
| リクエスト | レスポンス |
|---|---|
GET /items?status=active&status=pending&q=…(Apply) | 200 + ターゲットの innerHTML フラグメント — ポップオーバーは閉じます |
| フィルタに該当なし | 200 + 明示的な空状態フラグメント |
| 失敗 | 非 2xx — スワップされず、テーブルは行を保ち、ポップオーバーは開いたままです |
htmx ≥ 2 は非 2xx レスポンスをスワップしないため、テーブルは直前の行を
保ちます — そして installClosePopover は成功時にしか閉じないので、
ポップオーバーは開いたままになり、ユーザーはやり直せます。明示的に
知らせるには、エラーレスポンスに HX-Trigger: {"hc:toast":{…}}
ヘッダーを付けてください
(トーストレシピを参照)。
アクセシビリティ
Section titled “アクセシビリティ”- ポップオーバーは
popovertargetつきの本物の<button>で開かれ ます。フォーカス、ライトディスミス、Escape はブラウザが処理します。 - ポップオーバーは自動的にメニューになるわけではありません。この
フィルタフォームに
role="menu"を設定するのは誤りです — これは フォームです。デフォルトの role のままにしてください。 - 各 fieldset には見える
<legend>を与えてください。legend の代わりにaria-labelを使ったチェックボックス型フィルタは、視線で追いにくく なります。 - ポップオーバーが閉じた後のトリガーに
aria-current(例:「Status: 2 selected」)を付けると良い合図になります。件数は フィルタ状態からサーバサイドで描画してください。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”- JavaScript なしでは、ポップオーバーはただの
<div>です — ページに 見えてしまいます。リセット CSS に[popover]:not(:popover-open) { display: none; }を足し、非 JS フォールバック(別 URL のフルページフィルタフォーム)を検討して ください。 hc.behaviors.jsなしでも htmx のスワップは起こりますが、 ポップオーバーは開いたままです。これは無害です — ユーザーが手動で 閉じられます。
- ポップオーバーコンポーネント
- ライブ検索レシピ — ポップオーバーのラッパーなしの同じ htmx パターン。
- トーストレシピ — 「フィルタを保存しました」のフィードバックに。