[C#] ページングを、ページ表示を整理してみた

今リスト表示をするプログラムを書いています、そこでページングの扱いがちょっと複雑になったので整理してみました。

# 今回のシナリオ
前提として大きなレコード一覧から一部分をページとして切り出して表示するモノを想定しています。

ここでは例として、レコード数が100,000だとします。そして、1ページに100表示するとします。そうすると全部で1,000ページとなります。

## 利用されるであろう各変数
ここで各値を格納する変数を決めます。

レコード数を`totalItems`とします、今回は100,000。
現在のページ`currentPage`とします、ここでは1。
1ページに表示するアイテム数を`itemsPerPage`とします、今回は100。
ページ数を`totalPages`とします、ここでは1,000。
表示するページの数を`displayPageMax`とします。
一番初めに表示するページ数を`startingPage`とします。
一番初めに表示するページの前のページを`prevPage`としてます。
一番最後に表示するページ数を`endingPage`とします。
一番最後に表示するページ数の次のページを`nextPage`とします。

## 1,000ページも表示したら見ずらい
ページの下にはこんなのを想定しています。ここに1,000ものページを表示するのは量が多すぎるのでいったん10で切ります。 `displayPageMax=10`とします。

# イメージ

雑なイメージとして下記を想定。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/5f997ac5-0343-ad66-2ff3-4822e6d09fa6.png)

今のページが1なので、「前へ」は不要となりますので、こんなのを想定します。また現在のページが1であることがわかるようにします。HTMLであればリンクなしの状態にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/93125bae-282c-d23a-5a98-05a787695171.png)

Googleだとこんな感じになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/8101c2d6-dc19-7ca3-66cd-32e09f043815.png)

## ケース1 最初の方
1-5の間はこのまま表示を崩さず進めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/aad9f43d-8139-6283-578d-0fb30a319da5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/030ae267-0cec-4b21-355f-7cb8b6b8cdd6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/0fb54f5c-8a9e-621c-3729-f238f3d12ebe.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/164b5026-e163-d120-f664-721065840b89.png)

そして6になったときは下記のように表示を2-11にして6が真ん中になるようにします。6から見て+-4-5ページ遷移できるようにします。今回は1,000ページありますので、ここでは問題ないのですが、もしここで11ページまでしかないときは、「次へ」が不要になります。そして1ページ目が非表示となったので「前へ」を表示させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/a8dc7c47-1eca-c352-6c13-57466b6d8705.png)

## ケース2 最後の方
今回は1,000ページですが、もしペース数が11の場合、ページ7にいるときは下記のようになっていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/ee451426-3068-e44e-6bd0-720e8d47138b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/8f3f7a6e-41a5-ab7b-498f-851ebac9bdea.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/568a24b6-10fb-7c93-0667-90378dc2bc62.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/f6a6f858-707f-7eca-337d-1b00b97c940b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/17eaa879-aaa4-b0c1-1332-8fc360749869.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241909/90a1d5a2-d5e4-d1b3-69e9-ae536e71eec9.png)

# 一旦整理

ここまでの分岐点を整理します。

## 分岐1 最初の方
startingPage ～ endingPageの表示ロジック
表示するMAX表示数は10

## 分岐2 最後の方
1(startingPage）～ MAX表示数の半分までは、1(startingPage)～MAX表示数(endingPage)を表示したままcurrentPageだけ動かす。

## 分岐3 それ以外
この逆にトータルページ数-MAX表示数の半分まで～トータルページ数まではトータルページ数-MAX表示数(startingPage)～トータルページ数(endingPage)を表示してcurrentPageだけ動かす。

この二つの条件のとき以外は、currentPage-4をstartingPageとして currentPage+5をendingPageとする。

## 分岐4 前へと次へ

前へと次への条件
startingPageが1以外であればstartingPage-1を前へとする
endingPageがトータルページ数以外であればendingPage+1を次へとする

## 分岐5 そもそもtotalPagesがendingPageより小さい場合
ページングがいらないパターン

# コード

これをASP.NET CORE/C# で書くと下記になりました。

```html

@if (Model.SearchResult.TotalPages > 0)
{
   int displayPageMax = 10;
   int startingPage = 1;
   int endingPage = displayPageMax;

   if (displayPageMax < Model.SearchResult.TotalPages){
      if (Model.SearchResult.CurrentPage < (displayPageMax / 2))
      {
         // Then starting page is 1, and show up to displayPageMax.
         startingPage = 1;
         endingPage = displayPageMax;
      }
      else if (Model.SearchResult.CurrentPage > (Model.SearchResult.TotalPages - (displayPageMax / 2)))
      {
         // Then starting page is Model.SearchResult.TotalPages - displayPageMax
         startingPage = Model.SearchResult.TotalPages - displayPageMax;
         endingPage = Model.SearchResult.TotalPages;
      }
      else
      {
         startingPage = Model.SearchResult.CurrentPage - ((displayPageMax / 2) - 1);
         endingPage = Model.SearchResult.CurrentPage + ((displayPageMax / 2));
      }
   }
   else{
      // If TotalPages is less than displayPageMax then don't worry so much of what to do display
      endingPage = Model.SearchResult.TotalPages;
   }

   var nextPage = endingPage + 1;
   var prevPage = startingPage - 1;

   <div class="my-2 d-flex flex-wrap">

   @if (startingPage > 1)
   {
      <div class="m-1" style="min-width:55px;">
         <a href="/@Model.Culture/list?currentPage=@prevPage&keyword=@Model.SearchResult.Keyword" class="btn btn-outline-secondary rounded-0 w-100">
            <i class="fa-solid fa-chevron-left fa-fw"></i>
         </a>
      </div>
   }

   @for (int i = startingPage; i <= endingPage; i++)
   {
      if (i == Model.SearchResult.CurrentPage)
      {
         <div class="m-1" style="min-width:55px;">
            <a href="" class="btn btn-outline-secondary rounded-0 w-100 disabled">
               @i
            </a>
         </div>
      }
      else
      {
         <div class="m-1" style="min-width:55px;">
            <a href="/@Model.Culture/list?currentPage=@i&keyword=@Model.SearchResult.Keyword" class="btn btn-outline-secondary rounded-0 w-100">
               @i
            </a>
         </div>
      }
   }

   @if (endingPage < Model.SearchResult.TotalPages)
   {
      <div class="m-1" style="min-width:55px;">
         <a href="/@Model.Culture/list?currentPage=@nextPage&keyword=@Model.SearchResult.Keyword" class="btn btn-outline-secondary rounded-0 w-100">
            <i class="fa-solid fa-angle-right fa-fw"></i>
         </a>
      </div>
   }
   </div>
}
```

# 最後に

リスト表示して下部にページリンクを表示する、これは定番中の定番なのでここに記載してコピペできるようにしました。(ASP.NET CORE C#に限定ですが、今回の考え方は別の言語でも一緒だと思います）