コンテンツにスキップ

データグリッドのページネーション

hc-datagridページングされたデータのために作られています: データウィンドウはサーバが所有し、htmx が 1 ページ分の行をスワップし、 installDatagrid() がスワップされた行を再初期化します。このレシピは それを hc-pagination ページャーに配線します。

5,000 件のフェイク商品をページ送りしてみてください — 各ページャー リンクはそのページの行だけを <tbody> へスワップし(innerHTML)、 ページャーとステータス行は同じレスポンスで out-of-band に届きます。 エンドポイントはサーバレスポンス契約を実装した名前空間付きデモで、 api/recipes/datagrid-pager/ 配下にあります。(静的な docs サイトの 制約として、デモの tbody は 1 ページ目を load トリガーで取得します。 実サーバは下のとおり 1 ページ目をインラインで描画します。)

IDNameUnit price

Loading…

1 ページ目はサーバサイドで、最初のページに描画します — JavaScript が 走る前からグリッドは満たされています。グリッドの <tbody> をスワップ ターゲットにして、innerHTML(その内側の行)をスワップします: 各ページャーリンクは自分のページを同じ <tbody> へ読み込み、その href="?page=N" は非 JS のパスであり続けます:

<div class="hc-datagrid">
<div class="hc-datagrid__scroll">
<table class="hc-datagrid__table">
<thead class="hc-datagrid__head">
<tr>
<th class="hc-datagrid__headcell" data-frozen data-frozen-edge scope="col">ID</th>
<th class="hc-datagrid__headcell" scope="col">Name</th>
<th class="hc-datagrid__headcell" scope="col">Price</th>
</tr>
</thead>
<tbody class="hc-datagrid__body" id="rows">
<!-- Page 1's rows, rendered by the server: -->
<tr class="hc-datagrid__row">
<th class="hc-datagrid__cell" data-frozen data-frozen-edge scope="row">1</th>
<td class="hc-datagrid__cell">Chai</td>
<td class="hc-datagrid__cell">$18.00</td>
</tr>
<!-- … -->
</tbody>
</table>
</div>
<nav class="hc-pagination" id="pager" aria-label="Pagination">
<a class="hc-pagination__item" aria-current="page"
data-hx-get="/products?page=1" data-hx-target="#rows" data-hx-swap="innerHTML" href="?page=1">1</a>
<a class="hc-pagination__item"
data-hx-get="/products?page=2" data-hx-target="#rows" data-hx-swap="innerHTML" href="?page=2">2</a>
</nav>
</div>

なぜ outerHTML ではなく innerHTML

Section titled “なぜ outerHTML ではなく innerHTML か”

行を tbody の内側でスワップする(innerHTML)と、#rows 要素 そのものがスワップをまたいで安定します — その id と、要素が運ぶ 配線はページ読み込みのたびに生き残り、レスポンスは行だけで済みます。 <tbody> 全体の置き換え(outerHTML)は、ターゲットの属性を一言一句 再現する責任をすべてのレスポンスに負わせます: id="rows" を書き 忘れたフラグメント 1 つで、以後のページ送りが黙って壊れます。だから #rows をターゲットに innerHTML でスワップします — installDatagrid() はどちらのスワップ後でも ARIA roles、sticky オフセットの計測、リサイズ済みの列幅を読み込まれたばかりの行へ 再適用します。

したがってサーバでレイアウトを再計算する必要はありません: 各行を ヘッダーと同じ列構造で描画すれば(固定セルに data-frozen / data-frozen-edge、リサイズ / 編集可能な列に data-col)、固定列の --hc-datagrid-left オフセットとリサイズ済みの幅は自動で再適用され ます。

GET /products?page=N&size=100そのページの行だけを返します:

<tr class="hc-datagrid__row">
<th class="hc-datagrid__cell" data-frozen data-frozen-edge scope="row">101</th>
<td class="hc-datagrid__cell">Chai</td>
<td class="hc-datagrid__cell">$18.00</td>
</tr>
<!-- …one <tr> per row in the page… -->

(この例は最小の 3 列グリッドです。グリッドに追加の列がある場合 — ライブデモの行は固定の選択チェックボックスセルで始まります — すべての <tr> がヘッダーと同じ順序で全列を持つ必要があります。)

ページャーとステータス行は同じレスポンスで out-of-band に更新し、 2 回目のリクエストなしで最新にします:

<nav class="hc-pagination" id="pager" data-hx-swap-oob="true" aria-label="Pagination">
…items, with aria-current="page" on the active page…
</nav>
<p id="rows-status" data-hx-swap-oob="true" aria-live="polite">101–200 / 5,000</p>

現在のページには aria-current="page" を付け、両端では Prev / Next を aria-disabled="true" で無効化してください。

リクエストレスポンス
GET /products?page=2&size=100(htmx、HX-Request: true)200 + そのページの <tr> 行 + OOB のページャー / ステータスフラグメント
GET /products?page=2(HX-Request なし — href フォールバック)200 + 2 ページ目の行をサーバレンダリングしたフルページ
GET /products?page=2(失敗)非 2xx — スワップされず、現在の行とページャーが残ります

htmx ≥ 2 は非 2xx レスポンスをスワップしないため、ページ読み込みが 失敗しても現在の行とページャーはそのまま残ります。失敗はエラー レスポンスの HX-Trigger: {"hc:toast":{…}} ヘッダーで知らせてください (トーストレシピを参照)。

プログレッシブエンハンスメント

Section titled “プログレッシブエンハンスメント”
  • 1 ページ目はサーバレンダリングなので、JavaScript がなくても グリッドは完全です — 初期行のためのクライアント取得はありません。
  • すべてのページャーリンクは本物の href="?page=N" を保ちます。 htmx なしでは、そのページへの通常のフルページナビゲーションになり ます。
  • サーバは HX-Request ヘッダーで分岐します: あれば行フラグメント
    • OOB のページャー / ステータスを返し、なければそのページの行を <tbody> に入れたフルページを描画します。
  • ページャーは本物のリンクからなる <nav aria-label="Pagination"> です。現在ページは aria-current="page" で示し、端では Prev / Next を aria-disabled="true" で無効化してください。
  • out-of-band のステータス行(101–200 / 5,000)は aria-live="polite" を持つため、ページの変化はユーザーを遮らずに 読み上げられます。
  • フォーカス。 行のスワップは直前のアクティブセルを取り除きます。 グリッドはタブ可能なセルを保ちますが、フォーカスは移動しません。 フローが必要とするなら、サーバから(out-of-band のフォーカス ターゲットなどで)フォーカスを復元してください。
  • 選択はページ単位です。サーバが選択済み行を aria-selected="true" つきで再描画しない限り、ページをまたいで 保たれません — ページ横断の選択はサーバ側で追跡してください。
  • 複数行レコード。 この形は標準の 1 <tbody> 行レイアウトが 対象です。複数行レコード では、代わりに包んでいる領域をスワップし、document のオブザーバーに 再初期化させてください。