Thymeleaf-extras-tiles2でTilesレイアウトにする

【前提条件】

[環境]
  • Spring4.0.0
  • Thymeleaf2.1.2
  • Thymeleaf-extras-tiles2 2.1.2

【概要】

Thymeleafでベースとなるテンプレートを使用できるものです。
ApacheTilesのようなものをThymeleafでも使えるようになります。

Thymeleaf本体のドキュメントを読むと
fragmentsを使えば別ページを参照することができます。
ページ共通のものを使用するとなると
個別のページにth:includeで指定する必要があるみたいです。
(知識不足なので知らないだけの可能性がありますが・・・)

Thymeleaf-extras-tiles2ではベースのHTMLを
作ってそこを参照させることができます。

【pom.xml

今回はSpring4とThymeleafを連携させます。
必要なライブラリは↓です。

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring4</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-tiles2</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-tiles2-spring4</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

ディレクトリ構成】

ページのディレクトリ構成は↓のようにしています。

今回はログイン画面を作り、
ログイン後に何かしらのトップ画面を表示させるようにしています。
(ログインと言っても何もやっていないですが)

ディレクトリ/ファイル 概要
layout レイアウトのテンプレートになるHTMLが含まれています。
login ログイン画面用のHTMLが含まれています。
menu ログイン後に表示させるHTMLが含まれています。
テンプレートを使用するのはこの配下のページです。
tiles-def.xml ログイン後に表示させるHTMLが含まれています。
applicationContext.xml Springの設定ファイル。

Java

ログイン画面を表示するためのログインコントローラと
ログイン後に表示するトップコントローラの2つを作ります。

リクエストURLと遷移先は↓のようにしています。

リクエストURL 遷移先
/ /login/login
/login redirect:/top
/top redirect:/menu/top/top
[ログインコントローラ]
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class LoginController {

    @RequestMapping(value = "/", method = { RequestMethod.GET })
    public String viewLogin() {

		// ログイン画面表示の処理
        return "/login/login";
    }

    @RequestMapping(value = "/login", method = { RequestMethod.POST })
    public String login() {

		// ログインの何かしらの処理
        return "redirect:/top";
    }
}
[トップコントローラ]
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class TopController {

    @RequestMapping(value = RequestPath.Top.Top, method = { RequestMethod.GET })
    public String viewTop() {

		// 画面表示の処理
        return "/menu/top/top";
    }
}

【ApplicationContext】

applicationContext.xmlの設定です。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-4.0.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:resources mapping="/js/**" location="/js/" />
    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:annotation-driven />

    <context:component-scan base-package="sample" />

    <!-- HTMLファイルの参照先などの設定 -->
    <bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
        <property name="prefix" value="/WEB-INF/view" />
        <property name="suffix" value=".html" />
        <property name="templateMode" value="HTML5" />
        <property name="characterEncoding" value="UTF-8" />
        <property name="cacheable" value="false"/>
    </bean>

	<!-- Thymeleaf-extras-tiles2の設定 -->
    <bean id="tilesConfigurer" class="org.thymeleaf.extras.tiles2.spring4.web.configurer.ThymeleafTilesConfigurer">
      <property name="definitions">
        <list>
          <value>/WEB-INF/tiles-defs.xml</value>
        </list>
      </property>
    </bean>

	<!-- テンプレートエンジンの設定 -->
    <bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />
        <property name="additionalDialects">
            <set>
              <bean class="org.thymeleaf.extras.tiles2.dialect.TilesDialect"/>
            </set>
        </property>
    </bean>

	<!-- 表示の設定 -->
    <bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
        <property name="viewClass" value="org.thymeleaf.extras.tiles2.spring4.web.view.ThymeleafTilesView"/>
        <property name="templateEngine" ref="templateEngine" />
        <property name="characterEncoding" value="utf-8" />
    </bean>
</beans>
[Thymeleaf-extras-tiles2の設定]

設定ファイルの参照先を指定します。
WEB-INF配下にあるtiles-defs.xmlを指定します。

[テンプレートエンジンの設定]

additionalDialectsに
org.thymeleaf.extras.tiles2.dialect.TilesDialectクラスを指定します。

[表示の設定]

viewClassに
org.thymeleaf.extras.tiles2.spring4.web.view.ThymeleafTilesViewクラスを指定します。

【tiles-defs.xml

続いてtiles-defs.xmlの設定です。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN"
       "http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>

    <definition name="/login/login" template="/login/login"/>

    <definition name="/menu/**" template="/layout/baseLayout">
        <put-attribute name="header" value="headerPage" />
        <put-attribute name="menu" value="menuPage" />
        <put-attribute name="content" value="contentPage/{1}" />
        <put-attribute name="pageTitle" value="title/{1}" />
    </definition>

    <definition name="headerPage" template="/menu/common/header :: headerContent" />
    <definition name="menuPage" template="/menu/common/menu :: menuContent" />
    <definition name="contentPage/**" template="/menu/{1} :: contentDetail" />
    <definition name="title/**" template="/menu/{1} :: title" />

</tiles-definitions>
[tiles-definitionsタグ]

タグの下に
タグで
テンプレートなどの参照先を設定していきます。

このファイル上に定義されていないもパスが
コントローラから返された場合、エラーとなります。
そのため、全ての戻り値に対応する必要があります。

[definitionタグ]

name属性とコントローラの戻り値をマッチングさせています。

template属性を指定して、ページの参照させます。

    <definition name="/login/login" template="/login/login"/>

この指定はコントローラから「/login/login」が返されたら、
name属性が「/login/login」の箇所からテンプレートを参照します。

template属性の指定も「/login/login」となっていますので、
ThymeleafTilesConfigurerに指定した
prefixとsufixがくっついたファイルを参照します。

今回のサンプルでは「/WEB-INF/view/login/login.html」になります。
(prefix/sufixの指定はapplicationContext.xmlで指定しています)

    <definition name="/menu/**" template="/layout/baseLayout">
        <!-- put-attributetについては後ほど -->
    </definition>
    <definition name="contentPage/**" template="/menu/{1} :: contentDetail" />

name属性にはワイルドカードが使用できます。

name属性が「/menu/**」の箇所は
「/menu」で始まる場合、「/WEB-INF/view/layout/baseLayout.html」を参照する
という設定になります。

また、ワイルドカードの部分はtemplate属性に対して変数として扱えます。
template属性上で「{1}」となっている箇所は
ワイルドカードの値で置き換えられます。
「**」を指定した場合、サブディレクトリも検索してくれます。

template属性の指定で「::」のあとに指定した値は
fragmentのIDとして扱われます。

「template="/menu/{1} :: contentDetail"」と言う指定は
「/WEB-INF/view/menu/hoge.html」ファイルの中で
「th:fragment="contentDetail"」と指定された箇所を参照する
と言う設定になります。

[put-attributeタグ]

タグの下に指定します。

    <definition name="/menu/**" template="/layout/baseLayout">
        <put-attribute name="header" value="headerPage" />
        <put-attribute name="menu" value="menuPage" />
        <put-attribute name="content" value="contentPage/{1}" />
        <put-attribute name="pageTitle" value="title/{1}" />
    </definition>

後述しますがbaseLayout.htmlでは
いくつかのページを参照したり、ページごとにタイトルを変更したり
と言うことをさせています。

タグはそいうったテンプレート内で
使用したい属性を定義することができます。

name属性で指定した値はテンプレート内で参照することができます。

value属性で指定した値で他のdefinitionを参照します。
definitionでワイルドカードを使用している場合、
value属性でも同じく参照することができます。

【HTML】

[baseLayout.html]

コントローラからの返却値が「/menu」で始まる場合に参照するHTMLです。

<!DOCTYPE html>
<html>
    <head>
        <title tiles:replace="pageTitle">トップ</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div tiles:replace="header" style="background-color: lightblue">
            へっだー
        </div>
        <div>
            <div tiles:replace="menu">
                めにゅ〜
            </div>
            <div tiles:replace="content">
                こんてんつ
            </div>
        </div>
    </body>
</html>

tiles:replaceにtiles-defs.xmlで指定した
put-attributeの名前を指定すると描画時に置き換えてくれます。

サンプルではstyle属性を指定していますが、
置き換えられるので有効にはなりません。

[top.html]

各ページのコンテンツを表示するためのHTMLです。

<!DOCTYPE html>
<html>
    <head>
        <title tiles:fragment="title">お知らせページ</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div tiles:fragment="contentDetail" style="background-color: lightsteelblue;">
            コンテンツ
        </div>
    </body>
</html>

タグ内にtiles:fragmentを指定すると
baseLayout.htmlの中身がこちらのファイルで指定したものに置き換わります。

属性値はtiles-defs.xmlで指定したものと一致させる必要があります。

    <definition name="contentPage/**" template="/menu/{1} :: contentDetail" />
    <definition name="title/**" template="/menu/{1} :: title" />
[header.html]

ヘッダー部分です。

<!--
To change this template, choose Tools | Templates
and open the template in the editor.
-->
<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div tiles:fragment="headerContent" style="background-color: lightcoral">
            ヘッダーです。
        </div>
        ここは見せられないよ。
    </body>
</html>

header.htmlではtiles:fragmentを指定したDIVタグの外に
文字列を表示していますが、
置き換え時には範囲対象外となるため表示されません。

[menu.html]

メニュー用のHTMLです。
特に新しいことはしていません。

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div tiles:fragment="menuContent">
            メニューです。
        </div>
    </body>
</html>
[login.html]

ログイン用のHTMLはbaseLyaout.htmlを使わないページです。

<!DOCTYPE html>
<html>
    <head>
        <title>Start Page</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" th:href="@{/css/modules/bootstrap/3.0.3/css/bootstrap-theme.css}" />
        <link rel="stylesheet" th:href="@{/css/modules/bootstrap/3.0.3/css/bootstrap.css}" />
    </head>
    <body>
        <form method="POST" th:action="@{/login}">
            <div>
                ユーザID
                <input type="text" name="userId" value="" />
            </div>
            <div>
                パスワード
                <input type="password" name="password" value="" />
            </div>
            <div>
                <button>ログイン</button>
            </div>
        </form>
    </body>
</html>

bootstrapを使っていますが、特別なことはしていません。

【画面】

作成した画面を見てみます。

まずはログイン画面。

HTML通りの画面が表示されます。

続いてログイン後の画面です。

ヘッダー、メニュー、コンテンツ、タイトルと
top.htmlとheader.html、menu.htmlの内容が表示されています。

ちなみにbaseLayout.htmlのファイル自体を
表示した場合はこうなります。

【まとめ】

Thymeleaf-extras-tiles2を使えば
ThymeleafでもApacheTilesのようなことが簡単にできます。

tiles-defs.xmlと全てのコントローラの戻り値を
対応させないといけないので、
ディレクトリ構成か設定ファイルの書き方には一工夫が必要になりそうです。

通常のHTMLとしても使える状態は
保たれているので使ってもよさそうだなと
個人的には思いました。