0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ページングメモ

Posted at

【結論】全件を一度に渡し、ThymeleafでJSON埋め込み → Wijmo CollectionView(pageSize) → MultiRowにバインド、Prev/Nextだけの超ミニマル構成で「必要最低限のページング」を実装する。

【要点】

  • DTOは表示用に絞る:id・表示名など必要項目のみ(画像列はあればimageId程度)。
  • Thymeleafで初期データ埋め込みth:inline="javascript"itemspageSize をJS変数化。
  • クライアント側だけでページングCollectionView({ pageSize }) を作り、MultiRowitemsSource に渡す。
  • UIはPrev/Nextとページ情報のみ:余計なコンポーネントは不使用。pageIndex/pageCount に連動してボタン活性/非活性。
  • MultiRowは2行レイアウトの最小定義rowsPerItem: 2layoutDefinition だけ。並べ替え・複数選択などはオフ。

【例】

1) DTO(表示用最小)

// 表示用DTO:必要な列だけ(必要に応じて項目を追加)
public record ItemDto(
    Long id,
    String code,
    String name,
    String category,
    Integer price,
    Long imageId // 画像を表示するならAPIに渡すID
) {}

※Controller 側(参考・任意):
model.addAttribute("items", itemService.findAllAsDto());
model.addAttribute("pageSize", 20);


2) Thymeleaf + HTML(最小ページ)

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8" />
  <title>MultiRow Paging (Minimal)</title>
  <style>
    .toolbar { display:flex; gap:.5rem; align-items:center; margin: .5rem 0; }
    .toolbar button:disabled { opacity:.4; cursor:not-allowed; }
    #grid { height: 520px; }
  </style>

  <!-- Wijmo(手元の導入パスに置き換え) -->
  <link rel="stylesheet" href="/lib/wijmo/styles/wijmo.css">
  <script src="/lib/wijmo/wijmo.min.js"></script>
  <script src="/lib/wijmo/wijmo.grid.min.js"></script>
  <script src="/lib/wijmo/wijmo.grid.multirow.min.js"></script>
</head>
<body>

  <div class="toolbar">
    <button id="prevBtn">前へ</button>
    <span id="pageInfo"></span>
    <button id="nextBtn">次へ</button>
  </div>

  <div id="grid"></div>

  <!-- 初期データをThymeleafでJSへ埋め込み -->
  <script th:inline="javascript">
  /*<![CDATA[*/
    const items = /*[[${items}]]*/ [];     // List<ItemDto> がJS配列として展開される
    const PAGE_SIZE = /*[[${pageSize}]]*/ 20;
  /*]]>*/
  </script>

  <!-- 最小JS(後述のJSと同等。別ファイル化してもOK) -->
  <script>
  (function () {
    // 1) ページング用コレクション(全件いっぺんに受け取り、画面側のみで分割)
    const view = new wijmo.collections.CollectionView(items, { pageSize: PAGE_SIZE });

    // 2) MultiRowのレイアウト(2行に分けて最小限の表示)
    const layoutDefinition = [
      { cells: [
        { binding: 'id',       header: 'ID',       width: 80 },
        { binding: 'code',     header: 'コード',    width: 140 },
        { binding: 'name',     header: '名称',      width: 240, colspan: 2 }
      ]},
      { cells: [
        { binding: 'category', header: 'カテゴリ', width: 160 },
        { binding: 'price',    header: '価格',     width: 120, format: 'n0', align: 'right' },
        // 画像を載せるなら itemFormatter を使って <img> を差し込む最小構成では割愛
      ]}
    ];

    // 3) MultiRow本体
    const grid = new wijmo.grid.multirow.MultiRow('#grid', {
      itemsSource: view,
      layoutDefinition,
      rowsPerItem: 2,
      selectionMode: wijmo.grid.SelectionMode.Row,
      allowSorting: false,       // 余計な機能はオフ(必要ならtrue)
      alternatingRowStep: 0      // 見た目の交互色を消したい場合(任意)
    });

    // 4) Pager(Prev/Nextだけ)
    const prevBtn  = document.getElementById('prevBtn');
    const nextBtn  = document.getElementById('nextBtn');
    const pageInfo = document.getElementById('pageInfo');

    function syncPager() {
      pageInfo.textContent = `${view.pageIndex + 1} / ${view.pageCount} ページ(全 ${view.totalItemCount} 件)`;
      prevBtn.disabled = view.pageIndex <= 0;
      nextBtn.disabled = view.pageIndex >= view.pageCount - 1;
    }
    prevBtn.addEventListener('click', () => { if (view.pageIndex > 0) view.moveToPage(view.pageIndex - 1); });
    nextBtn.addEventListener('click', () => { if (view.pageIndex < view.pageCount - 1) view.moveToPage(view.pageIndex + 1); });

    view.collectionChanged.addHandler(syncPager);
    view.pageChanged.addHandler(syncPager);
    syncPager();
  })();
  </script>
</body>
</html>

3) 画像列を足したい場合(任意の最小追加)

// grid 生成時の options に itemFormatter を追加(最低限)
itemFormatter: (panel, r, c, cell) => {
  const Col = wijmo.grid.CellType;
  if (panel.cellType !== Col.Cell) return;
  const col = panel.columns[c];
  if (col.binding === 'imageId') {
    const it = panel.rows[r].dataItem;
    // サムネをオンザフライで軽く(必要に応じてw/h調整)
    cell.innerHTML = `<img src="/api/images/${it.imageId}?w=64&h=64" alt="" style="width:64px;height:64px;object-fit:cover;border-radius:4px">`;
  }
}

画像が要らなければ、この項はスキップでOK。必要最低限を崩さないために分離しています。


【用語】
ThymeleafのJSインラインth:inline="javascript" を付けた <script> 内で /*[[${var}]]*/ 形式にすると、サーバ側のオブジェクトが安全にJSリテラルとして埋め込まれる。
CollectionView.pageSize=クライアント側でデータをページに分割するWijmoの仕組み。サーバ呼び出しは発生しない。
MultiRow=1件を複数行にレイアウトするグリッド。rowsPerItemlayoutDefinition が肝。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?