ページ番号が省略されるようなページング実装方法（Thymeleaf）

# １ はじめに
Spring+Thymeleafでのページング処理方法を調べてみると、下記のようなページングの実装例は簡単に見つかる。
![oldPaging.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640342/6da5fe54-a15e-6200-9ce9-149606873063.png)
このページングの実装は、下記の記事を参考にさせていただいた。
[Spring Boot + Thymeleafでページング機能を実装する](https://qiita.com/KevinFQ/items/ca68a3001bae19f92879)

しかし、この実装の内容だと**ページ数が増えた場合に横にページ番号を増やし続けてしまう。**
（自分でカスタマイズしろ！というお話なのだが・・・）

そこで、次のようなページ番号が増えた場合には「...」で省略するページングを実装してみたので共有する。
（JavaScriptを使用してのページングを実装する方法もあるそうなのだが、今回はThymeleafのみで実装した。）
![newPaging1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640342/43b97481-a54f-c141-96f2-db4d716ca220.png)

# ２ 実装例
Thymeleafのコーディング例はこちら

```html:pagination.html
<ul class="center padding-l-0">
		<li th:class="inline">
			<span th:if="${page.first}">&lt;</span>
			<a th:if="${not page.first}" th:href="@{${url}(page=${page.number-1})}">&lt;</a>
		</li>
		<!--/* 全ページ数が９以下の時は、全ページ番号を表示 */-->
		<li th:if="${page.totalPages <= 9}" th:each='i : ${#numbers.sequence(0, page.totalPages-1)}' th:class="inline">
			<span th:if='${i}==${page.number}' th:text='${i+1}'></span>
			<a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
				<span th:text='${i+1}'></span>
			</a>
		</li>
		<!--/* 全ページが９以上の時 */-->
		<!--/* 1~5ページの時は後ろに「...」をつける */-->
		<li th:if="${page.totalPages > 9 && page.number < 5}" th:each='i : ${#numbers.sequence(0, 8)}' th:class="inline">
			<span th:if='${i}==${page.number}' th:text='${i+1}'></span>
			<a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
				<span th:text='${i+1}'></span>
			</a>
		</li>
		<li th:if="${page.totalPages > 9 && page.number < 5}" class="inline">
			<span th:text="..."></span>
		</li>
		<!--/* (6~TotalPage数-5)ページの時は前後に「...」をつける */-->
		<li th:if="${page.totalPages > 9 && 4 < page.number && page.number < page.totalPages - 5}" class="inline">
			<span th:text="..."></span>
		</li>
		<li th:if="${page.totalPages > 9 && 4 < page.number && page.number < page.totalPages - 5}" th:each='i : ${#numbers.sequence(page.number-4, page.number+4)}' th:class="inline">
			<span th:if='${i}==${page.number}' th:text='${i+1}'></span>
			<a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
				<span th:text='${i+1}'></span>
			</a>
		</li>
		<li th:if="${page.totalPages > 9 && 4 < page.number && page.number < page.totalPages - 5}" class="inline">
			<span th:text="..."></span>
		</li>
		<!--/* (TotalPage数-5)以上ページの時は前方に「...」をつける */-->
		<li th:if="${page.totalPages > 9 && page.number >= page.totalPages-5}" class="inline">
			<span th:text="..."></span>
		</li>
		<li th:if="${page.totalPages > 9 && page.number >= page.totalPages-5}" th:each='i : ${#numbers.sequence(page.totalPages-9, page.totalPages-1)}' th:class="inline">
			<span th:if='${i}==${page.number}' th:text='${i+1}'></span>
			<a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
				<span th:text='${i+1}'></span>
			</a>
		</li>
		<li th:class="inline">
			<span th:if="${page.last}">&gt;</span>
			<a th:if="${not page.last}" th:href="@{${url}(page=${page.number+1})}">&gt;</a>
		</li>
	</ul>
```

コード中のpageオブジェクトに検索結果が格納されている。
（pageオブジェクトに検索結果を渡す処理（ControllerやServiceなど）のコーディング例は参考情報として文末に記載しておく）

## 条件分岐について

全ページ数（page.totalPages）や現在閲覧しているページ番号（page.number）などを使用して、下記4パターンにif条件で分岐させている。
ここでは説明のために、全ページ数をN、現在閲覧しているページ番号をxと表記することにする。

### パターン１（どのページ番号も省略しない）
![oldPaging.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640342/ddf69d3b-0f44-4124-94ae-28a52babebf5.png)
このパターンは`N<=9`の時に発生する。
### パターン２（後方に...がつく）
![newPaging1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640342/f1745874-e0ab-0390-79e7-a7b29cf51ef2.png)
このパターンは`N>9 かつ 1<=x<=5`の時に発生する。
### パターン３（前方、後方に...がつく）
![スクリーンショット 2021-03-08 19.07.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640342/4bfa8ade-da09-0afc-f046-f15a049a6a22.png)
このパターンは`N>9 かつ 6<=x<=N-5`の時に発生する。
### パターン４（前方に...がつく）
![スクリーンショット 2021-03-08 19.07.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640342/04448071-7897-fa2f-fe1f-066d9f3f8230.png)
このパターンは`N>9 かつ N-5<x`の時に発生する。

---

上記のような分岐になっている。
ここでは上記のような条件分岐をさせているために、ページ番号として表示されるのは最大9件までとなっており、現在閲覧しているページから最大前後４ページまでが表示されるようになっている。
（つまり、８ページ目を閲覧している時には4~12のページ番号が表示される）
**なので、表示させるページ数を増やしたい場合は上記の条件分岐に手を加えていただければ実装可能となっている。**

また、このような分岐以外にも条件分岐をさせている。
例えば、現在見ているページ（page.number）のページ番号を表示する時にはaタグではなくspanタグで表記するような条件を記載している。
また、同様に次ページ前ページボタン（<,>）についても現在見ているページ（page.number）が最初のページ（page.firstがtrue）もしくは最後のページ（page.lastがtrue）ならaタグではなくてspanタグで表記するような条件としている。


# 参考情報（Controller、Service、Repositoryの実装例）
### Controller

```java:ItemSearchController.java
@Controller
public class ItemSearchController {

	// アイテム検索サービス
	@Autowired
	private ItemSearchService itemSearchService;

	@RequestMapping(value = "/")
	public ModelAndView searchAllItem(ModelAndView modelAndView, Pageable pageable) {
		// 全アイテムの検索
		Page<Item> itemListPage = itemSearchService.searchAllItem(pageable);

		// htmlに値を渡す
		modelAndView.addObject("page", itemListPage);
		modelAndView.addObject("itemList", itemListPage.getContent());
		modelAndView.addObject("url", "/");

		// 遷移先：アイテム検索ページ
		modelAndView.setViewName("item_search");

		return modelAndView;
	}
}
```

### Service

```java:ItemSearchService.java
@Service
public class ItemSearchService {

	// アイテムリポジトリ
	@Autowired
	private ItemRepository itemRepository;

	/**
	 * 全アイテム検索メソッド
	 * @return 全アイテムリスト
	 */
	public Page<Team> searchAllItem(Pageable pageable) {
		// 全チームを検索する
		return itemRepository.findAllItem(pageable);
	}
}
```

### Repository

```java:ItemRepository.java
@Repository
public interface ItemRepository extends JpaRepository<Item, Integer> {

	// アイテムを検索する
	@Query(value = "SELECT * FROM item", nativeQuery = true)
	public Page<Item> findAllItem(Pageable pageable);
}
```