コンテンツにスキップ

自動保存

未保存ガードは データが失われる前に警告します。自動保存はその警告の出番をほぼ なくします — クラッシュで失うのは最大でもデバウンス 1 回分の入力 です。新規 JavaScript はゼロ: フォーム内のリクエストを持つ <div>、htmx のトリガー修飾子 3 つ、そして小さなドラフト用サーバー 契約だけです。 バージョニングポリシーの 対象です。

別名: 下書き自動保存、オートセーブ。

タイトルに入力して手を止めてください — 2 秒のデバウンス後、 ドラフトのステータス行に時刻が刻まれます。Save はレコードを送信し、 未保存ガードはクリーンに戻ります。

Type in the title, then pause two seconds.

<form id="report" data-hc-dirty-guard
data-hx-post="/reports/42"
data-hx-target="#status" data-hx-swap="innerHTML">
<div class="hc-field">
<label class="hc-field__label" for="title">Title</label>
<input class="hc-input" id="title" name="title" value="Quarterly report">
</div>
<div data-hx-post="/reports/42/draft"
data-hx-include="closest form"
data-hx-trigger="input from:closest form changed delay:2s"
data-hx-target="#draft-status" data-hx-swap="innerHTML"></div>
<p class="hc-field__hint" id="draft-status" aria-live="polite"></p>
<p class="hc-field__hint" id="status" aria-live="polite"></p>
<button class="hc-button" data-variant="primary" type="submit">Save</button>
</form>

修飾子 3 つがループ全体を担います: from:closest form は全フィールドの バブリングする input を聴き、changed はキャレット移動だけの キーストロークを飛ばし、delay:2s がバーストをデバウンスします。 data-hx-include="closest form" がフォーム全体を送信します。空の <div> はリクエストを所有するだけで、それ以外何もしません。

ドラフトは意図的に未保存ガードをクリーンにしません: ガードが リセットされるのはフォーム自身のリクエストが成功したときだけです (htmx:afterRequest の要素同一性) — ドラフトはレコードでは ありません。

ケース応答
POST …/draft(デバウンスされた各バースト)200 + ステータス行(「Draft saved at 14:03:12」— サーバーが時刻印字)。ドラフトは生のまま保存され、検証されません
POST …(レコード保存)mutating-form の契約。成功時にサーバーがドラフトを削除
より新しいドラフトがある状態でのページレンダリングレコードからレンダリングしたフォーム + hc-alert の復元バナー: 復元(GET …/draft → ドラフト値からのフォーム全体、data-dirty 付き)/ 破棄(DELETE …/draft)
DELETE …/draft200 + 空フラグメント
no-JSネイティブの PRG。ドラフトはそもそも発生しません

サーバー側の衛生は契約の規範です: type="password" の値をドラフトに 入れない、ドラフトはユーザー×レコード単位で last-write-wins、 レコード保存時にパージ。

  • #draft-statusaria-live="polite" — メッセージは短く安定させて ください(「Draft saved at …」)。おしゃべりにしないこと。
  • 復元バナーは本物の hc-alert で、DOM 順でフォームの前にあります。