ThymeleafでEnumからセレクトボックスを作成する

【前提条件】

[環境]

【概要】

ThymeleafでEnum型を使ってセレクトボックスを表示させます。

今回はJerseyMVCとThymeleafを連携させていますが、
セレクトボックスはJerseryやJAX-RSに依存していないはずです。
なので、SpringBootなどでも動作すると思います。
(動作確認はしていないですが。)

【ThymeleafとJerserMVCの連携】

基本的には こちらの設定と同じです。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jp.glory</groupId>
    <artifactId>JerseyMVC-Thymeleaf</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>JerseyMVC-Thymeleaf</name>

    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-core</artifactId>
            <version>1.19</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-servlet</artifactId>
            <version>1.19</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.ext</groupId>
            <artifactId>jersey-mvc</artifactId>
            <version>2.19</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>2.1.4.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies> 

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>7.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
[TemplateProcessor]
package jp.glory.jerseymvc.thymeleaf.settings;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import org.glassfish.jersey.server.mvc.Viewable;
import org.glassfish.jersey.server.mvc.spi.TemplateProcessor;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;

@Provider
public class ThymeleafViewProcessor implements TemplateProcessor<String> {

    @Context
    private HttpServletRequest request;

    @Context
    private HttpServletResponse response;

    @Context
    private ServletContext servletContext;

    private final TemplateEngine templateEngine;

    public ThymeleafViewProcessor() {
        TemplateResolver resolver = new ServletContextTemplateResolver();
        resolver.setPrefix("/WEB-INF/view/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode("HTML5");
        resolver.setCacheTTLMs(3600000L);

        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(resolver);
    }

    @Override
    public String resolve(String name, MediaType mediaType) {

        return name;
    }

    @Override
    public void writeTo(String templateReference, Viewable viewable, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException {

        WebContext context = new WebContext(request, response, servletContext);

        context.setVariable("item", viewable.getModel());

        Writer writer = new OutputStreamWriter(out);
        templateEngine.process(templateReference, context, writer);

        writer.flush();
    }
}
[ResourceConfig]
package jp.glory.jerseymvc.thymeleaf.application;

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.mvc.MvcFeature;

import jp.glory.jerseymvc.thymeleaf.settings.ThymeleafViewProcessor;
@ApplicationPath("service")
public class ApplicationSetting extends ResourceConfig {

    public ApplicationSetting() {

        packages(this.getClass().getPackage().getName());

        register(ThymeleafViewProcessor.class);

        register(MvcFeature.class);

        register(LoggingFilter.class);
    }
}

Java側の実装】

ここからようやく今回の本題と関わるところです。

まずはJava側の実装です。
構成としてはEnum、表示用のBean、リソースクラスです。

[Enum]
package jp.glory.jerseymvc.thymeleaf.application.resource;

public enum Category {

    ProgrammingLanguage(1, "プログラム言語"),
    Server(2, "サーバ"),
    Tool(3, "ツール");
    
    private final int id;
    private final String label; 

    private Category(int id, String label) {

        this.id = id;
        this.label = label;
    }

    public int getId() {
        return id;
    }

    public String getLabel() {
        return label;
    }
}

idとlabelを持ったEnumです。
特に何も特別なことはしていないです。

[表示用のBean]
package jp.glory.jerseymvc.thymeleaf.application.resource;

import java.util.Arrays;
import java.util.List;

public class EnumView {

    private List<Category> categorys = null;
    private Category selected = null;

    public List<Category> getCategorys() {
        return categorys;
    }

    public void setCategorys(List<Category> categorys) {
        this.categorys = categorys;
    }

    public Category getSelected() {
        return selected;
    }

    public void setSelected(Category selected) {
        this.selected = selected;
    }

}

表示用のBeanはセレクトボックスの選択肢となるEnumのListと
画面が表示されたタイミングで選択されているEnumを持ちます。

[リソースクラス]
package jp.glory.jerseymvc.thymeleaf.application.resource;

import java.util.Arrays;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.glassfish.jersey.server.mvc.Viewable;

@RequestScoped
@Path("enum")
public class EnumSample {

    @GET
    public Viewable view() {

        EnumView view = new EnumView();
        view.setCategorys(Arrays.asList(Category.values()));
        view.setSelected(Category.Tool);
        return new Viewable("/enum", view);
    }
}

リソースクラスでは先ほどの表示用のBeanにデータを設定しています。
設定したBeanをモデルとしてViewableオブジェクトを返しています。

JAX-RS以外のフレームワークを使用している場合は
使用しているフレームワークにあった実装に変えてください。

[Thymeleaf]

今回のメインとなる内容です。

<!DOCTYPE html>
<html>
    <head>
        <title>Enum</title>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    </head>
    <body>
        <select>
            <option th:each="category : ${item.categorys}"
                    th:selected="${item.selected.id == category.id}"
                    th:value="${category.id}"
                    th:text="${category.label}">hoge</option>
        </select>
       
    </body>
</html>

【表示結果】

[キャプチャ]

EnumSampleクラスでCategory.Toolを設定していたので、
ツールが選択された状態になっています。

[ソース]

実際に生成されたソースはこちらです。

<!DOCTYPE html>

<html>
    <head>
        <title>Enum</title>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    </head>
    <body>
        <select>
            <option value="1">プログラム言語</option>
            <option value="2">サーバ</option>
            <option selected="selected" value="3">ツール</option>
        </select>
       
    </body>
</html>

選択肢[ツール]の部分だけ、selected属性が付与されています。
selected属性の値はselectedになるようです。

【まとめ】

Thymeleaf側でEnumを意識せずに使えて、個人的には便利だと思います。
optionタグ部分の属性部分が長くなってしまうのがちょっと気になりますが・・・

【変更】

2015/07/11
ApplicationSettingクラスの@ApplicationPathのパス設定が間違っていたので修正しました。