Thymeleafを使用した入力フォームのサンプルコード

# 概要

Spring bootとテンプレートエンジンのthymeleafを使用した入力フォームのサンプルコードです。


**Thymeleaf 3.0を利用した記事「[Thymeleaf 3.0を使用した入力フォームのサンプル] (https://qiita.com/rubytomato@github/items/8da1bb19537bbfc9c2ea) 」を投稿しました。(2018/04/14)**

**環境**

下記の環境で動作確認を行いました。

* Windows7 (64bit)
* Java 1.8.0_45
* thymeleaf 2.1.4
    * Spring boot 1.2.4

**参考**

下記のサイトを参考にさせていただきました。

Thymeleaf

* [Tutorial: Using Thymeleaf (ja)] (http://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf_ja.html)

## サンプルコード

[Spring Bootで簡単な検索アプリケーションを開発する] (http://qiita.com/rubytomato@github/items/e4fda26faddbcfd84d16)で作成した検索アプリケーションに、今回の入力フォームのサンプルコードを追加しました。プロジェクトの構成などはこちらの記事から確認できますので当記事では省略します。

|完成図 |
|:------|
|index  |
|![FireShot Capture - SimpleForm index - http___localhost_9000_simple_.png](https://qiita-image-store.s3.amazonaws.com/0/22772/27d9ecac-c90a-3b26-22a3-0d8d8d6d8389.png)|
|confirm|
|![FireShot Capture - SimpleForm confirm - http___localhost_9000_simple_confirm.png](https://qiita-image-store.s3.amazonaws.com/0/22772/f9a103fa-0ed0-7f40-9309-694c6c762ef8.png)|


### form

パッケージ: com.example.actor.web

入力フォームの値を格納するフォームクラスです。
（長くなるのでgetter/setterは省略します。）

```java:SimpleForm.java
package com.example.actor.web;

import java.io.Serializable;
import java.time.LocalDate;

import javax.validation.constraints.Digits;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.validator.constraints.Email;
import org.springframework.format.annotation.DateTimeFormat;

public class SimpleForm implements Serializable {

  private static final long serialVersionUID = -157143280035400042L;

  @NotNull
  @Size(min = 1, max = 120)
  private String ftext;

  @Pattern(regexp = "((19|[2-9][0-9])[0-9]{2})/(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])")
  private String ftdate;

  @DateTimeFormat(pattern = "yyyy/MM/dd")
  private LocalDate fdate;

  @Digits(integer = 3, fraction = 0)
  private String ftnum;

  @Min(1)
  @Max(999)
  private Integer fnum;

  @Size(min = 1, max = 600)
  private String farea;

  @Email
  private String femail;

  @NotNull
  @Size(min = 6, max = 12)
  private String fpass;

  @Pattern(regexp = "A|B|C|D|E")
  private String fselect;

  private String[] fmselect;

  @Pattern(regexp = "on")
  private String fcheck;

  private String[] fchecks;

  @NotNull
  @Pattern(regexp = "A|B|C|D|E")
  private String fradio;

  // getter/setterは省略します

  public String getFareaNl2br() {
    if (StringUtils.isNotEmpty(this.farea)) {
      return this.farea.replaceAll("\n", "<br/>");
    }
    return "";
  }

  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE);
  }

}
```

:exclamation: Stringの配列に対するバリデーションの設定方法が分からなかったので、バリデーションを行っていません。

textareaで入力した値に含まれる改行コードをwebページへ出力する際に`<br/>`タグへ変換する機能(play frameworkのnl2brのような)が無いようなので、formに変換メソッドを定義しました。
テンプレート側では、このフィールド値を下記のように`th:utext`でエスケープせずに出力します。

```html:confirm.html
<td th:utext="*{fareaNl2br}"></td>
```

### controller

パッケージ: com.example.actor.web

入力フォームを表示するアクション(index)と、入力フォームの値を受け取るアクション(confirm)を定義します。

```java:SimpleController.java
package com.example.actor.web;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping(value = "/simple")
public class SimpleController {
  final static Logger logger = LoggerFactory.getLogger(SimpleController.class);

  /**
   * selectの表示に使用するアイテム
   */
  final static Map<String, String> SELECT_ITEMS =
    Collections.unmodifiableMap(new LinkedHashMap<String, String>() {
    {
      put("select_A", "A");
      put("select_B", "B");
      put("select_C", "C");
      put("select_D", "D");
      put("select_E", "E");
    }
  });

  /**
   * check boxの表示に使用するアイテム
   */
  final static Map<String, String> CHECK_ITEMS =
    Collections.unmodifiableMap(new LinkedHashMap<String, String>() {
    {
      put("checkbox_A", "A");
      put("checkbox_B", "B");
      put("checkbox_C", "C");
      put("checkbox_D", "D");
      put("checkbox_E", "E");
    }
  });

  /**
   * radio buttonの表示に使用するアイテム
   */
  final static Map<String, String> RADIO_ITEMS =
    Collections.unmodifiableMap(new LinkedHashMap<String, String>() {
    {
      put("radio_A", "A");
      put("radio_B", "B");
      put("radio_C", "C");
      put("radio_D", "D");
      put("radio_E", "E");
    }
  });

  @InitBinder
  public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
  }

  @RequestMapping(value = "/", method = RequestMethod.GET)
  public String index(SimpleForm form, Model model) {
    model.addAttribute("selectItems", SELECT_ITEMS);
    model.addAttribute("checkItems", CHECK_ITEMS);
    model.addAttribute("radioItems", RADIO_ITEMS);
    return "Simple/index";
  }

  @RequestMapping(value = "/confirm", method = RequestMethod.POST)
  public String confirm(@Validated @ModelAttribute SimpleForm form, BindingResult result, Model model) {
    if (result.hasErrors()) {
      model.addAttribute("validationError", "不正な値が入力されました。");
      return index(form, model);
    }
    return "Simple/confirm";
  }

}
```

### template

#### index.html

src/main/resources/templates/Simple/index.html

入力フォームを表示するテンプレートです。

```html:index.html
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head th:replace="_temp :: header ('SimpleForm index')">
</head>
<body>

  <div class="container">

    <div class="page-header">
     <h1>SimpleForm index</h1>
     <p th:if="${validationError}" th:text="${validationError}">validation error</p>
    </div>

    <div class="row">
      <div class="col-md-12">

        <form role="form" action="/simple/confirm" th:action="@{/simple/confirm}" th:object="${simpleForm}" method="post">
          <!-- text -->
          <div class="form-group">
            <label class="control-label" for="inputText"><abbr title="required">*</abbr> Text</label>
            <input type="text" class="form-control" id="inputText" name="ftext" th:field="*{ftext}" />
            <span th:if="${#fields.hasErrors('ftext')}" th:errors="*{ftext}" class="help-block">error!</span>
          </div>
          <!-- date type text -->
          <div class="form-group">
            <label class="control-label" for="inputDateText">Date type text</label>
            <input type="text" class="form-control" id="inputDateText" name="ftdate" th:field="*{ftdate}" />
            <span th:if="${#fields.hasErrors('ftdate')}" th:errors="*{ftdate}" class="help-block">error!</span>
          </div>
          <!-- date -->
          <div class="form-group">
            <label class="control-label" for="inputDate">Date</label>
            <input type="text" class="form-control" id="inputDate" name="fdate" th:field="*{fdate}" />
            <span th:if="${#fields.hasErrors('fdate')}" th:errors="*{fdate}" class="help-block">error!</span>
          </div>
          <!-- number type text -->
          <div class="form-group">
            <label class="control-label" for="inputNumberText">Number type text</label>
            <input type="text" class="form-control" id="inputNumberText" name="ftnum" th:field="*{ftnum}" />
            <span th:if="${#fields.hasErrors('ftnum')}" th:errors="*{ftnum}" class="help-block">error!</span>
          </div>
          <!-- number -->
          <div class="form-group">
            <label class="control-label" for="inputNumber">Number</label>
            <input type="text" class="form-control" id="inputNumber" name="fnum" th:field="*{fnum}" />
            <span th:if="${#fields.hasErrors('fnum')}" th:errors="*{fnum}" class="help-block">error!</span>
          </div>
          <!-- textarea -->
          <div class="form-group">
            <label class="control-label" for="inputTextarea1">Textarea</label>
            <textarea rows="3" cols="80" class="form-control" id="inputTextarea" name="farea" th:field="*{farea}"></textarea>
            <span th:if="${#fields.hasErrors('farea')}" th:errors="*{farea}" class="help-block">error!</span>
          </div>
          <!-- email -->
          <div class="form-group">
            <label class="control-label" for="inputEmail">Email</label>
            <input type="email" class="form-control" id="inputEmail" name="femail" th:field="*{femail}" />
            <span th:if="${#fields.hasErrors('femail')}" th:errors="*{femail}" class="help-block">error!</span>
          </div>
          <!-- password -->
          <div class="form-group">
            <label class="control-label" for="inputPassword"><abbr title="required">*</abbr> Password</label>
            <input type="password" class="form-control" id="inputPassword" name="fpass" th:field="*{fpass}" />
            <span th:if="${#fields.hasErrors('fpass')}" th:errors="*{fpass}" class="help-block">error!</span>
          </div>
          <!-- select single -->
          <div class="form-group">
            <label class="control-label" for="exampleSelect">Select</label>
            <select class="form-control" id="exampleSelect" name="fselect">
              <option value="">---</option>
              <option th:each="item : ${selectItems}" th:value="${item.value}" th:text="${item.key}" th:selected="${item.value} == *{fselect}">pulldown</option>
            </select>
            <span th:if="${#fields.hasErrors('fselect')}" th:errors="*{fselect}" class="help-block">error!</span>
          </div>
          <!-- select multi -->
          <div class="form-group">
            <label class="control-label" for="exampleMSelect">Multi Select</label>
            <select class="form-control" id="exampleMSelect" name="fmselect" multiple="multiple" size="6">
              <option value="">---</option>
              <option th:each="item : ${selectItems}" th:value="${item.value}" th:text="${item.key}" th:field="*{fmselect}">pulldown</option>
            </select>
            <span th:if="${#fields.hasErrors('fmselect')}" th:errors="*{fmselect}" class="help-block">error!</span>
          </div>
          <!-- checkbox single -->
          <div class="form-group">
            <label class="control-label">Single checkbox</label>
            <div class="checkbox">
              <label>
                <input type="checkbox" name="fcheck" value="on" th:field="*{fcheck}" /> Active
              </label>
            </div>
            <span th:if="${#fields.hasErrors('fcheck')}" th:errors="*{fcheck}" class="help-block">error!</span>
          </div>
          <!-- checkbox multi -->
          <div class="form-group">
            <label class="control-label">Multi checkbox</label>
            <div class="checkbox" th:each="item : ${checkItems}">
              <label>
                <input type="checkbox" name="fchecks" th:value="${item.value}" th:text="${item.key}" th:field="*{fchecks}">checkbox</input>
              </label>
            </div>
            <span th:if="${#fields.hasErrors('fchecks')}" th:errors="*{fchecks}" class="help-block">error!</span>
          </div>
          <!-- radio -->
          <div class="form-group">
            <label class="control-label"><abbr title="required">*</abbr> Radio</label>
            <div class="radio" th:each="item : ${radioItems}">
              <label>
                <input type="radio" name="fradio" th:value="${item.value}" th:text="${item.key}" th:field="*{fradio}">radio</input>
              </label>
            </div>
            <span th:if="${#fields.hasErrors('fradio')}" th:errors="*{fradio}" class="help-block">error!</span>
          </div>
          <button type="submit" class="btn btn-default">confirm</button>
        </form>

      </div>
    </div>

    <div th:replace="_temp :: footer"></div>
  </div>

  <div th:include="_temp :: script"></div>

</body>
</html>
```

#### confirm.html

src/main/resources/templates/Simple/confirm.html

入力された値を表示するテンプレートです。

```html:confirm.html
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head th:replace="_temp :: header ('SimpleForm confirm')">
</head>
<body>

  <div class="container">

    <div class="page-header">
     <h1>SimpleForm confirm</h1>
    </div>

    <div class="row">

      <div class="col-md-12">

        <table class="table table-striped table-bordered" th:object="${simpleForm}">
          <tr>
            <th class="col-md-3">Text (ftext)</th>
            <td class="col-md-9" th:text="*{ftext}"></td>
          </tr>
          <tr>
            <th>Date type text (ftdate)</th>
            <td th:text="*{ftdate}"></td>
          </tr>
          <tr>
            <th>Date (fdate)</th>
            <td th:text="*{fdate}"></td>
          </tr>
          <tr>
            <th>Number type text (ftnum)</th>
            <td th:text="*{ftnum}"></td>
          </tr>
          <tr>
            <th>Number (fnum)</th>
            <td th:text="*{fnum}"></td>
          </tr>
          <tr>
            <th>Textarea (farea)</th>
            <td th:utext="*{fareaNl2br}"></td>
          </tr>
          <tr>
            <th>Email (femail)</th>
            <td th:text="*{femail}"></td>
          </tr>
          <tr>
            <th>password (fpass)</th>
            <td th:text="*{fpass}"></td>
          </tr>
          <tr>
            <th>single select (fselect)</th>
            <td th:text="*{fselect}"></td>
          </tr>
          <tr>
            <th>multi select (fselect)</th>
            <td>
              <p th:each="c : *{fmselect}" th:text="${c}"></p>
            </td>
          </tr>
          <tr>
            <th>single checkbox (fcheck)</th>
            <td th:text="*{fcheck}"></td>
          </tr>
          <tr>
            <th>multi checkbox (fchecks)</th>
            <td>
              <p th:each="c : *{fchecks}" th:text="${c}"></p>
            </td>
          </tr>
          <tr>
            <th>radio (fradio)</th>
            <td th:text="*{fradio}"></td>
          </tr>
        </table>

      </div>
    </div>

    <div th:replace="_temp :: footer"></div>
  </div>

  <div th:include="_temp :: script"></div>
</body>
</html>
```