Spring Boot フォーム関連のサンプルコード (Thymeleaf)
[履歴] [最終更新] (2017/06/13 22:59:31)
1
作品
407
技術情報
最近の投稿
ここは
趣味の電子工作を楽しむ人のためのハードウェア情報共有サイト

技術情報や作品の投稿機能、リアルタイム遠隔操作 API をご利用いただけます。
新着作品

概要

Spring Boot のテンプレートエンジンとしては、こちらで使用方法を把握した Thymeleaf が有名です。本ページでは、フォーム関連の処理について、基本的なサンプルコードをまとめます。Rails におけるビューヘルパーや、フォーム入力値のバリデーションに相当する機能です。

公式ドキュメント

サンプルプロジェクト構成

.
|-- build.gradle
|-- gradle
|   `-- wrapper
|       |-- gradle-wrapper.jar
|       `-- gradle-wrapper.properties
|-- gradlew
|-- gradlew.bat
`-- src
    `-- main
        |-- java
        |   `-- hello
        |       |-- Application.java
        |       |-- Greeting.java
        |       `-- GreetingController.java
        `-- resources
            `-- templates
                |-- greeting.html
                `-- result.html

build.gradle および Application.java については、こちらのページと同じ内容です。

Java ソースコード

Greeting.java

コントローラとビューで値のやり取りを行うための、入れ物となるクラスです。idcontent は HTML ファイル内の form 内の input に対応しています。

package hello;

public class Greeting {

    private long id;
    private String content;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

GreetingController.java

こちらのページにおける @RequestMapping(path = "/greeting", method = RequestMethod.GET) に相当するアノテーション @GetMapping("/greeting") を利用しています。@GetMapping 側のアクションについては、Model model に空の Greeting オブジェクトを格納して greeting ビューで利用しています。@PostMapping 側のアクションについては、@ModelAttribute が付与された Greeting greeting に POST されたパラメータが自動で格納されており、result ビューで利用されます。

package hello;

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

@Controller
public class GreetingController {

    @GetMapping("/greeting")
    public String greetingForm(Model model) {
        model.addAttribute("greeting", new Greeting());
        return "greeting";
    }

    @PostMapping("/greeting")
    public String greetingSubmit(@ModelAttribute Greeting greeting) {
        // return "greeting"; // 同じビューを使い回すこともできます。
        return "result";
    }
}

HTML ファイル

greeting.html

こちらのページにも記載した @{} を利用してリンクすることで、例えばアプリケーション全体が /myapp といったプレフィックス以下で動作する場合にも対応できます。また、th:field はモデルのメンバ変数と input をバインドするための設定です。

Relative URLs starting with / (eg: /order/details) will be automatically prefixed by the application context name.
http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#link-urls

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Getting Started: Handling Form Submission</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
  <h1>Form</h1>
  <form action="#" th:action="@{/greeting}" th:object="${greeting}" method="post">
    <p>Id: <input type="text" th:field="*{id}" /></p>
    <p>Message: <input type="text" th:field="*{content}" /></p>
    <p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
  </form>
</body>
</html>

result.html

POST された値を Thymeleaf の記法にしたがって設定して表示します。ここまで内容は http://localhost:8080/greeting にアクセスして動作確認できます。

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Getting Started: Handling Form Submission</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
  <h1>Result</h1>
  <p th:text="'id: ' + ${greeting.id}" />
  <p th:text="'content: ' + ${greeting.content}" />
  <a href="/greeting">Submit another message</a>
</body>
</html>

チェックボックス

チェックボックスが一つの場合

input タグには id が自動で付与されて、th:for から参照されます。

greeting.html

<div>
  <label th:for="${#ids.next('admin')}">Admin</label>
  <input type="checkbox" th:field="*{admin}" />
</div>

Greeting.java

private boolean isAdmin;

public boolean isAdmin() {
    return isAdmin;
}

public void setAdmin(boolean isAdmin) {
    this.isAdmin = isAdmin;
}

チェックボックスが複数の場合

先程の例の #ids.next() ではなく #ids.prev() を用いると、label タグを input タグの後ろに設置できます。また、先程の例の isAdmin と異なり、今回の features は boolean ではないため、複数チェックボックスに対してそれぞれ th:value で値を設定しています。

greeting.html

<ul>
  <li th:each="feat : ${allFeatures}">
    <input type="checkbox" th:field="*{features}" th:value="${feat}" />
    <label th:for="${#ids.prev('features')}" 
           th:text="${feat}">feature label</label>
  </li>
</ul>

Greeting.java

private List<String> features;

public List<String> getFeatures() {
    return features;
}

public void setFeatures(List<String> features) {
    this.features = features;
}

GreetingController.java

@GetMapping("/greeting")
public String greetingForm(Model model) {
    model.addAttribute("greeting", new Greeting());
    model.addAttribute("allFeatures", Arrays.asList("xxx", "yyy"));
    return "greeting";
}

@PostMapping("/greeting")
public String greetingSubmit(@ModelAttribute Greeting greeting, Model model) {
    greeting.setId(greeting.getId() + 1);
    model.addAttribute("allFeatures", Arrays.asList("xxx", "yyy"));
    return "greeting";
}

ラジオボタン

ラジオボタンは、複数チェックボックスの場合と似た記法になります。ただし、モデルが保持する値は単数です。

greeting.html

<ul>
  <li th:each="feat : ${allFeatures}">
    <input type="radio" th:field="*{feature}" th:value="${feat}" />
    <label th:for="${#ids.prev('feature')}" th:text="${feat}">feature label</label>
  </li>
</ul>

Greeting.java

private String feature;

public String getFeature() {
    return feature;
}

public void setFeature(String feature) {
    this.feature = feature;
}

GreetingController.java

(複数チェックボックスの場合と同じであるため、省略。)

セレクトボックス

セレクトボックスはラジオボタンとほぼ同じ構造になります。以下の例において、Greeting.java と GreetingController.java はラジオボタンと同じであるため省略します。

greeting.html

<select th:field="*{feature}">
  <option th:each="feat : ${allFeatures}" 
          th:value="${feat}" 
          th:text="${feat}"></option>
</select>

基本的なフォームバリデーション

Validating Form Input に記載の org.hibernate:hibernate-validator は、org.springframework.boot:spring-boot-starter-thymeleaf が依存するため build.gradle に明記する必要はありません。依存関係は以下のコマンドで確認できます。

./gradlew dependencies

Greeting.java

バリデーションは Greeting.java で設定します。

import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

上記ライブラリをインポートして、メンバ変数にアノテーションを設定します。@NotNull で null を許容しない設定になります。@Max による最大値設定や、@Size による文字列長の設定も可能です。

@NotNull
@Max(10)
private long id;

@NotNull
@Size(min=2, max=30)
private String content;

GreetingController.java

@ModelAttribute ではなく @Valid を設定することで、バリデーションが行われます。バリデーション結果 bindingResult によって、表示するビューを変更します。エラーがある場合は、エラーメッセージと伴に、再度フォームビューを表示します。その際、フォームにはユーザーが入力した値が設定された状態になります。

package hello;

import javax.validation.Valid;

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

@Controller
public class GreetingController {

    @GetMapping("/greeting")
    public String greetingForm(Model model) {
        model.addAttribute("greeting", new Greeting());
        return "greeting";
    }

    @PostMapping("/greeting")
    public String greetingSubmit(@Valid Greeting greeting, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "greeting";
        }
        return "result";
        // return "redirect:/somewhere"; // リダイレクトさせることもできます。
    }
}

greeting.html

エラーが存在する場合のみ表示される、th:errors を用いた以下の二行を追記します。複数のエラーが存在する場合は <br /> で区切られます。

<p th:if="${#fields.hasErrors('id')}" th:errors="*{id}">Id Error</p>
<p th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Content Error</p>

th:each で個別にタグを設定することもできます。

<p th:each="err : ${#fields.errors('id')}" th:text="${err}"></p>
<p th:each="err : ${#fields.errors('content')}" th:text="${err}"></p>

全体としては以下のようになります。

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Getting Started: Handling Form Submission</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
  <h1>Form</h1>
  <form action="#" th:action="@{/greeting}" th:object="${greeting}" method="post">
    <p>Id: <input type="text" th:field="*{id}" /></p>
    <p th:if="${#fields.hasErrors('id')}" th:errors="*{id}">Id Error</p>
    <p>Message: <input type="text" th:field="*{content}" /></p>
    <p th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Content Error</p>
    <p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
  </form>
</body>
</html>

エラー時の CSS 設定

th:field が設定された input タグに対して、エラー時に特定の CSS クラスを設定することができます。th:errorclass を利用します。上書きではなく、クラスが追加されます。

<p>Id: <input type="text" th:field="*{id}" th:errorclass="has-error" /></p>

フォーム全体のエラー

input タグ毎ではなく、フォーム全体としてエラーを表示したい場合は * または all でエラー処理します。その際、form タグ内でしか利用できないことに注意します。

<ul th:if="${#fields.hasErrors('*')}">
  <li th:each="err : ${#fields.errors('*')}" th:text="${err}"></li>
</ul>

意味内容としては同じですが、専用の関数 hasAnyErrors()allErrors() を利用して書き換えると以下のようになります。

<ul th:if="${#fields.hasAnyErrors()}">
  <li th:each="err : ${#fields.allErrors()}" th:text="${err}"></li>
</ul>

<br /> 区切りで表示したい場合は以下のようにします。

<p th:if="${#fields.hasErrors('all')}" th:errors="*{all}"></p>

オブジェクトとしてより詳細に制御したい場合は以下のようにします。やはり form タグ内でしか利用できないことに注意します。

<ul>
  <li th:each="e : ${#fields.detailedErrors()}" th:unless="${e.global}">
    <span th:text="${e.fieldName}"></span> | <span th:text="${e.message}"></span>
  </li>
</ul>

フォームタグの外でエラーを表示

<ul th:if="${#fields.hasErrors('${greeting.*}')}">
  <li th:each="err : ${#fields.errors('${greeting.*}')}" th:text="${err}"></li>
</ul>

<form action="#" th:action="@{/greeting}" th:object="${greeting}" method="post">
...

エラーメッセージの日本語化

こちらのページで使い方を把握した messages_{en|ja}.properties と同様に、ValidationMessages_{en|ja}.properties を利用したバリデーションメッセージの多言語対応ができます。以下のページの項目に対応する設定を行うことで、エラーメッセージをカスタマイズできます。

https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties

ただし、Spring Boot のエンコーディング設定の関係で、日本語は文字化けしてしまいます。簡単な対応方法としては、native2ascii で ascii コードに変換することです。また、Eclipse を利用している場合、マーケットプレイスの Properties Editor をインストールすれば、閲覧編集が容易になります。

native2ascii -encoding UTF-8 src/main/resources/ValidationMessages_ja.properties src/main/resources/ValidationMessages_ja.properties

例えば以下のようになります。

src/main/resources/ValidationMessages_ja.properties

javax.validation.constraints.Size.message = {min} \u6587\u5b57\u4ee5\u4e0a {max} \u6587\u5b57\u4ee5\u4e0b\u3067\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002

src/main/resources/ValidationMessages_en.properties

javax.validation.constraints.Size.message = size must be between {min} and {max}

リダイレクト先にパラメータを引き継ぐ

コントローラで以下のように文字列を返すことで、302 リダイレクトを発生させることができます。

return "redirect:/somewhere";

その際、リダイレクト先にパラメータを渡すためには RedirectAttributes を利用します。Rails における flash / notice / alert に相当する機能です。

GreetingController.java

package hello;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
public class GreetingController {

    @GetMapping("/greeting")
    public String greetingForm(Model model) {
        model.addAttribute("greeting", new Greeting());
        return "greeting";
    }

    @PostMapping("/greeting")
    public String greetingSubmit(@ModelAttribute Greeting greeting, RedirectAttributes redirectAttributes) {

        // 直接格納
        redirectAttributes.addFlashAttribute("id", greeting.getId());

        // 入れ物 modelMap を用意して格納
        ModelMap modelMap = new ModelMap();
        modelMap.addAttribute("content", greeting.getContent());
        redirectAttributes.addFlashAttribute("modelMap", modelMap);

        return "redirect:/somewhere";
    }

    @GetMapping("/somewhere")
    public String someWhere(@ModelAttribute("id") long id, @ModelAttribute("modelMap") ModelMap modelMap) {
        System.out.println(id);
        System.out.println(modelMap.get("content"));
        return "redirect:/greeting";
    }
}
関連ページ
    概要 Rails における ERB と同様に、Spring Boot でもテンプレートエンジンを利用できます。今回は特に Thymeleaf (タイムリーフ) のサンプルコードを、こちらのページで構築した環境をもとにまとめます。 公式ドキュメント Serving Web Content with Spring MVC