コンテンツにスキップ

編集競合

同じレコードを 2 人が開いたとき、遅いほうの保存が速いほうを黙って 食べてはいけません。このレシピは純粋な合成としての楽観ロックです: 隠し version フィールド、HX-Retargetsession-expiry レシピと同じ共有ダイアログホストへ操縦される 409、そして通常の htmx でフローを完結させるダイアログのボタン群。新規 JavaScript ゼロ、新規公開 API ゼロ。 バージョニングポリシーの 対象です。

別名: 楽観ロック、排他制御、更新競合。

デモのフォームは v12 からレンダリングされ、レコードは v13 にあります — Save は意図的に必ず競合します。Overwrite はダイアログの新しい version で強制し、Reload はあなたの編集を破棄します(その後はきれいに保存 できます)。

This form was rendered from v12; the record is at v13 — Save to see the conflict.

<form id="ticket-form" data-hx-put="/tickets/7"
data-hx-target="#status" data-hx-swap="innerHTML">
<input type="hidden" name="version" value="12">
<div class="hc-field">
<label class="hc-field__label" for="title">Title</label>
<input class="hc-input" id="title" name="title" value="Restock the beans">
</div>
<p class="hc-field__hint" id="status" aria-live="polite"></p>
<button class="hc-button" data-variant="primary" type="submit">Save</button>
</form>
<div id="error-dialog" data-hc-remote-dialog-root></div>
ケース応答
現在の version での PUTmutating-form の成功。フラグメントが新しい version を運びます
古い version での PUT409 + HX-Retarget: #error-dialog + HX-Reswap: innerHTML + 競合ダイアログ: 相手/自分の表、Overwrite(…?force=1、あなたのフィールド + ダイアログ自身の新しい隠し versiondata-hx-include)、Reload(GET …/edit でフォームを outerHTML スワップ)、編集を続ける <form method="dialog">。操作ボタンは data-hc-close-dialog-on-success 付き
force=1 + 新しい version での PUT上書きが勝ちます。その間にさらに動いたレコードは再び競合します
GET …/edit現在の version でフォーム全体を再レンダリング
no-JS同じ 2 択を提示する完全な 409 ページ(PRG)

ダイアログの version フィールドは競合した瞬間の現在値です — それで強制することは「v13 を見た上で上書きを選んだ」という意味に なります。負けたフォームから来ることはありません。

  • 本物の <dialog>(showModal() — ネイティブのフォーカストラップと Escape)、aria-labelledby が名前を与えます。相手/自分の差分は 行・列見出しを持つ本物のテーブルです。
  • 結果はフォームの aria-live ステータス枠に着地します。