Thymeleafの`th:each`で行番号やindexを使いたいときは、反復変数の後ろに**status変数**を追加します。`th:each="user, stat : ${users}"`と書けば、`stat.index`は0始まり、`stat.count`は1始まりです。画面に「1、2、3…」と行番号を出すなら`count`、0-basedの位置が必要なら`index`を使う、と覚えると迷いません。
最初に整理:indexは0始まり、countは1始まり
| プロパティ | 意味 | よく使う場面 |
|---|---|---|
| stat.index | 0から始まる現在位置 | 0-based位置が必要な処理 |
| stat.count | 1から始まる現在位置 | 画面の行番号・連番表示 |
| stat.size | 総要素数 | 現在位置 / 総件数の表示 |
| stat.current | 現在の反復要素 | status側から現在要素を参照 |
| stat.even / stat.odd | 現在の反復が偶数か奇数か | 交互のCSSクラスや表示条件 |
| stat.first | 最初の要素か | 先頭だけ表示を変える |
| stat.last | 最後の要素か | 末尾だけ表示を変える |
Thymeleaf 3.1.5の公式チュートリアルでは、status変数に`index`、`count`、`size`、`current`、`even/odd`、`first`、`last`が用意されています。status変数は反復変数の後ろへカンマ区切りで定義し、`th:each`を持つ要素とその内部で利用できます。
用語解説:status変数
th:eachの現在位置や総件数、偶奇、先頭・末尾など、繰り返し処理の状態を保持する変数です。
表に1始まりの行番号を表示する
一覧表で利用者に見せる行番号なら、0始まりの`index`より1始まりの`count`が自然です。
<table>
<tr th:each="user, stat : ${users}">
<td th:text="${stat.count}">1</td>
<td th:text="${user.name}">山田</td>
</tr>
</table>
1件目では`stat.index`が0、`stat.count`が1です。表示上の連番と0-based位置を混同しないことがポイントです。
indexを使うのは0-basedの位置が必要なとき
`index`は現在の要素が何番目かを0から数えます。0-basedの添字と対応させたい場合など、位置そのものを0始まりで扱う目的に向きます。
<ul>
<li th:each="item, stat : ${items}">
<span th:text="${stat.index}">0</span>
<span th:text="${item.name}">Item</span>
</li>
</ul>
even・oddで交互のクラスを付ける
status変数には偶数・奇数を判定する`even`と`odd`もあります。行ごとにクラスを切り替えたい場合、Controller側でフラグを作る必要はありません。
<tr th:each="user, stat : ${users}"
th:classappend="${stat.odd} ? 'row-odd' : 'row-even'">
<td th:text="${stat.count}">1</td>
<td th:text="${user.name}">山田</td>
</tr>
first・lastで先頭と末尾だけ表示を変える
<div th:each="item, stat : ${items}">
<span th:if="${stat.first}">先頭</span>
<span th:text="${item.name}">Item</span>
<span th:if="${stat.last}">末尾</span>
</div>
`first`と`last`は、現在の反復が最初・最後かを真偽値で返します。先頭や末尾だけ注記を出す表示に使えます。
size・currentと暗黙のStat変数
総件数は`size`、現在の反復要素は`current`です。またstatus変数を明示しない場合、Thymeleafは反復変数名の末尾へ`Stat`を付けた変数を自動作成します。`user`なら`userStat`です。
<ul>
<li th:each="user : ${users}">
<span th:text="${userStat.count + ' / ' + userStat.size}">1 / 3</span>
<span th:text="${user.name}">山田</span>
</li>
</ul>
短いテンプレートでは便利ですが、ネストした繰り返しでは明示的なstatus変数名を付けた方が追いやすくなります。
ネストしたth:eachではstatus変数名を分ける
<section th:each="category, categoryStat : ${categories}">
<h2 th:text="${category.name}">Category</h2>
<ul>
<li th:each="item, itemStat : ${category.items}">
<span th:text="${categoryStat.count}">1</span>
<span th:text="${itemStat.count}">1</span>
<span th:text="${item.name}">Item</span>
</li>
</ul>
</section>
外側と内側で`categoryStat`、`itemStat`のように別名を付けると、どちらのindex/countを参照しているか明確です。
Thymeleafの基本構文と式を一緒に確認する
`th:each`以外の基本構文もまとめて確認したい場合は、既存のThymeleaf入門記事が補助になります。
(Thymeleafの基本構文全体については『Thymeleaf入門│Spring Bootで10分実践サンプル5選』をご参照ください)
(Thymeleaf式の記号の違いについては『Thymeleafで「$」「@」「*」はどう違う?初心者でもわかる使い分け完全ガイド』をご参照ください)
よくある質問
行番号を1から表示したい場合はindex + 1でもよいですか?
表示結果としては作れますが、Thymeleafには1始まりの`count`が用意されています。単純な行番号なら`stat.count`の方が意図を直接表せます。
status変数を必ず宣言する必要がありますか?
必須ではありません。明示しない場合は反復変数名に`Stat`を付けた暗黙のstatus変数が作られます。
indexとcountは何が違いますか?
`index`は0始まり、`count`は1始まりです。0-based位置が必要ならindex、画面上の連番ならcountが分かりやすい選択です。
まとめ:表示行番号はcount、0-based位置はindex
- `stat.index`は0始まり、`stat.count`は1始まり
- 行番号表示ならcount、0-based位置ならindexを使う
- size/current/even/odd/first/lastも同じstatus変数から取得できる
- status変数を省略すると反復変数名 + Statが自動生成される
- ネストしたループではstatus変数名を分ける
まず手元の`th:each`へ`, stat`を追加し、行番号なら`stat.count`、位置確認なら`stat.index`を表示して挙動を確かめてみてください。