データグリッドの編集エラー
データグリッドの
インラインコミットは楽観的です — このレシピは、サーバーにしか
判定できない値のためにそのフィードバックループを閉じます。各行は
永続化ワイヤリングを運ぶレコード <tbody> で、サーバーの応答は
そのレコードを再描画します: 200 は行だけで確定、422 は行
(サーバーの値に復元、セルは data-invalid)と
.hc-datagrid__error-row のメッセージを一緒に持ってきます — 1 つの
アトミックなスワップ単位なので、古いエラーが取り残されることは
ありません。
バージョニングポリシーの
対象です。
Price セルをダブルクリック(またはフォーカスして
Enter)し、abc をコミットしてください — セルは保存中
状態を見せた後、エラーリングつきでサーバーの値に戻り、メッセージが
拒否された入力を名指しします。0 より大きい数値をコミットすると
200 ブランチが確定してクリアします。
| Name | Price | Ship date |
|---|---|---|
| Chai | 18.00 | 2026-08-01 |
| Chang | 19.00 | 2026-08-03 |
マークアップ
Section titled “マークアップ”<div class="hc-datagrid" id="items-grid" data-hc-datagrid-pending> <template data-datagrid-editor data-col="price"> <input class="hc-input" type="text" inputmode="decimal" aria-label="Price"> </template> <div class="hc-datagrid__scroll"> <table class="hc-datagrid__table"> <thead class="hc-datagrid__head">…</thead> <tbody class="hc-datagrid__record" id="item-1" data-hx-patch="/items/1" data-hx-trigger="hc:datagridedit" data-hx-vals="js:{ col: event.detail.col, value: event.detail.value }" data-hx-disinherit="hx-vals" data-hx-swap="outerHTML"> <tr class="hc-datagrid__row"> <td class="hc-datagrid__cell">Chai</td> <td class="hc-datagrid__cell" data-numeric data-editable data-col="price" data-value="18">18.00</td> </tr> </tbody> </table> </div></div>data-hc-datagrid-pending はグリッドを保存中状態にオプトインします
(再描画が届くまでセルに data-pending + aria-busy)。422 の
スワップは一度だけグローバルに許可します —
field-errors の
許可スニペット:
document.body.addEventListener('htmx:beforeSwap', (event) => { if (event.detail.xhr.status === 422) { event.detail.shouldSwap = true; event.detail.isError = false; }});サーバーレスポンス契約
Section titled “サーバーレスポンス契約”PATCH /items/:id(col、value):
| ケース | レスポンス |
|---|---|
| 受理 | 200 + レコード <tbody> — 行のみ。セルは値のサーバーによる整形を表示。楽観的コミットを確定し、data-pending をクリアし、以前のエラー行があればアトミックに取り除きます |
| 拒否 | 422 + レコード <tbody> — セルはサーバーの現在値に戻り、data-invalid + aria-invalid + aria-describedby 付き。続けて role="alert" のメッセージが拒否された入力を名指しする __error-row |
| 確認が必要 | 200 + レコード <tbody> を確認待ち状態で — 下記参照。まだ何もコミットされていません |
| 未知の行 / 列 | 404 — 何もスワップしない。標準のエラートーストがカバー |
キャンセルにはもう 1 つの経路が要ります: GET /items/:id → 保存済みの
レコードをそのまま返します。何も書き込んでいないので、取り消すものは
ありません。
レコード tbody がアトムです: 行とエラー行は常に一緒に移動します — OOB の帳簿づけなし、ステートレスなサーバー、そしてセルがサーバーの 保証しない値を表示することはありません。
確認付きの警告
Section titled “確認付きの警告”値としては受理可能だが異常なものがあります: 未来日の出荷日、
規定を超える割引、通常の 10 倍の数量。これらはエラーではありません —
422 は「直す必要のないものを直せ」と言うことになります — そして
ルールを知っているのはサーバーだけなので、クライアント側の確認
(installConfirm)では表現できません。ルールは送信した後で、
ユーザーが既に確定した後に判明するからです。
正直な答えは 200 です。何も失敗しておらず、何も拒否されていません。
サーバーは会話を続けているだけです。htmx:beforeSwap の許可も不要に
なります。
<td class="hc-datagrid__cell" data-editable data-col="ship" data-value="2027-01-01" data-attention="warning" aria-describedby="item-2-note">2027-01-01</td>…<tr class="hc-datagrid__error-row"> <td class="hc-datagrid__error" data-tone="warning" colspan="3"> <span role="alert" id="item-2-note">2027-01-01 は未来日です。この日付で Chang を出荷する場合は確認してください。</span> <button class="hc-button" data-size="sm" data-variant="primary" type="button" data-hx-patch="/items/2" data-hx-vals='{"col":"ship","value":"2027-01-01","confirm":"9f2c1a"}' data-hx-target="closest tbody" data-hx-swap="outerHTML">確認して更新</button> <button class="hc-button" data-size="sm" type="button" data-hx-get="/items/2" data-hx-target="closest tbody" data-hx-swap="outerHTML">取り消す</button> </td></tr>セルは提案値を表示します — 見えないものは確認できません — そして
data-attention="warning" でマークされます。レコードにも同じ属性を
付けるので、上にどのティントが塗られても行は「対応が必要」と読めます。
トークンは値に束縛します。confirm は 1 つの
(行・列・値) — バージョン管理されたストアではバージョンも含めて —
に対して発行される単回限りのトークンです。この束縛がないと、ある値に
対して得た確認が別の値をコミットしてしまい、409 のバージョンガードも
リプレイで回避されます。confirm=1 だけを見るサーバーは、確認ではなく
混乱した代理人を作ったことになります。ボタンは 静的な
data-hx-vals(js: ではない)を使うので、確認対象の値は描画時に
固定され、CSP にも安全です。
一括操作は既に同じ形を持っています: datagrid-bulk-errors のプリフライト(「18 件は実行可能、2 件は不可」)は、多数の行に対する 同じ会話です。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”インラインセル編集自体が JavaScript のエンハンスメントです。no-JS の 経路は inline-edit レシピのページレベルフォームです。グリッドはスクリプトなしでも描画・ 閲覧できます。
アクセシビリティ
Section titled “アクセシビリティ”- メッセージは内側要素の
role="alert"で読み上げられます(セルはgridcellロールを保持)。拒否されたセルはaria-invalid+aria-describedbyでリンクされます。 - 保存中は 1 セルの
aria-busyだけ — グリッドの残りは操作可能な まま。エラー行はキーボードナビゲーションに入りません。 - フォーカスはスワップを生き延び、Enter でその場で 再編集できます。
- 確認行はナビゲーション行列の外にあるため、中のボタンは通常のタブ順を
保ちます。
role="alert"はフォーカスを奪わずに問いを読み上げます。
- データグリッド —
このレシピが駆動する編集ライフサイクル状態(
data-pending、data-invalid、エラー行スロット)。 - field-errors — フォーム全体のための同じ 422 哲学。
- inline-edit — 単一フィールド click-to-edit の兄弟。