Java入門 / Web入門

【Java Web入門 #17】Thymeleafの利用(4) | Thymeleafの属性

2025.04.04

Thymeleaf のテンプレートでは、HTMLタグに「th:」で始まる属性を設定することで、様々な機能を利用することが可能です。当記事では、Thymeleaf のテンプレートで利用できる属性の種類と動作について、サンプルコードを交えて説明します。

◆Java Web入門の過去記事はこちら
◆Java入門 記事一覧はこちら

Thymeleaf 機能のための属性

Thymeleaf で利用する「th:」で始まる属性には、大まかに分けて「Thymeleaf の機能を利用・制御するもの」「HTML(HTML5)属性の値を編集(置換、追加)するもの」があります。

ここでは、まず Thymeleaf の機能を利用・制御するための属性について説明します。

疑似的なブロック

Thymeleaf では「th:」で始まる書式のほぼ全てがタグに設定する属性として利用されますが、例外的にタグそのものとして利用されるものがあります。それが Thymeleaf の疑似的なブロック(th:block)です。

th:block

th:block は、他の属性のように HTML タグの中に属性として設定するのではなく、疑似的なブロック(要素)として使用されます。

この疑似ブロックを利用することで、<div> などのHTML要素を付け加えることなく、<th:block> ~ </th:block> の子要素を繰り返し処理したり、その他のThymeleafの機能を利用することができます。

ただし th:block タグは、HTMLのタグとしては有効なタグではありません。このため、テンプレートを静的HTMLとして表示できるように、前回の記事で説明した「プロトタイプのみのコメント」と合わせて利用すると効果的です。

以下のサンプルコードは、<th:block> ~ </th:block> の間の4行を、データ(items)の数だけ繰り返し出力します。

<table class="product">
  <!--/*/ <th:block th:each=" item : ${items} "> /*/-->
  <tr>
    <td rowspan="4" th:text="${item.name}">サンプル商品名</td>
    <td class="head">税込価格(円)</td>
    <td class="head">サイズ(高さ x 幅 x 奥行・単位cm)</td>
  </tr>
  <tr>
    <td th:text="${item.price}">99,999</td>
    <td th:text="${item.size}">100 x 40 x 30</td>
  </tr>
  <tr>
    <td class="head"></td>
    <td class="head">材質</td>
  </tr>
  <tr>
    <td th:text="${item.color}"></td>
    <td th:text="${item.material}">合板</td>
  </tr>
  <!--/*/ </th:block> /*/-->
</table>


th:each 属性は「設定した要素(とボディ部)」を繰り返し出力するための属性です。(詳細は後述します)

このため<tr>タグに th:each 属性を設定しても、同じ tr 要素の繰り返しになりますが、このように疑似ブロックで囲んで繰り返し処理の対象とすることで、静的HTMLにもサンプル行を表示しながら「複数行の繰り返し出力」が実現できます。

ボディ部への文字列出力

th:text

th:text 属性は、属性を設定したタグのボディ部に変数の値・式の結果を文字列として出力します。th:text 属性は Thymeleaf の最も基本的な属性です。

以下のサンプルコードでは、<p>~</p>の間に元々記述されていた「ユーザー名」の代わりに、変数 name の値が出力されます。

<p th:text="${name}">ユーザー名</p>


変数 name の値が「knovus」であった場合、上記サンプルの実行時に生成される HTML コードは以下のようになります。

<p>knovus</p>


このように Thymeleaf のテンプレートでは、Webアプリケーションとして実行された際に指定した位置のコンテンツを差し替えることができるため、静的HTMLとしてもそのまま表示できる形でテンプレートを作成することが可能となっています。

th:utext

th:utext 属性は、th:text 属性と同様に、属性を設定したHTMLタグのボディ部に文字列を出力します。

ただし、th:utext 属性を利用して出力する場合は、XML特殊文字(「<」「>」「 " 」「 ' 」「&」)をエスケープせずにそのまま出力します。対して、th:text 属性を利用した場合は、それぞれエスケープ(「< (→ &gt; )」「> (→ &lt; )」「 " (→ &quot; )」「 ' (→ &apos; )」「& (→ &amp; )」)して出力されます。

以下のサンプルコードでは、「<strong>こんにちは!</strong>」という文字列の変数 xmlText を、th:utext 属性および th:text 属性を利用して出力しています。出力されるHTML文字列が異なる=ブラウザ上での表示内容が異なる点に注意してください。

<p th:utext="${xmlText}">ここに出力されます。</p>
<p th:text="${xmlText}">ここに出力されます。</p>


上記のサンプルから出力されるHTMLコードは以下の通りです。

条件分岐

th:if

th:if 属性をHTMLタグに設定した場合、属性値に記述した条件に合致している場合にのみ出力されます。条件に合致しない場合は、th:if 属性を設定した要素およびボディ部は HTML に出力されません。

th:if 属性の判定条件には、真偽値および真偽値を返す式が利用できます。

<!-- isMorning が true の場合のみ表示 -->
<p th:if="${isMorning}">おはようございます。</p>

<!-- age が 20未満の場合のみ表示 -->
<p th:if="${age lt 20}">お酒は未成年には販売できません。</p>


条件に真偽値以外を設定した場合、基本的に null 以外は真(true)と判定されます。但し、数値の 0 および文字列 "false" は偽(false)と判定されます。

th:unless

th:unless 属性は、属性値に設定した条件に合致して「いない」場合にのみ出力を行います。これは、th:if 属性と全く反対の動作であり、th:if 属性で条件に否定(not)を設定した場合と同じ動作となります。

th:if 属性を利用するか th:unless 属性を利用するかは、設定する条件によってわかりやすいものを選択するのが良いでしょう。

<!-- いずれも isMorning が false の場合のみ表示 -->
<p th:unless="${isMorning}">こんにちは。</p>
<p th:if="${not isMorning}">こんにちは。</p>

<!-- いずれも age が 「20以上ではない」=「20未満」の場合のみ表示 --> 
<p th:unless="${age ge 20}">お酒は未成年には販売できません。</p>
<p th:if="${age lt 20}">お酒は未成年には販売できません。</p>

th:switch/th:case

条件によって複数パターンの分岐が必要な場合は、th:switch 属性を利用することができます。各々の条件は th:case 属性を利用して指定します。

一般的には親要素に th:switch 属性、子要素に th:case 属性を設定して利用しますが、疑似ブロック(th:block)を利用することで、並列の要素に対して(親要素なしに)条件指定をすることも可能です。

<!-- 親要素(div)に th:switch を設定 --> 
<div th:switch="${mode}">
  <div th:case="'complete'" th:text="#{msg.complete}">登録が完了しました。</div>
  <div th:case="'error'" th:text="#{msg.regError}">エラーが発生しました。</div>
</div>

<!-- th:block を利用 --> 
<!--/*/ <th:block th:switch="${mode}"> /*/-->
<div th:case="'complete'" th:text="#{msg.complete}">登録が完了しました。</div>
<div th:case="'error'" th:text="#{msg.regError}">エラーが発生しました。</div>
<!--/*/ </th:block> /*/-->

繰り返し処理

th:each

th:each 属性は繰り返し処理を行うための属性です。th:each 属性を設定したHTML要素およびボディ部・子要素が繰り返し出力されます。

繰り返し処理は th:each="[当回のオブジェクト名] : [繰り返し処理の対象オブジェクト]" の書式で記述します。ループごとに、[当回のオブジェクト名]で指定した名前で、繰り返し対象から取り出したオブジェクトを(変数として)利用することができます。

以下のサンプルコードでは、変数 items を対象に繰り返し処理を行い、ループごとに取り出したオブジェクトには item という名前を付けて利用しています。

<ul>
  <li th:each="item : ${items}" th:text="${item.name}">商品名をここに出力</li>
</ul>


Thymeleaf の繰り返し処理で、対象とできるオブジェクトは以下の通りです。これら以外のオブジェクトを対象として繰り返し処理を行った場合は、1回だけ処理されます。

繰り返し処理が可能なもの主なクラス例
配列(なし)
java.util.Iterable の実装クラスArrayListArrayDeque など
java.util.Enumeration の実装クラスStringTokenizer など
java.util.Iterator の実装クラスListSet のイテレータなど)
java.util.Map の実装クラスHashMap など
Map#entrySet の戻り値に対して繰り返し処理される)
java.util.stream.Stream の実装クラス(配列やListSet のストリームなど)


これらのインターフェイスを実装しているクラスは、そのオブジェクトを直接指定することで問題ありません。例えば以下のサンプルコードでは、HashMap のオブジェクトに対して色々なインターフェイスを利用して繰り返し処理を実行していますが、同じ動作となります。

コントローラークラス(EachController.java)

package com.example.demo.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
public class EachController {
	@GetMapping("/each")
	public String each(Model model) {
		
		// th:each で出力するためのMap
		Map<String, String> strMap = new HashMap<>(); 
		strMap.put("name", "knovus");
		strMap.put("age", "21");
		strMap.put("country", "日本");
		strMap.put("pref", "北海道");
		strMap.put("city", "札幌市");
		
		// Map インターフェイス
		model.addAttribute("map", strMap);
		// Iterator インターフェイス
		model.addAttribute("mapIterator", strMap.entrySet().iterator());
		// Stream インターフェイス
		model.addAttribute("mapStream", strMap.entrySet().stream());

		return "each";
	}
}


テンプレートファイル(each.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8">
    <title>th:each Sample Page</title>
  </head>
  <body>
    <!-- Map(HashMap)を利用して出力 -->
    <p>--Map--</p>
    <ul>
      <li th:each=" kv : ${map} ">
        <span th:text="${kv.key}"/><span th:text="${kv.value}"/>
      </li>
    </ul>
    <!-- Iteratorを利用して出力 -->
    <p>--Iterator--</p>
    <ul>
      <li th:each=" kv : ${mapIterator} ">
        <span th:text="${kv.key}"/><span th:text="${kv.value}"/>
      </li>
    </ul>
    <!-- Streamを利用して出力 -->
    <p>--Stream--</p>
    <ul>
      <li th:each=" kv : ${mapStream} ">
        <span th:text="${kv.key}"/><span th:text="${kv.value}"/>
      </li>
    </ul>
  </body>
</html>


実行結果:

繰り返し処理のステータスの利用

th:each 属性で繰り返し処理を行う際は、繰り返し処理の現在の回数などのステータス情報を利用することが可能です。ステータス情報を格納する変数名は、ループごとに利用する項目の変数名の後ろに、カンマで区切って指定します。

<!-- 変数「loopInfo」に繰り返し処理のステータス情報を格納 -->
<li th:each="item, loopInfo : ${items}" />


これで、この繰り返し処理内では変数 loopInfo からループの状況に関する情報が取得できます。

<!-- 変数「loopInfo」に繰り返し処理のステータス情報を利用した出力 -->
<li th:each="item, loopInfo : ${items}">
  <span th:text="${'No.' + loopInfo.count + ':' + item.name}" />
</li>


このように、ループの状況として参照できるプロパティは以下の通りです。これらのプロパティを利用することで、表の偶数行・奇数行の背景色を指定したり、連番の表示を簡単に行うことが可能です。

プロパティ名値の種類内容
index数値0 から始まる繰り返し処理のインデックス。
count数値1 から始まる繰り返し処理のインデックス。(index + 1)
size数値ループ対象のオブジェクトのサイズ(要素数)。
currentオブジェクト現在のループで利用する
even真偽値現在の繰り返し処理が偶数回目のループであるかどうか。
(count が偶数かどうか、と同じ)
odd真偽値現在の繰り返し処理が奇数回目のループであるかどうか。
(count が奇数かどうか、と同じ)
first真偽値現在の処理がループの先頭の処理であるかどうか。
last真偽値現在の処理がループの末尾の処理であるかどうか。

オブジェクトの定義

th:object

th:object 属性は、選択変数式 *{ ... } で利用するオブジェクトを指定するために利用します。 指定したオブジェクトは、th:object 属性を設定したHTMLタグ自身またはそのボディ部のHTMLタグで、選択変数式の対象とすることができます。

同一のオブジェクトの様々なプロパティにアクセスするような場合、それらの親要素に th:object 属性で対象のオブジェクトを指定しておき、その子要素で選択変数式を利用することで、シンプルな記述が実現できます。

<!-- セッションに格納した bean「user」の情報を出力する -->
<ul th:object="${session.user}">
  <li>氏名:<span th:text="*{name}" /></li> <!-- ${session.user.name} と同じ -->
  <li>年齢:<span th:text="*{age}" /></li> <!-- ${session.user.age} と同じ -->
  <li>住所:<span th:text="*{addr}" /></li> <!-- ${session.user.addr} と同じ -->
</ul>  

th:with

th:with 属性は、任意のローカル変数を定義します。ローカル変数の有効範囲は、変数を定義したHTMLタグ自身またはそのボディ部の HTMLタグ です。

 <ul th:with="details=${session.user}">
  <li>氏名:<span th:text="${details.name}" /></li>
  <li>年齢:<span th:text="${details.age}" /></li>
  <li>住所:<span th:text="${details.addr}" /></li>
</ul>  


th:with 属性では、カンマで区切ることで同時に複数の変数を定義することもできます。

<ul th:with="details=${session.user},items=${session.cart.itemList}">
  <li> ...

要素の削除

th:remove

th:remove 属性は、設定した HTML タグ自身、またはボディ部、子要素を削除します。属性値に指定した値(文字列)により、異なる動作となります。

属性値th:remove 属性の動作
allth:remove 属性を設定したHTMLタグ、およびそのボディ部を全て削除。
bodyth:remove 属性を設定したHTMLタグ自身は残し、そのボディ部を全て削除。
tagth:remove 属性を設定したHTMLタグ自身を削除し、そのボディ部は残す。
all-but-firstth:remove 属性を設定したHTMLタグ自身を残し、そのボディ部の2つ目以降のHTMLタグを全て削除。(ボディ部の1つ目の子要素およびテキストは残す)
none何も削除しない。


「all-but-first」は、最初の子要素のみ残して、2個目以降の子要素は削除します。これは『動的にデータ行を表示するテーブルだが、プロトタイプとして静的HTMLを表示する際にも複数のデータ行を準備したい』というような場合に利用することができます。

例えば以下のサンプルコードでは、静的HTMLとして表示した場合にはテーブル上に5行のデータが表示されますが、Webアプリケーション上で動作時は先頭の子要素(th:each 属性を指定している tr 要素)以外の行は削除され、変数 items のデータの数だけデータ行が表示されます。

<table th:remove="all-but-first">
  <tr th:each="item : ${items}">
    <td th:text="${item.manufacturer}">Apple</td>
    <td th:text="${item.brand}">iPhone</td>
  </tr>
  <tr>
    <td>Google</td>
    <td>Google Pixel</td>
  </tr>
  <tr>
    <td>SONY</td>
    <td>Xperia</td>
  </tr>
  <tr>
    <td>SHARP</td>
    <td>AQUOS</td>
  </tr>
  <tr>
    <td>Samsung</td>
    <td>Galaxy</td>
  </tr>
</table>


また th:remove 属性の値には、上記のいずれかの文字列が返される式を指定することも可能です。例えば上記のサンプルコードで items が空だった場合に、th:if 属性を利用しなくてもテーブル自体を表示しないようにすることができます。

<table th:remove="${#lists.isEmpty(items) ? 'all' : 'all-but-first'}">
  <tr ...

フラグメントの利用

th:fragment

th:fragment 属性は、フラグメントを定義するために利用します。定義したフラグメントは、th:insert 属性または th:replace 属性 で利用することができます。また、別のテンプレートファイルに定義したフラグメントを利用することも可能です。

属性はフラグメントとして定義したいHTMLオブジェクト(タグ)に対して設定します。属性の値がフラグメント利用時に指定する名前になりますので、内容がわかりやすい名前を付けると良いでしょう。

以下のサンプルコードでは、 div 要素を「header」という名前のフラグメントとして定義しています。(フラグメントの利用は th:insert 属性および th:replace 属性で説明します)

<div style="width:100%; background-color: aquamarine; text-align: center;" th:fragment="header">
  [Thymeleaf sample page]
</div>

th:insert

th:insert 属性は、設定したHTMLタグの子要素に(ボディ部として)フラグメントを挿入します。取り込むフラグメントはフラグメント式 ~{ ... } を利用して、テンプレート名(ファイル名)とフラグメント名を指定します。

以下は、テンプレート「frg.html」内に定義されたフラグメント「header」を div の子要素として取り込む例です。

<div id="headerplace" th:insert="~{frg :: header}">ここに挿入</div>


上記のサンプルの実行時に生成される HTML コードは以下のようになります。この時、対象のHTMLタグのボディ部に設定されていたテキストや子要素は、全て削除されることに注意してください。

<div id="headerplace">
  <div style="width:100%; background-color: aquamarine; text-align: center;">
    [Thymeleaf sample page]
  </div>
</div>


フラグメント式の中でも条件判定などの記述が可能です。また、フラグメントが自身のテンプレート(ファイル)内に定義されている場合は、テンプレート名の記述を省略できます。

<!-- 条件に応じたフラグメントを挿入 -->
<div th:insert="~{frg :: (${ isHeader ? 'header' : 'footer' })}">ここに挿入</div>

<!-- 同一ファイル内のフラグメントではテンプレート名を省略可能 -->
<div th:insert="~{ :: seperator })">ここに挿入</div>

th:replace

th:replace 属性は、設定したHTMLタグ自体を指定したフラグメントに差し替えます。この時、th:replace 属性を指定したHTMLタグの種類と、フラグメントのタグの種類が異なっていても動作します。また、Thymeleaf の 疑似ブロック要素(th:block)を置き換えることもできます。

th:insert 属性、th:replace 属性のいずれでもフラグメントを取込むことができますが、HTML タグの階層に差が出るため、css のセレクターなどに影響を及ぼす場合があることに注意してください。

th:include(Thymeleaf 3.1 から非推奨)

th:include 属性はフラグメントの挿入に利用しますが、Thymeleaf 3.1 から非推奨となりました。フラグメントを挿入する場合は、th:include 属性の替わりに th:insert 属性を利用してください。

インライン処理の制御

th:inline

前回の記事でご紹介しましたが、Thymeleaf で扱う式の1つとして、インライン式( [[ ... ]] 、[( ... )] )による出力があります。th:inline 属性は、設定した HTML タグのボディ部におけるインライン式の動作を制御することができます。

通常、インライン式は式の結果を HTML コードに出力します。

<p>[[ ${10 * 20} ]]</p>


上記のサンプルコードを実行した場合、生成される HTML コードは以下のとおりです。インライン式が記述されていた部分は、式の結果に置き換えられて出力されています。

<p>200</p>


th:inline 属性では、このインライン式の書式で記述された文字列を、式として扱うかどうかを指定できます。th:inline="none" と指定することで、インライン式は無効化され、単純な文字列とみなされます。

<p th:inline="none">[[ ${10 * 20} ]]</p>


上記のサンプルコードの実行結果として生成される HTML コードは以下の通りです。テンプレート上の [[ ${10 * 20} ]] の部分は式の結果に置き換えられるのではなく、そのまま文字列として出力されます。

<p>[[ ${10 * 20} ]]</p>

アサーション

th:assert

th:assert 属性は Thymeleaf テンプレート内での検査を行い、合致しない場合は例外(org.thymeleaf.exceptions.TemplateProcessingException)をスローします。複数の評価を行う場合は、カンマ区切りでまとめて記述することもできます。

以下の例では、「セッション上にオブジェクト『userinfo』が保持されているか」と「変数 items (配列)が空ではないか」をチェックしています。

<th:block th:assert="${session.userinfo ne null}, ${#arrays.length(items) > 0}" />


ただし、本来データやパラメーターのチェックなどの処理は、一般的にはテンプレートへの転送の前にJavaのコードで実行するため、th:assert 属性は検証や開発時のデバッグ用にのみ利用するのが良いでしょう。

マーカーの定義(マークアップセレクター用)

th:ref

th:ref 属性では、Thymeleaf のマークアップセレクターに対するマーカー(目印)を設定することができます。通常、Thymeleaf のマークアップセレクターでは、指定した名前に対して「同名のタグ」「th:fragment 属性で同名を指定されているタグ」がマッチしますが、ここに「th:ref 属性で同名を指定されているタグ」を加えられます。

通常のHTMLタグの属性を利用せず、th:ref 属性を利用することで、Webアプリケーション上で生成される HTML コードにマーカーが残らないようにすることができます。

HTML(HTML5)属性値の編集

次に、HTML(HTML5)属性の値を編集するための Thymeleaf の属性について説明します。Thymeleafの機能を利用することで、既存の属性値に対して置換や追加を行ったり、属性自体の有無を制御したりすることができます。

値の設定/置換

属性値を設定/置換

input タグの value 属性や、画像に対する alt 属性など、一般的なHTML(HTML5)の属性に対しては、「th:」の後ろにその属性名を記述することで、テンプレートとしての動作時に属性値を設定することができます。

<input type="text" th:value="${greet}" />


静的HTMLとしての表示時に既定の値を設定しておくことも可能です。この場合、テンプレートとしての動作時には指定した属性の値が置換されます。

<input type="text" value="Hello World!" th:value="${greet}" />


上記のサンプルでは、事前に value の値を設定しておくことで、静的HTMLとして表示する際はテキストボックスの中に「Hello World!」を、テンプレートとしての動作時には変数 greet の値を表示することができます。

このように、「th:」を前につけることで値の設定/置換が可能な HTML 属性は以下の通りです。
(参考:Thymeleaf Tutorial - 5.2 特定の属性に値を設定

th:abbr

th:accept

th:accept-charset

th:accesskey

th:action

th:align

th:alt

th:archive

th:audio

th:autocomplete

th:axis

th:background

th:bgcolor

th:border

th:cellpadding

th:cellspacing

th:challenge

th:charset

th:cite

th:class

th:classid

th:codebase

th:codetype

th:cols

th:colspan

th:compact

th:content

th:contenteditable

th:contextmenu

th:data

th:datetime

th:dir

th:draggable

th:dropzone

th:enctype

th:for

th:form

th:formaction

th:formenctype

th:formmethod

th:formtarget

th:fragment

th:frame

th:frameborder

th:headers

th:height

th:high

th:href

th:hreflang

th:hspace

th:http-equiv

th:icon

th:id

th:inline

th:keytype

th:kind

th:label

th:lang

th:list

th:longdesc

th:low

th:manifest

th:marginheight

th:marginwidth

th:max

th:maxlength

th:media

th:method

th:min

th:name

th:onabort

th:onafterprint

th:onbeforeprint

th:onbeforeunload

th:onblur

th:oncanplay

th:oncanplaythrough

th:onchange

th:onclick

th:oncontextmenu

th:ondblclick

th:ondrag

th:ondragend

th:ondragenter

th:ondragleave

th:ondragover

th:ondragstart

th:ondrop

th:ondurationchange

th:onemptied

th:onended

th:onerror

th:onfocus

th:onformchange

th:onforminput

th:onhashchange

th:oninput

th:oninvalid

th:onkeydown

th:onkeypress

th:onkeyup

th:onload

th:onloadeddata

th:onloadedmetadata

th:onloadstart

th:onmessage

th:onmousedown

th:onmousemove

th:onmouseout

th:onmouseover

th:onmouseup

th:onmousewheel

th:onoffline

th:ononline

th:onpause

th:onplay

th:onplaying

th:onpopstate

th:onprogress

th:onratechange

th:onreadystatechange

th:onredo

th:onreset

th:onresize

th:onscroll

th:onseeked

th:onseeking

th:onselect

th:onshow

th:onstalled

th:onstorage

th:onsubmit

th:onsuspend

th:ontimeupdate

th:onundo

th:onunload

th:onvolumechange

th:onwaiting

th:optimum

th:pattern

th:placeholder

th:poster

th:preload

th:radiogroup

th:rel

th:rev

th:rows

th:rowspan

th:rules

th:sandbox

th:scheme

th:scope

th:scrolling

th:size

th:sizes

th:span

th:spellcheck

th:src

th:srclang

th:standby

th:start

th:step

th:style

th:summary

th:tabindex

th:target

th:title

th:type

th:usemap

th:value

th:valuetype

th:vspace

th:width

th:wrap

th:xmlbase

th:xmllang

th:xmlspace

複数属性の値を一括設定/一括置換

Thymeleaf では、複数の属性に一括で同じ値を設定する、特殊な属性が以下の2つ準備されています。

・th:alt-title
 画像の alt 属性と title 属性に同じ値をまとめて設定します。

<img src="/images/xxx.jpg" th:alt-title="#{sampleName}" />


・th:lang-xmllang
 <html>タグに設定し、lang 属性と xmllang 属性に同じ値をまとめて設定します。

<html th:lang-xmllang="${session.lang}" xmlns:th="http://www.thymeleaf.org">
  ...
</html>

論理属性の設定

HTML/HTML5の属性の中には、selected 属性や checked 属性のように、属性を設定することで特定の動作を行うものがあります。これらの属性はその有無だけで動作が決定され、「論理属性」と呼ばれます。

Thymeleaf ではこれらの論理属性について、属性自体を設定するかどうかを真偽値で指定できる機能が準備されています。

以下のサンプルコードでは、セレクトボックスのオプションとチェックボックスのオン・オフを Thymeleaf のタグで動的に指定しています。
(変数 opt の値は'2'、chk01・chk03の値はfalse、chk02の値はtrue とします)

<div>
  <select>
    <option value="1" th:selected="${opt eq '1'}" selected>1</option>
    <option value="2" th:selected="${opt eq '2'}">2</option>
    <option value="3" th:selected="${opt eq '3'}">3</option>
  </select>
</div>
<div>
  <p><input type="checkbox" value="01" th:checked="${chk01}" checked />01</p>
  <p><input type="checkbox" value="02" th:checked="${chk02}" />02</p>
  <p><input type="checkbox" value="03" th:checked="${chk03}" />03</p>
</div>


このサンプルコードを実行して生成される HTML コードは以下の通りです。

条件に合致する(真である)ものには selected 属性や checked 属性が付与され、属性に応じた値が設定されています。また、1個めのオプションや1個めのチェックボックスのように、条件が合致しないものについては、元々設定されていた真偽値属性が削除されていることがわかります。

<div>
  <select>
    <option value="1">1</option>
    <option value="2" selected="selected">2</option>
    <option value="3">3</option>
  </select>
</div>
<div>
  <p><input type="checkbox" value="01" />01</p>
  <p><input type="checkbox" value="02" checked="checked" />02</p>
  <p><input type="checkbox" value="03" />03</p>
</div>


このように、論理属性の設定を制御できる Thymeleaf の属性は以下の通りです。
(参考:Thymeleaf Tutorial - 5.5 固定値の真偽値属性

th:async

th:autofocus

th:autoplay

th:checked

th:controls

th:declare

th:default

th:defer

th:disabled

th:formnovalidate

th:hidden

th:ismap

th:loop

th:multiple

th:novalidate

th:nowrap

th:open

th:pubdate

th:readonly

th:required

th:reversed

th:scoped

th:seamless

th:selected

任意の属性値の設定/置換

これらの「th:」で始まる属性以外にも、任意の名前のHTMLタグに属性を設定することができます。設定方法は「th:」の後ろに同じ属性名を記述するだけです。

以下の例では架空の属性である「imaginary-attr」に対して、静的HTMLでは固定値 "abc"、Webアプリケーション上で動作時は変数 image の値を設定しています。

<div imaginary-attr="abc" th:imaginary-attr="${image}">div要素のコンテンツ</div>


このように任意の属性名を利用できることで、HTML/HTML5 の仕様追加(新しい属性の追加)があった場合でも、既存の HTML 属性と同じように Thymeleaf により値の設定を行うことができます。

属性への値の追加

HTMLの属性の中には、複数の値を(スペース区切りなどで)設定できるものがあります。Thymeleaf では属性値を置換するだけでなく、このような属性に対して値を「追加」する機能が準備されています。

th:classappend

th:classappend 属性は、HTMLタグの class 属性に値を追加します。対象のHTMLタグに class 属性が設定されていない場合は、class 属性を追加します。

下記のサンプルコードでは、th:classappend 属性と th:class 属性を利用した場合の比較を行っています。(変数 className の値は 'extra' とします)

<!-- th:classappend は class 属性の値を追加 -->
<div class="header" th:classappend="${className}">Page Title</div>

<!-- th:class は class 属性の値を置換 -->
<div class="header" th:class="${className}">Page Title</div>


このサンプルコードを実行時に生成される HTML コードは以下のようになります。

<!-- th:classappend は class の値を追加 -->
<div class="header extra">Page Title</div>

<!-- th:class は class の値を置換 -->
<div class="extra">Page Title</div>


また下記のサンプルコードのように、条件式を用いて動的に class 属性に値を追加したりすることもできます。(ここでは偶数行に「evenrow」、奇数行に「oddrow」を追加しています)

<table>
  <tr class="tablerow" th:each="item, status : ${items}" th:classappend="${status.even ? 'evenrow' : 'oddrow'}">
    <td th:text="${item.name}">氏名</td>
    <td th:text="${item.age}">年齢</td>
  </tr>
</table>

th:styleappend

th:styleappend 属性は、HTMLタグの style 属性に値を追加します。対象のHTMLタグに style 属性が設定されていない場合は、style 属性を追加します。

<div style="width: 100%;" th:styleappend="${extraStyle}">100%幅のdiv</div>

<div th:styleappend="${isTitle ? 'background-color: yellow'}">タイトル</div>


この動作は class 属性に対する th:classappend 属性の動作と同じです。ページのデザインにスタイルシートを利用している(class 属性を利用している)場合は th:classappend 属性を利用して class 属性でのスタイル適用に統一するなど、コードの保守性、可読性が下がらないように利用するのが良いでしょう。

th:attrappend/th:attrprepend

th:attrappend 属性および th:attrprepend 属性は、HTMLタグ内に設定されている属性の名前を指定することで、その属性の値を追加することができます。この2つの属性の差は、既存の属性値の後ろに値を追加(attrappend)するか、前に値を追加(attrpretend)だけです。また、対象の HTML タグに指定した名前の属性値が設定されていなかった場合は新しく追加します。

以下のサンプルコードでは、th:classappend/th:attrappend/th:attrprepend 属性のそれぞれを利用して class 属性に値を追加しています。

<!-- th:classappend を利用-->
<p class="content" th:classappend="extra">p要素のコンテンツ</p>
<!-- th:classappend を利用-->
<p class="content" th:attrappend="class=${' ' + 'extra'}">p要素のコンテンツ</p>
<!-- th:attrprependを利用-->
<p class="content" th:attrprepend="class=${'extra' + ' '}">p要素のコンテンツ</p>


上記のサンプルコードの実行時に生成される HTML コードは以下の通りです。

<!-- th:classappend を利用-->
<p class="content extra">p要素のコンテンツ</p>
<!-- th:classappend を利用-->
<p class="content extra">p要素のコンテンツ</p>
<!-- th:attrpretend を利用-->
<p class="extra content">p要素のコンテンツ</p>

th:classappend 属性の利用時は自動的にスペースを挟んで属性値が追加されますが、th:attrappend/th:attrprepend 属性を利用する場合はそのまま追加されるため、文字列に適宜スペースを付加する必要があることに注意してください。

複数属性設定時の優先順位

Thymeleaf のテンプレートでは、1つのHTMLタグ内に複数の Thymeleaf の属性を設定した場合、その機能ごとに優先順位が定められています。

出力が競合する場合

最も優先順位が高いフラグメントの挿入・置換(th:insert、th:replace)と、最も低い要素の削除(th:remove)が競合するケースについて考えてみます。

以下の例では同じタグ内に「th:insert」と「th:remove」を設定し、同じタグに対して「子要素の挿入」と「子要素の削除」という競合する処理が記述されています。この場合、どのような出力結果になるのでしょうか。

<div id="hdr" th:insert="~{frg :: sample}" th:remove="body" />


各属性によって処理される内容について、順を追って考えてみます。まず優先順位の高い th:insert が処理され、フラグメントが子要素として挿入されます。

この時点で生成される HTML コードは以下のようになっていることが想定できます。(th:remove="body" は、便宜上実行される処理の順番がわかるように残しています)

<div id="hdr" th:remove="body">
  <div>「frg :: sample」で定義されているフラグメント</div>
</div>


この後に、th:remove="body" の処理(ボディ部の削除)が実行されるため、「<div> insertされたフラグメント </div>」の部分が削除され、最終的に出力されるのは以下の HTML コードとなります。

<div id="hdr"></div>


このように、同じ箇所に対する出力が競合する場合は、優先順位の低い(後から処理される)処理によって上書きされる場合があります。この優先順位は「どちらの処理結果が残されるか」ではなく「どの属性から処理されるか」であることに注意が必要です。

また、処理の対象はあくまで「属性を設定したタグ」であることに注意してください。上記の例と似ていますが、th:replace 属性を利用した以下のサンプルコードでは実行結果が異なります。

<div id="hdr" th:replace="~{frg :: sample}" th:remove="all" />


このテンプレートから生成される HTML コードは以下のようになります。

<div>「frg :: sample」で定義されているフラグメント</div>


これは、先に th:replace 属性の処理が実行され、後続の th:remove 属性での処理対象(id="hdr" の div要素)がフラグメントに「置き換えられ」てしまうため、削除すべきタグが既にいなくなっているためです。

ローカル変数の利用

Thymeleaf の属性によって定義するローカル変数を利用する場合も、優先順位に注意が必要です。Thymeleaf テンプレートで利用できるローカル変数の定義には、任意のローカル変数を定義する th:with 属性と、選択変数式を利用するための th:object 属性を利用するケースがあります。

ローカル変数の定義の優先順位はボディ部への文字列出力(th:text、th:utext)や、HTMLオブジェクトへの属性値の置換(th:value、th:class など)よりも優先順位が高いため、これらの機能では同じタグで定義したローカル変数を利用することができます。

<!-- ローカル変数を利用できる例(1) 変数の利用 -->
<input type="text" value="0" th:with="user=${session.userInfo}" 
       th:value="${user.age}" />

<!-- ローカル変数を利用できる例(2) 選択変数式の利用 -->
<ul>
  <li th:object="${session.userInfo}" th:text="*{name}" />
</ul>


しかし、条件判定(th:if、th:unless など)や繰り返し処理(th:each)では、これらのローカル変数が利用できないため、正しく処理できません。

<!-- ローカル変数を利用できない例(1) 変数の利用 -->
<input type="text" th:with="user=${session.userInfo}"
       th:value="${user.age}" th:if="${user.age gt 0}"/>
       
<!-- ローカル変数を利用できない例(2) 選択変数式の利用 -->
<ul>
  <li th:object="${session.userInfo}"
      th:each="fm : *{family}" th:text="${fm.name}" />
</ul>


このため、これらのローカル変数や選択変数式を利用する場合は、宣言する階層を考慮する必要があります。例えば上記の繰り返し処理であれば、以下のように記述することで想定どおりの動作になるでしょう。

<ul th:object="${session.userInfo}">
  <li th:each="fm : *{family}" th:text="${fm.name}" />
</ul>


また、同じようにスコープが限られている変数としては、繰り返し処理の対象オブジェクトの宣言があります。上記のサンプルコードであれば「fm : *{family}」の「fm」が該当しますが、繰り返し処理(th:each)の優先順位がボディ部への文字列出力(th:text)よりも高いため、th:text 属性では変数 fm の値を利用できます。

優先順位の一覧

各機能・属性の優先順位は以下の通りです。
(参考:Thymeleaf Tutorial - 10 属性の優先順位

優先順位機能属性
1フラグメントの挿入・置換th:insert
th:replace
2繰り返し処理th:each
3条件判定th:if
th:unless
th:switch
th:case
4ローカル変数の定義th:object
th:with
5任意の属性への値の設定th:attrprepend
th:attrappend
6HTMLオブジェクトへの属性値の置換/設定th:href
th:class
など
7class/style 属性への値の追加th:classappend
th:styleappend
8ボディ部への文字列出力th:text
th:utext
9フラグメントの定義th:fragment
10要素(タグ)の削除th:remove



今回の記事では、Thymeleaf の属性についてご紹介しました。Thymeleaf ではこれらの属性と、式を組み合わせて利用することで、様々なパターンを動的に表示することが可能です。全ての機能をいっぺんに使いこなすのはちょっと大変かもしれませんが、主だった機能だけでも色々な表現が実現できます。

次回の記事では、これらの属性と式を実際にどのように組み合わせて使うのか、具体的なサンプルを交えてご紹介します。

株式会社GSI 採用サイト

新しいこと、始めよう

あなたとともに歩を進めるWEBメディア

広告

広告