コンテンツにスキップ

マスタ参照

業務フォームはマスタを参照します: 受注の取引先、明細の品目、経費の 原価センタ。コードを覚えているパワーユーザーは直接入力し、それ以外の 人には検索ダイアログが要ります(SAP でいう F4 ヘルプ)。このレシピは そのフィールドを契約にしたものです — 2 つの入力、1 つの真実: 人間向けの可視の *_code、同一性を運ぶ hidden の *_id、そして 両者を決して食い違わせないサーバ。

別名: コード入力、参照ダイアログ、外部キー選択。

コードを編集してフィールドを離れてみてください: C-1043 は解決し、 C-9999 は 422 の形(メッセージ+ id クリア)、空にすれば クリア状態に。🔍 ボタンで検索ダイアログが開き、行を選ぶとフィールドが 再描画されてダイアログが閉じます — 非アクティブの取引先は「見えるが 選べない」形で並びます。

Loading field…

<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 はクライアント 経由の値で、それ自体は何の証明でもありません。

change/resolve?customer_code=… を GET し、フィールド全体が 返ります:

入力レスポンス
既知のコード200 — ヒント=名称、id 充填、コードは正規形(大小・全半角)に整えて返す
未知のコード422aria-invalid、ヒントの位置にメッセージ、id は空、生のコードをエコー
空のコード200 — クリア状態。必須かどうかは送信エンドポイントの仕事

422 には ミューテーションフォームの 1 行 beforeSwap 許可を併用します。

リモートダイアログライブ検索と結果 リストを載せたもの。選択可能な行は #customer-fieldouterHTMLターゲットするボタンで、選ぶとフィールドが直接再描画され、成功 リクエストがダイアログを閉じます(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 つ減るだけ。

  • 🔍 ボタンには aria-haspopup="dialog"aria-label を — アイコンは名前ではありません。
  • 未解決 = aria-invalid + 入力に隣接するテキストメッセージ。 解決 = フィールド自身のヒント行に名称。
  • ダイアログは自分でタイトルを持ち、結果はリスト内の素のボタンです。
  • 自由入力+参照の混在フィールドは、id を nullable にした同じ レシピです — 契約が求めるのは「コードと id が食い違わない」こと だけ。
  • 複数選択の参照トランスファーの 領分です。
  • 小さくローカルなリストなら hc-combobox のタイプアヘッドが軽量。 マスタが大きい・複数列で探す・眺めて選ぶ、のときにこのレシピの ダイアログが割に合います。