コンテンツにスキップ

カスケードセレクト

国 → 都道府県 → 市区町村、カテゴリ → サブカテゴリ — Ant Design の Cascader を、ハイパーメディア流で: 各レベルの change が次のレベルの 選択肢をサーバから GET する連鎖した <select> です。カスタム JavaScript はゼロ: hc-select + htmx。

別名: カスケーダー、連動セレクト、絞り込みセレクト。

都道府県を選んでみてください — その change が実際の htmx リクエストで 市区町村の選択肢を GET し、同じレスポンスが区レベルをアウトオブバンドで リセットします。プレースホルダーを選び直すと連鎖が巻き戻ります。 エンドポイントはサーバレスポンス契約を実装した名前空間付きデモで、 api/recipes/cascading-select/ 配下にあります。

<div class="hc-field">
<label class="hc-field__label" for="prefecture">Prefecture</label>
<select class="hc-select" id="prefecture" name="prefecture"
data-hx-get="/areas/cities" data-hx-include="this"
data-hx-target="#city" data-hx-swap="outerHTML">
<option value="">Select…</option>
<option value="13">Tokyo</option>
<option value="27">Osaka</option>
</select>
</div>
<div class="hc-field">
<label class="hc-field__label" for="city">City</label>
<select class="hc-select" id="city" name="city" disabled>
<option value="">Select a prefecture first</option>
</select>
</div>

レベルに 4 つの属性:

属性理由
data-hx-get子の選択肢のエンドポイント
data-hx-include="this"自分の値を送る — htmx の GET はデフォルトで囲むフォームを含めません
data-hx-target="#city"置き換える子
data-hx-swap="outerHTML"子は丸ごと返るため、id / name / 配線が一貫したまま

select に対する htmx のデフォルトトリガー change がそのまま正解です — data-hx-trigger は不要。各はプレースホルダーの option を持つ disabled で始まるため、選択前でもチェーンは正しく読めます。

サーバは再描画された子 <select> を返します — 有効化され、選択肢が 入り、(3 レベル以上のチェーンでは)その子を読み込むための同じ 4 属性が配線された状態で。より深いレベルは同じレスポンスの中で out-of-band スワップとしてリセットします — 1 つのレスポンスがチェーン 全体を一貫させます:

<select class="hc-select" id="city" name="city"
data-hx-get="/areas/wards" data-hx-include="this"
data-hx-target="#ward" data-hx-swap="outerHTML">
<option value="">Select…</option>
<option value="13101">Chiyoda</option>
</select>
<select class="hc-select" id="ward" name="ward" disabled data-hx-swap-oob="true">
<option value="">Select a city first</option>
</select>

プレースホルダーの選び直しも change です: サーバは空の値を受け取り、 無効化されたプレースホルダーの子(+ OOB リセット)で応答してチェーンを 巻き戻します。未知・陳腐化した親の値にも同じ応答を — エラーページには しません。

リクエストレスポンス
GET /areas/cities?prefecture=13(親の change)200 + 再描画された子 <select>、より深いレベルの OOB リセットつき
GET /areas/cities?prefecture=(プレースホルダーの選び直し)200 + 無効化されたプレースホルダーの子、OOB リセットつき
GET /areas/cities?prefecture=99(未知・陳腐化した値)200 + 無効化されたプレースホルダーの子 — エラーページにはしません

動的なチェーンは定義上 JavaScript を必要とします。推奨のフォール バック: 全レベルをサーバサイドで描画し、素のフォーム GET の往復で 次のレベルが埋まったページを再描画する、または自由入力を受け付ける。 いずれにせよ select は周囲のフォームと普通にシリアライズされます — このマークアップが no-JS ユーザーを閉じ込めることはありません。

  • すべてのレベルが本物の <label for> を保ちます — 子は同じ id のまま outerHTML でスワップされるため、関連づけはスワップを 生き延びます。
  • 無効化されたプレースホルダー(「Select a prefecture first」)こそが アフォーダンスです — スクリーンリーダーはそのレベルが不活性な理由を 読み上げます。
  • 選択の順序はスクリプトではなくマークアップ(準備できるまで disabled)が強制します — キーボードユーザーが壊せる状態はありません。
  • セレクト — 土台となる コントロール。
  • live-search — 選択肢が select には多すぎるときは、代わりに検索を。
  • ツリー + lazy-tree — パス全体が 見えたままの階層ブラウジングに。