マスタ参照
業務フォームはマスタを参照します: 受注の取引先、明細の品目、経費の
原価センタ。コードを覚えているパワーユーザーは直接入力し、それ以外の
人には検索ダイアログが要ります(SAP でいう F4 ヘルプ)。このレシピは
そのフィールドを契約にしたものです — 2 つの入力、1 つの真実:
人間向けの可視の *_code、同一性を運ぶ hidden の *_id、そして
両者を決して食い違わせないサーバ。
別名: コード入力、参照ダイアログ、外部キー選択。
コードを編集してフィールドを離れてみてください: C-1043 は解決し、
C-9999 は 422 の形(メッセージ+ id クリア)、空にすれば
クリア状態に。🔍 ボタンで検索ダイアログが開き、行を選ぶとフィールドが
再描画されてダイアログが閉じます — 非アクティブの取引先は「見えるが
選べない」形で並びます。
Loading field…
マークアップ
Section titled “マークアップ”<div class="hc-field" id="customer-field" data-hc-lookup> <label class="hc-field__label" for="customer-code">Customer</label> <div class="hc-input-group"> <input class="hc-input" id="customer-code" name="customer_code" value="C-1041" data-hx-get="/customers/resolve" data-hx-trigger="change" data-hx-target="#customer-field" data-hx-swap="outerHTML"> <button class="hc-button" type="button" aria-haspopup="dialog" aria-label="Search customers" data-hx-get="/customers/lookup" data-hx-target="#lookup-root" data-hx-swap="innerHTML">🔍</button> </div> <p class="hc-field__hint">Acme Trading K.K.</p> <input type="hidden" name="customer_id" value="cus_9f2"></div>
<div id="lookup-root" data-hc-remote-dialog-root></div>これを健全にしているルール:
- 2 フィールド、1 つの真実。 コードはユーザーが見るもの。hidden の id(不透明トークン — 複合キーはこの中に畳む、 スナップショットページャーの キー規則)が送信されるもの。表示名はプレゼンテーションで、決して 送信されません。
- 未解決のコード = 空の id。 古典的な欠陥は、修正されたコードの
下に古い id が残ること — 未解決レスポンスは必ず hidden をクリアし、
hc validateはフィールド全体が一体でスワップされること (outerHTML)を要求します。コード・ヒント・id は別々に動けません。 - 送信先エンドポイントはどのみち再検証します。 id はクライアント 経由の値で、それ自体は何の証明でもありません。
解決(直接入力)
Section titled “解決(直接入力)”change で /resolve?customer_code=… を GET し、フィールド全体が
返ります:
| 入力 | レスポンス |
|---|---|
| 既知のコード | 200 — ヒント=名称、id 充填、コードは正規形(大小・全半角)に整えて返す |
| 未知のコード | 422 — aria-invalid、ヒントの位置にメッセージ、id は空、生のコードをエコー |
| 空のコード | 200 — クリア状態。必須かどうかは送信エンドポイントの仕事 |
422 には
ミューテーションフォームの
1 行 beforeSwap 許可を併用します。
リモートダイアログに
ライブ検索と結果
リストを載せたもの。選択可能な行は #customer-field を outerHTML
でターゲットするボタンで、選ぶとフィールドが直接再描画され、成功
リクエストがダイアログを閉じます(data-hc-close-dialog-on-success)。
ダイアログ内の検索フォームは
data-hc-close-dialog-on-success="false" でオプトアウトが必須です
— 最も近いキャリアが勝つため、オプトアウトなしではデバウンスされた
最初のキーストロークの 200 が、行を選ぶ前にダイアログを閉じて
しまいます。真似する価値のある 2 つの姿勢:
- 非アクティブなマスタは「見えるが選べない」 —
aria-disabled="true"、配線なし、理由は行のテキストに(「2026-04 から取引停止」)。黙って消すより見えて拒否される方が良い、 検索結果の上限の バナーと同じ姿勢です。 - 認可は行単位 — 参照してはいけないマスタは一覧に出さない。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”JavaScript なしでは、これはただのコード入力フィールドです: 🔍 は
type="button" なので不活性、検証は送信時に行われます — それは
送信先エンドポイントがどのみちやることです。行き止まりなし、便利が
1 つ減るだけ。
アクセシビリティ
Section titled “アクセシビリティ”- 🔍 ボタンには
aria-haspopup="dialog"とaria-labelを — アイコンは名前ではありません。 - 未解決 =
aria-invalid+ 入力に隣接するテキストメッセージ。 解決 = フィールド自身のヒント行に名称。 - ダイアログは自分でタイトルを持ち、結果はリスト内の素のボタンです。
- 自由入力+参照の混在フィールドは、id を nullable にした同じ レシピです — 契約が求めるのは「コードと id が食い違わない」こと だけ。
- 複数選択の参照は トランスファーの 領分です。
- 小さくローカルなリストなら
hc-comboboxのタイプアヘッドが軽量。 マスタが大きい・複数列で探す・眺めて選ぶ、のときにこのレシピの ダイアログが割に合います。