CSV インポート
一括取り込みに欠けていたワイヤ契約です。フェーズ 1:
file-upload の
フォームが CSV を POST し、サーバーはインポートせずに解析・検証
します — 応答は検証レポートです: サマリー行、本物のエラーテーブル、
そして(インポート可能な行があるときだけ)サーバー側バッチを参照する
隠し token を持つ確認フォーム。フェーズ 2:
POST /imports/<token>/commit の確認が、検証されたとおりを正確に
実行します。再アップロードはバッチを置き換えます。新規 JavaScript
ゼロ。バージョニングポリシーの
対象です。
別名: 一括インポート、一括取り込み。
これを items.csv として保存して(最後の 2 行はわざと壊してあります)
アップロードしてください — レポートが有効な行のインポートを提案し、
残りをエラーテーブルに並べます。コミットすると結果が表示され、
トーストが上がります:
name,qtyAnvil,3"Widget, small",12Sprocket,zero,4マークアップ
Section titled “マークアップ”<form id="csv-upload" method="post" action="/imports" enctype="multipart/form-data" data-hx-post="/imports" data-hx-encoding="multipart/form-data" data-hx-target="#import-report" data-hx-indicator="find progress" data-hx-disabled-elt="find button[type=submit]"> <div class="hc-field"> <label class="hc-field__label" for="csv">CSV file</label> <input class="hc-input" id="csv" name="csv" type="file" required accept=".csv,text/csv"> </div>
<progress class="hc-progress htmx-indicator" data-hc-upload-progress value="0" max="100" aria-label="Upload progress"></progress>
<button class="hc-button" data-variant="primary" type="submit">Upload</button></form>
<div id="import-report" aria-live="polite"></div>アップロードフォームは file-upload の形そのままです(あのレシピの
両エンコーディング: htmx 用の data-hx-encoding とネイティブ送信用の
enctype)。確認フォームはレポートの中にサーバーがレンダリング
します — /imports/<token>/commit へ POST し、トークンはパスと隠し
input の両方に載り、同じスロットを target にするので結果がレポート
全体を置き換えます。
サーバー応答契約
Section titled “サーバー応答契約”POST /imports(multipart、ファイルフィールド csv)、続いて
POST /imports/<token>/commit:
| ケース | 応答 |
|---|---|
| アップロード、全行有効 | 200 + レポート:「N 行の準備ができました」+ 確認フォーム(隠し token) |
| アップロード、一部の行が不正 | 200 + レポート: スキップ数入りのサマリー、エラーテーブル(Row / Field / Message — 本物の <table>)、「有効な N 行をインポート」確認フォーム |
| アップロード、有効な行なし/読めないファイル | 422 + エラーレポート(またはファイルレベルのエラー行)— 確認フォームなし |
| コミット、生きているトークン | 200 + 結果サマリー + HX-Trigger(hc:toast とドメインイベント、例: items:changed — data-region のリスナーがグリッドを更新) |
| コミット、期限切れ/消費済みトークン | 409 + 再アップロードのヒント — トークンは単発です。直すには常に再アップロードで、リトライではありません(409 は統合された [401, 409, 422] の htmx:beforeSwap 許可に乗ります) |
トークンはサーバー側の解析済みバッチを参照します: コミットは検証 された行を正確に実行します — その後ディスク上のファイルが変わって いても、です。CSV の解析はサーバーの領分です — パーサーは各自の ものを使ってください。ワイヤ契約は変わりません。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”enctype がフォームにあるので、ネイティブの multipart POST は
そのまま機能します。サーバーはフルページのレポートで応え、その中の
確認フォームもネイティブに POST できます(古典的な
post/redirect/get — 実アプリはバッチをサーバー側に持つので
/imports/<token> へ 303 します)。JavaScript なしでもフローは
同一で、1 ページずつ進みます。
アクセシビリティ
Section titled “アクセシビリティ”- レポートスロットは
aria-live="polite"— サマリー、エラー レポート、コミット結果が、フォーカスを奪わずに読み上げられます。 - エラーテーブルは
<caption>、scope="col"ヘッダー、行番号のscope="row"ヘッダーを持つ本物の<table>です。 - プログレスバーは
aria-labelを持ち、アップロード/確認ボタンは リクエスト中は無効化されます。
- file-upload — フェーズ 1 が使い回すアップロードフォームの形(とその checks)。
- toast — コミットが
上げる
HX-Trigger通知。 - data-region — 取り込んだ行が着地するグリッドを更新するドメインイベントの ペアリング。