Thymeleaf usage notes in Spring Boot

What is Thymeleaf

--Reading is "Time Leaf" --One of the template engines --Recommended for use with Spring Boot --You can write the template as pure HTML

Operating environment

Investigate on the assumption that it runs as a view template engine of Spring MVC on Spring Boot.

Hello World

build.gradle


buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.6.RELEASE'
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.springframework.boot:spring-boot-devtools'
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
}

ext['thymeleaf.version'] = '3.0.7.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.2'

jar.baseName = 'thymeleaf'

--Is spring-boot-devtools a tool that allows you to reflect code changes immediately? So, it is not directly related to the use of Thymeleaf, but it is included because it is convenient --As of August 2017, if Thymeleaf is enabled in Spring Boot (spring-boot-starter-thymeleaf is added to the dependency), ver 2.x system will be adopted. --However, Thymeleaf is currently in the ver 3.x series. --To make Thymeleaf used by Spring Boot 3.x, add 'thymeleaf.version' and 'thymeleaf-layout-dialect.version' to the extended property (ʻext). --The version number is 'thymeleaf.version' is the version of ʻorg.thymeleaf: thymeleaf-spring4. --'thymeleaf-layout-dialect.version' specifies the version of nz.net.ultraq.thymeleaf: thymeleaf-layout-dialect respectively. - Maven Repository: org.thymeleaf » thymeleaf-spring4 - Maven Repository: nz.net.ultraq.thymeleaf » thymeleaf-layout-dialect

Folder structure


|-build.gradle
`-src/main/
  |-java/sample/thymeleaf/
  |  |-Main.java
  |  `-web/
  |    `-HelloController.java
  |
  `-resources/
    |-application.properties
    `templates/
       `-hello.html

application.properties


spring.thymeleaf.mode=HTML

--By default it works in HTML5 mode --Since HTML mode is recommended from Thymeleaf ver 3.x, a warning message is output to the console in HTML5 mode. --If you put this setting, it will work in HTML mode and no warning will be issued.

Main.java


package sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

HelloController.java


package sample.thymeleaf.web;

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

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello Thymeleaf!!");
        return "hello";
    }
}

--With the default settings of Spring Boot, template files are searched in /resources/templates/[Controller return value] .html under the classpath. --In the case of the above implementation, src / main / resources / templates / hello.html will be used as the template file.

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="${message}"></h1>
    </body>
</html>

--Template files can be written as pure HTML --Attributes for Thymeleaf can be used by declaring a namespace with xmlns: th =" http://www.thymeleaf.org "

Execution result

thymeleaf.jpg

Embed text

HelloController.java


package sample.thymeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="'hello world'"></h1>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

hello.html


<h1 th:text="'hello world'"></h1>

--By setting th: text =" [value to be output] ", the text element of that tag can be output. --Thymeleaf's own expression language can be used for [value to be output] --Basically the same feeling as other expression languages (SpEL, EL expression, etc.)

In-line processing

hello.html


<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1>[['hello world!!']]</h1>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--With [[[value to output]]], you can output the value directly on the template without using th: text.

Variable output

HelloController.java


package sample.thymeleaf.web;

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

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("modelValue", "Model Value!!");
        return "hello";
    }
}

hello.html


<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1>[[${modelValue}]]</h1>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

hello.html


<h1>[[${modelValue}]]</h1>

--Use the expression $ {[variable name]} to embed the value stored in the runtime context in the template.

HelloControlloer.java


    public String hello(Model model) {
        model.addAttribute("modelValue", "Model Value!!");

--Saving the value in the context is done by receiving the Model class as a controller argument of Spring MVC and using the ʻaddAttribute ()` method.

Expression syntax

literal

<!--String-->
<span th:text="'some text'"></span>
<!--Numerical value-->
<span th:text="123"></span>
<!--Boolean value-->
<span th:text="true"></span>
<!-- null -->
<span th:text="null"></span>

String concatenation

<span th:text="'some text' + '!!'"></span>

--Strings can be concatenated with +

Literal replacement

<span th:text="|Hello ${message}|"></span>

--You can embed variables in string literals by using $ {} in string literals enclosed in |.

Arithmetic operator

<span th:text="(30 * 20 + 10 / 5 - 1) % 3"></span>

--*, /, +,'-','%' can be used --The meaning is the same as Java

Logical operator

<span th:text="true
                and false
                or true
                and not true
                or !false"></span>

--You can use ʻand or ʻor --Can be denied with not or!

Equivalent to comparison

<span th:text="1 < 10"></span>
<span th:text="1 > 10"></span>
<span th:text="1 <= 10"></span>
<span th:text="1 >= 10"></span>
<span th:text="1 == 10"></span>
<span th:text="1 != 10"></span>

--<, >, <=, > = can be used --Each is the same as Java operator --==, ! = Is replaced with a comparison using ʻequals () `(string comparison is also possible)

Conditional operator

1: <span th:text="true ? 'a'"></span><br>
2: <span th:text="false ? 'b'"></span><br>
3: <span th:text="true  ? 'c': 'C'"></span><br>
4: <span th:text="true ?: 'd'"></span><br>
5: <span th:text="false ?: 'e'"></span><br>
6: <span th:text="null ?: 'f'"></span><br>

** Output result **

thymeleaf.jpg

** [Condition]? [Value] **

-[Value] is evaluated when [Condition] is true --Empty if false

** [Condition]? [Value 1]: [Value 2] **

-If [Condition] is true, [Value 1] is evaluated, and if it is false, [Value 2] is evaluated.

** [Object]?: [Value] **

-[Value] is evaluated when [Object] is null --If it is other than null, [object] is evaluated as it is.

SpEL expression

The inside of $ {...} is evaluated by the expression language called SpEL (Spring Expression Language). With this SpEL, you can easily access objects.

The standard expression language is OGNL

Looking at the plain Thymeleaf reference, it says that OGNL is used as the expression language. However, when integrated with Spring, SpEL will be used instead.

Tutorial: Thymeleaf + Spring

2 The SpringStandard Dialect

Use Spring Expression Language (Spring EL or SpEL) as a variable expression language, instead of OGNL. Consequently, all ${...} and *{...} expressions will be evaluated by Spring’s Expression Language engine.

(Translation) Use Spring Expression Language (Spring EL or SpEL) as the expression language for variables instead of OGNL. Therefore, all $ {...} and * {...} expressions are evaluated by Spring's expression language engine.

Field property method reference

HelloController.java


package sample.thymeleaf.web;

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

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("hoge", new Hoge());
        return "hello";
    }
    
    public static class Hoge {
        public int publicField = 1;
        
        public int publicMethod() {return 2;}
        
        public int getPublicValue() {return 3;}
    }
}
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:text="${hoge.publicField}"></div>
        <div th:text="${hoge['publicField']}"></div>
        <div th:text="${hoge.publicMethod()}"></div>
        <div th:text="${hoge.publicValue}"></div>
        <div th:text="${hoge['publicValue']}"></div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--Direct reference is possible for fields and methods that are public --The method getXxx () can be accessed as a property with ʻobjectName.xxx (the method must be public). --Fields and properties can also be referenced in square brackets, such as ʻobjectName ['name']

Map reference

HelloController.java


package sample.thymeleaf.web;

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

import java.util.HashMap;

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        HashMap<String, String> map = new HashMap<>();
        map.put("message", "Hello World!!");

        model.addAttribute("map", map);
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:text="${map.message}"></div>
        <div th:text="${map['message']}"></div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--For Map, you can refer to the value with map. Key --You can also refer to it with square brackets like map ['key']

List reference

HelloConteroller.java


package sample.thymeleaf.web;

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

import java.util.Arrays;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("list", Arrays.asList(1, 2, 3));
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:text="${list[0]}"></div>
        <div th:text="${list[1]}"></div>
        <div th:text="${list[2]}"></div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--To access List and array elements, uselist [index]

Set a value for an attribute

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <span th:class="'hello' + 'world'">hoge</span>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--By describing the attribute in the form of th: attribute name, the processing result of the expression can be set in the attribute. --From v3.x? Seems to be able to use this method for arbitrary attributes - 5.6 Setting the value of any attribute (default attribute processor)

Link URL

application.properties


server.contextPath=/thymeleaf

--Set Spring Boot to start with context path to check operation

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li>
                @{foo/bar} = [[@{foo/bar}]]
            </li>
            <li>
                @{/foo/bar} = [[@{/foo/bar}]]
            </li>
            <li>
                @{~/foo/bar} = [[@{~/foo/bar}]]
            </li>
            <li>
                @{http://localhost:8080/foo/bar} = [[@{http://localhost:8080/foo/bar}]]
            </li>
            <li>
                @{//localhost:8080/foo/bar} = [[@{//localhost:8080/foo/bar}]]
            </li>
        </ul>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--You can use the expression @ {...} to construct a path in a format that complements the context path etc. well.

type Description
foo/bar Normal relative path
/foo/bar Relative path from context path
~/foo/bar Relative path from the root of the server (/fooBecomes another context path)
http://xxx/xxx Absolute path
//xxx/xxx Protocol relative path

Normally, it is specified in the href attribute of the<a>tag, so

<a th:href="@{/foo/bar}">/foo/bar</a>

It will be set as follows.

Set query parameters

HelloController.java


package sample.thymeleaf.web;

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

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("paramValue", "PARAM VALUE");
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        [[@{/foo/bar(hoge='HOGE', paramValue=${paramValue})}]]
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--To set query parameters in the path, write the key-value combination enclosed in parentheses (()) at the end of the path. --If you specify more than one, list them separated by commas. --URL encoding is done automatically

Embed variables in the path

hello.html


<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        [[@{/foo/{pathValue}/bar(pathValue=123)}]]
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--Variables declared with () at the end can be used in the path enclosed in {} --In this case, the parameter is not set in the query parameter and is only used as part of the path

Objects that can be referenced implicitly

In addition to the value set in the controller Model, there are objects that can be accessed by default.

<span th:text="${#httpServletRequest}"></span>
<span th:text="${#httpSession}"></span>
<span th:text="${param}"></span>
<span th:text="${session}"></span>
<span th:text="${application}"></span>

-- # httpServletRequest is the HttpServletRequest object itself -- # httpSession refers to the HttpSession object itself --param is the object that holds the request parameters --session is the attribute information stored in HttpSession --ʻApplicationis the attribute information stored inServletContext --param, session, ʻapplication can be referenced like Map --param ['parameterName'] etc.

Difference with and without #

--httpServletRequest is headed with#, not session or param --This difference depends on where the object is stored in the context --If # is not attached, its value is searched from the implicit variable # vars.

Object selection

HelloController.java


package sample.thymeleaf.web;

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

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("hoge", new Hoge());
        return "hello";
    }
    
    public static class Hoge {
        public String name = "hogeee";
        public String value = "HOGEEE";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:object="${hoge}">
            <div>name = [[*{name}]]</div>
            <div>value = [[*{value}]]</div>
        </div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

hello.html


        <div th:object="${hoge}">
            <div>name = [[*{name}]]</div>
            <div>value = [[*{value}]]</div>
        </div>

--If you specify an object with th: object, you can directly refer to the fields and properties of that object in the form* {}in the child elements of that tag.

Declaration of local variables

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <div th:with="message = 'Hello World!!'">
            message = [[${message}]]
        </div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

hello.html


        <div th:with="message = 'Hello World!!'">
            message = [[${message}]]
        </div>

--Variables declared in th: with become valid only in the child elements of that tag and can be referenced in$ {}expressions

Switching between display and non-display depending on conditions

HelloController.java


package sample.thymeleaf.web;

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

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("flag", true);
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:if="${flag}">flag is true</h1>
        <h1 th:if="${!flag}">flag is false</h1>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--If you use th: if =" [condition] ", it will be displayed only when [condition] is ** a value that evaluates to true **.

Value evaluated as true / false

HelloController.java


package sample.thymeleaf.web;

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

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("booleanTrue", true);
        model.addAttribute("booleanFalse", false);
        model.addAttribute("numericZero", 0);
        model.addAttribute("numericOne", 1);
        model.addAttribute("charZero", '0');
        model.addAttribute("stringEmpty", "");
        model.addAttribute("stringZero", "0");
        model.addAttribute("stringOff", "off");
        model.addAttribute("stringNo", "no");
        model.addAttribute("stringFalse", "false");
        model.addAttribute("anyObject", new Object());
        model.addAttribute("nullValue", null);
        
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:if="${booleanTrue}">booleanTrue</li>
            <li th:if="${booleanFalse}">booleanFalse</li>
            <li th:if="${numericZero}">0</li>
            <li th:if="${numericOne}">numericOne</li>
            <li th:if="${charZero}">charZero</li>
            <li th:if="${stringEmpty}">stringEmpty</li>
            <li th:if="${stringZero}">stringZero</li>
            <li th:if="${stringOff}">stringOff</li>
            <li th:if="${stringNo}">stringNo</li>
            <li th:if="${stringFalse}">stringFalse</li>
            <li th:if="${anyObject}">anyObject</li>
            <li th:if="${nullValue}">nullValue</li>
        </ul>
    </body>
</html>

Execution result

thymeleaf.jpg

** Value evaluated as false </ font> **

--boolean`` false --Numeric 0

  • null --String " false ", " off ", " no "

** Value evaluated as true </ font> **

--Everything except "value evaluated as false" --Example -- true of boolean --Number 1 --String " 0 " --Empty string --Any object that is not null

Reading the reference, it says, "Values other than"0"in the string are evaluated as true. " In other words, it seems that ""0"of the character string is evaluated as false ", but when it is actually tried, it is evaluated as true. bug?

Iterative processing

HelloController.java


package sample.thymeleaf.web;

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

import java.util.Arrays;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("list", Arrays.asList("hoge", "fuga", "piyo"));
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="element : ${list}">[[${element}]]</li>
        </ul> 
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--th: each =" [Variable name to store each element]: $ {[Object to be iterated]} ", the tag can be output repeatedly. -[Variable name to store each element] becomes a variable that is valid only in repeated output --If the class implements ʻIterable, it can be iteratively processed with th: each`.

Repeat Map

HelloController.java


package sample.thymeleaf.web;

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

import java.util.HashMap;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        HashMap<String, String> map = new HashMap<>();
        map.put("hoge", "HOGE");
        map.put("fuga", "FUGA");
        map.put("piyo", "PIYO");

        model.addAttribute("map", map);
        
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="entry : ${map}">
                key=[[${entry.key}]], value=[[${entry.value}]]
            </li>
        </ul>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--When Map is iteratively processed, Map.Entry is iteratively processed as each element.

When processing other than List and Map repeatedly

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="element : 'text'">[[${element}]]</li>
        </ul>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--If an object other than Map that does not implement ʻIterable is iteratively processed with th: each, it is treated as a List` that has only that value.

Repetitive status reference

HelloController.java


package sample.thymeleaf.web;

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

import java.util.Arrays;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("list", Arrays.asList("hoge", "fuga", "piyo"));
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="element, status : ${list}">
                index = [[${status.index}]],
                count = [[${status.count}]],
                size = [[${status.size}]],
                current = [[${status.current}]],
                even = [[${status.even}]],
                odd = [[${status.odd}]],
                first = [[${status.first}]],
                last = [[${status.last}]]
            </li>
        </ul>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--th: each =" [each element], [status variable]: [repeated target] ", by adding , variable name after the declaration of each element variable, the state of each loop can be changed. You will be able to use the retained variables --If the declaration of the status variable is omitted, the status variable is prepared with the variable named [each element] Stat. --If you set th: each =" element: $ {list} ", you can refer to the status variable with ʻelementStat`. --Status contains information about the current loop state --The information stored in the status is as follows

Variable name meaning
index Current loop index (0Start)
count Current loop count (1Start)
size Size of object to be repeated
current Current repeating element
even Whether the current element is even (true / false value)
odd Whether the current element is odd (true / false value)
first Whether the current element is the first (true / false value)
last Whether the current element is at the end (true / false value)

th:block

HelloController.java


package sample.thymeleaf.web;

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

import java.util.Arrays;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("list", Arrays.asList("hoge", "fuga", "piyo"));
        
        return "hello";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <th:block th:each="element : ${list}">
            <h2>[[${element}]]</h2>
        </th:block>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

hello.html


        <th:block th:each="element : ${list}">
            <h2>[[${element}]]</h2>
        </th:block>

--<th: block> tags are completely erased after rendering --Thymeleaf processing (th: if, etc.) can be written, so there is no need to increase the<div>tag just to write the processing.

Embed a fragment

Embed other templates

Folder structure


`-src/main/
  |-java/sample/thymeleaf/
  | `-FragmentController.java
  |
  `-resources/templates/fragment/
    |-fragment.html
    `-embedded.html

FragmentController.java


package sample.thymeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class FragmentController {
    
    
    @GetMapping("/fragment")
    public String fragment() {
        return "fragment/fragment";
    }
}

embedded.html


<h2>embedded</h2>

fragment.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Fragment</title>
    </head>
    <body>
        <h1>Hello Fragment</h1>
        <div id="foo"
             th:insert="fragment/embedded"></div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

Folder structure


`-src/main/
  `-resources/templates/fragment/
    |-fragment.html
    `-embedded.html

fragment.html


        <div id="foo"
             th:insert="fragment/embedded"></div>

--Use th: insert to embed other templates --There was th: include in v2.x, but it is deprecated in v3.x. --Specify the template to embed in the value --The value specified here applies the same logic as when resolving the template file from the return value of the controller class, so match it. --You can also use an expression with the value of th: insert (th: insert = "fragment / $ {name}" )

Specify the fragment to embed

embedded.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <span th:fragment="hoge">hoge</span>
        <span th:fragment="fuga">fuga</span>
    </body>
</html>

fragment.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Fragment</title>
    </head>
    <body>
        <h1>Hello Fragment</h1>
        <div id="foo"
             th:insert="fragment/embedded :: fuga"></div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

embedded.html


        <span th:fragment="hoge">hoge</span>
        <span th:fragment="fuga">fuga</span>

--Specify the fragment name in the th: fragment attribute on the embedding side

fragment.html


        <div id="foo"
             th:insert="fragment/embedded :: fuga"></div>

--By specifying [template name] :: [fragment name to embed] in th: insert, you can embed only the specified fragment of the specified template.

Completely replace with th: replace

embedded.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <span id="hoge" th:fragment="hoge">
            HOGE
        </span>
    </body>
</html>

fragment.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Fragment</title>
    </head>
    <body>
        <h1>Hello Fragment</h1>
        <div id="foo"
             th:insert="fragment/embedded :: hoge"></div>
        <div id="bar"
             th:replace="fragment/embedded :: hoge"></div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--In the case of th: insert, the child of the tag to which the content (th: fragment) of the tag of the embedding target (ʻembedded.html) is added to the tag on the embedding source (fragment.html) side. Element) is embedded --In the case of th: replace, the tag on the embedding source (fragment.html) side is replaced with the tag itself (the tag itself with th: fragment) of the embedding target (ʻembedded.html).

Pass parameters to the fragment

embedded.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <span th:fragment="hoge(p1, p2)">
            p1 = [[${p1}]], p2 = [[${p2}]]
        </span>
    </body>
</html>

fragment.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Fragment</title>
    </head>
    <body>
        <h1>Hello Fragment</h1>
        <div th:insert="fragment/embedded :: hoge('HOGE', 'FUGA')"></div>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

embedded.html


        <span th:fragment="hoge(p1, p2)">
            p1 = [[${p1}]], p2 = [[${p2}]]
        </span>

--After declaring the fragment name, you can declare the parameters that the fragment receives in the form of method arguments.

fragment.html


        <div th:insert="fragment/embedded :: hoge('HOGE', 'FUGA')"></div>

--The embedding side can pass arguments after the fragment name like a method call --The argument can also be specified by name, such as hoge (p1 ='HOGE', p2 ='FUGA')

Thymeleaf Layout Dialect Fragments were in the form of embedding common parts in individual pages. This time, on the contrary, embed the own page in the common layout (header and footer).

This process itself cannot be realized by the standard function alone, and it is necessary to include an extension called thymeleaf-layout-dialect.

build.gradle


ext['thymeleaf.version'] = '3.0.7.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.2'

This 'thymeleaf-layout-dialect.version' is sore.

Folder structure


`-src/main/
  |-java/sample/thymeleaf/web/
  |  `-LayoutController.java
  |
  `-resources/templates/layout/
     |-layout.html
     `-content.html

LayoutController.java


package sample.thymeleaf.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LayoutController {
    
    @GetMapping("/layout")
    public String method() {
        return "layout/content";
    }
}

layout.html


<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <meta charset="UTF-8" />
        <title>Layout File</title>
    </head>
    <body>
        <h1>Common Layout</h1>
        <section layout:fragment="content">
        </section>
    </body>
</html>

content.html


<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">
    <body>
        <section layout:fragment="content">
            Content
        </section>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

LayoutController.java


    @GetMapping("/layout")
    public String method() {
        return "layout/content";
    }

--Specify as a template for content.html

layout.html


<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <meta charset="UTF-8" />
        <title>Layout File</title>
    </head>
    <body>
        <h1>Common Layout</h1>
        <section layout:fragment="content">
        </section>
    </body>
</html>

--Declare xmlns: layout =" http://www.ultraq.net.nz/thymeleaf/layout " as namespace --Define the location to embed the information of each page with layout: fragment

content.html


<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">
    <body>
        <section layout:fragment="content">
            Content
        </section>
    </body>
</html>

--Specify the template whose layout is defined by layout: decorate --Use the fragment expression added in v3.x for the value --Fragment expression is described in the format ~ {}, and the position of the template is specified. --The format of the fragment expression is almost the same as the one specified by th: insert. --For details, see Official Document --By the way, it works even if you don't use the fragment expression ~ {}, but a warning message is output to the console. --According to the message, it will not be usable in the future

Building title

layout.html


<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <meta charset="UTF-8" />
        <title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Layout</title>
    </head>
    <body>
        <h1>Common Layout</h1>
        <section layout:fragment="content">
        </section>
    </body>
</html>

content.html


<!doctype html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">
    <head>
        <title>Content</title>
    </head>
    <body>
        <section layout:fragment="content">
            Content
        </section>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

layout.html


        <title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Layout</title>

--The contents of <title> can be a combination of those in the layout file and those in the content file. --Set the layout: title-pattern attribute to<title>on the layout file side, and define the shape of the combined<title>in the value. -- $ LAYOUT_TITLE refers to<title>in the layout file, $ CONTENT_TITLE refers to <title> on the content file side.

content.html


    <head>
        <title>Content</title>
    </head>

--The content file side just declares <title> normally

Generated HTML title


<title>Layout - Content</title>

--The value is a combination of <title> in the layout file and <title> in the content file.

comment

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <!-- standard html comment -->
        <!--/* -->
            <h1 th:text="'parser level comment'"></h1>
        <!-- */-->
        <!--/*/
            <h1 th:text="'prototype comment'"></h1>
        /*/-->
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--There are three types of Thymeleaf comments

Regular HTML comments

hello.html


        <!-- standard html comment -->

--This will be a normal HTML comment --It is output as it is after rendering, and it is treated as a comment on HTML.

Parser level comments

hello.html


        <!--/* -->
            <h1 th:text="'parser level comment'"></h1>
        <!-- */-->

--Starting with <!-/ * And ending with * /-> --This comment is completely removed at render time and no longer exists in HTML --You can also enclose it with <!-/ *-> And <!-* /->, So it will be displayed when you open it in a browser, but it will disappear when you run it on the server. Can be realized

Prototype only comments

hello.html


        <!--/*/
            <h1 th:text="'prototype comment'"></h1>
        /*/-->

--The range from <!-/ * / To / * /-> is applicable. --When opened directly in the browser, it is treated as a simple HTML comment, but Thymeleaf rendering is targeted. --It is treated as a comment as a prototype, but it is used when you want Thymeleaf to process it when you actually run it on the server.

Message resource

Folder structure


`-src/main/
  |-java/sample/thymeleaf/
  |  |-web/
  |  |  `-HelloController.java
  |  `-Main.java
  |
  `-resources/
     |-messages/
     |  `-Messages_ja.properties
     `-templates/
        `-hello.html

Main.java


package sample.thymeleaf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ResourceBundleMessageSource;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
    
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages/Messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}

Messages_ja.properties


foo.message=Good morning world
bar.message=good-bye world

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="#{foo.message}"></h1>
        <h1 th:text="#{bar.message}"></h1>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

Main.java


import org.springframework.context.support.ResourceBundleMessageSource;

...

    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages/Messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

--Specifying the resource bundle file handled by Spring --Since the encoding is set to ʻUTF-8`, you can write Japanese as it is in the message file.

hello.html


        <h1 th:text="#{foo.message}"></h1>
        <h1 th:text="#{bar.message}"></h1>

--Use the expression # {} to refer to the message from the Thymeleaf template. --Specify the key of the message you want to display inside

Embed parameters in message

Messages_ja.properties


foo.message=Good morning{0}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="#{foo.message('World')}"></h1>
        <h1 th:text="#{foo.message('world')}"></h1>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

hello.html


        <h1 th:text="#{foo.message('World')}"></h1>
        <h1 th:text="#{foo.message('world')}"></h1>

--If the message has placeholders, you can pass parameters like # {key name (parameter ...)}, like a method argument.

Spring bean

MySpringBean.java


package sample.thymeleaf;

import org.springframework.stereotype.Component;

@Component
public class MySpringBean {
    
    public String hello() {
        return "Hello MySpringBean!!";
    }
}

hello.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="${@mySpringBean.hello()}"></h1>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--$ {} is evaluated as a SpEL expression, so you can also refer to Spring beans. --To refer to Spring Bean, use @bean name

Linking Form data with Spring MVC

MyForm.java


package sample.thymeleaf.web;

public class MyForm {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

--Getter and Setter are required because the Form class defaults to property access.

FormController.java


package sample.thymeleaf.web;

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.RequestMapping;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping
    public String submit(MyForm form) {
        System.out.println("form.value=" + form.getValue());
        return "form";
    }
}

--Register the object of MyForm withModel.addAttribute ()at the initial display

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <input type="text" th:field="*{value}" />
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Enter the appropriate characters and click the Submit button.

Server console output


form.value=test

Description

form.html


        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <input type="text" th:field="*{value}" />
            <input type="submit" value="Submit" />
        </form>

--To associate the Java side Form object (MyForm) with the HTML<form>tag, specify the Form object registered in the controller with the th: object tag. -(It worked even if I didn't use th: object, but the documentation said it was required ...) --Use th: field attribute to map each input item to Form property

Checkbox label

MyForm.java


package sample.thymeleaf.web;

public class MyForm {
    private boolean checked;

    public boolean isChecked() {
        return checked;
    }

    public void setChecked(boolean checked) {
        this.checked = checked;
    }
}

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <label th:for="${#ids.next('checked')}">checked</label>
            <input type="checkbox" th:field="*{checked}" />
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--When using th: field, the ʻidattribute is automatically assigned by Thymeleaf. --It becomes difficult to specifyfor, especially if you are creating multiple checkboxes while looping. --To support this, by specifying $ {# ids.next ('[name of the property you want to associate]')}withth: for, you can set the for attribute value to the target property. ʻId attribute value is set --Use $ {# ids.prev ()} if the checkbox item precedes <label>

Radio buttons

MyForm.java


package sample.thymeleaf.web;

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

public class MyForm {
    private String selectedValue = "piyo";
    
    public Map<String, String> radioButtons() {
        Map<String, String> radioButtons = new LinkedHashMap<>();
        radioButtons.put("hoge", "HOGE");
        radioButtons.put("fuga", "FUGA");
        radioButtons.put("piyo", "PIYO");
        
        return radioButtons;
    }

    public String getSelectedValue() {
        return selectedValue;
    }

    public void setSelectedValue(String selectedValue) {
        this.selectedValue = selectedValue;
    }
}

FormController.java


package sample.thymeleaf.web;

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.RequestMapping;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping
    public String submit(MyForm form) {
        System.out.println("form.selectedValue=" + form.getSelectedValue());
        return "form";
    }
}

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <div th:each="radioButton : *{radioButtons()}">
                <label th:for="${#ids.next('selectedValue')}" th:text="${radioButton.value}"></label>
                <input type="radio" th:field="*{selectedValue}" th:value="${radioButton.key}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

MyForm.java


    private String selectedValue = "piyo";
    
    public Map<String, String> radioButtons() {
        Map<String, String> radioButtons = new LinkedHashMap<>();
        radioButtons.put("hoge", "HOGE");
        radioButtons.put("fuga", "FUGA");
        radioButtons.put("piyo", "PIYO");
        
        return radioButtons;
    }

form.html


            <div th:each="radioButton : *{radioButtons()}">
                <label th:for="${#ids.next('selectedValue')}" th:text="${radioButton.value}"></label>
                <input type="radio" th:field="*{selectedValue}" th:value="${radioButton.key}" />
            </div>

--Prepare the following two to map the radio button and Form. --Information for building radio buttons (raidoButtons ()) ――This time, I used LinkedHashMap to make it quickly, but in reality, I think that I will prepare a dedicated container class to store labels and values and pack it in List. --Property to store the selected value (selectedValue) --Construct radio buttons while turning the result of radioButtons () with th: each --When mapping a radio button to a Form, it is mandatory to specify not only th: field but also th: value. --Because you need the value when that item is selected --If you want to select the initial value, set the selected value in the corresponding property (selectedValue =" piyo ") --If you do not set a value, all items will be unselected.

drop-down list

MyForm.java


package sample.thymeleaf.web;

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

public class MyForm {
    private String selectedValue;
    
    public Map<String, String> options() {
        Map<String, String> radioButtons = new LinkedHashMap<>();
        radioButtons.put("hoge", "HOGE");
        radioButtons.put("fuga", "FUGA");
        radioButtons.put("piyo", "PIYO");
        
        return radioButtons;
    }

    public String getSelectedValue() {
        return selectedValue;
    }

    public void setSelectedValue(String selectedValue) {
        this.selectedValue = selectedValue;
    }
}

FormController.java


package sample.thymeleaf.web;

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.RequestMapping;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping
    public String submit(MyForm form) {
        System.out.println("form.selectedValue=" + form.getSelectedValue());
        return "form";
    }
}

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <select th:field="*{selectedValue}">
                <option th:each="option : *{options()}"
                        th:value="${option.key}"
                        th:text="${option.value}">
                </option>
            </select>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--To dynamically generate a drop-down list, loop <option> with th: each -- th: field is written in the <select> tag --th: value must be specified for<option>

Dynamic field

How to create input items that can dynamically add / delete lines

MyForm.java


package sample.thymeleaf.web;

import java.util.ArrayList;
import java.util.List;

public class MyForm {
    private List<Row> rows = new ArrayList<>();
    
    public void appendRow() {
        this.rows.add(new Row());
    }
    
    public void removeRow(int index) {
        this.rows.remove(index);
    }

    public List<Row> getRows() {
        return rows;
    }

    public void setRows(List<Row> rows) {
        this.rows = rows;
    }

    public static class Row {
        private String value;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }
}

FormController.java


package sample.thymeleaf.web;

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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping(params="appendRow")
    public String appendRow(MyForm form) {
        form.appendRow();
        this.printRows(form);
        return "form";
    }
    
    @PostMapping(params="removeIndex")
    public String submit(MyForm form, @RequestParam int removeIndex) {
        form.removeRow(removeIndex);
        this.printRows(form);
        return "form";
    }
    
    private void printRows(MyForm form) {
        List<MyForm.Row> rows = form.getRows();
        for (int i = 0; i < rows.size(); i++) {
            MyForm.Row row = rows.get(i);
            System.out.println("i=" + i + ", row.value=" + row.getValue());
        }
    }
}

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <table border="1">
                <tr>
                    <th>No</th>
                    <th>User input</th>
                    <th>Remove Row</th>
                </tr>
                <tr th:each="row, loop : *{rows}">
                    <th th:text="${loop.count}"></th>
                    <th>
                        <input type="text" th:field="*{rows[__${loop.index}__].value}" />
                    </th>
                    <th>
                        <button type="submit" name="removeIndex" th:value="${loop.index}">
                            Remove
                        </button>
                    </th>
                </tr>
            </table>
            
            <input type="submit" name="appendRow" value="Append Row" />
        </form>
    </body>
</html>

Execution result

thymeleaf.gif

Description

form.html


<tr th:each="row, loop : *{rows}">
    <th th:text="${loop.count}"></th>
    <th>
        <input type="text" th:field="*{rows[__${loop.index}__].value}" />
    </th>
    <th>
        <button type="submit" name="removeIndex" th:value="${loop.index}">
            Remove
        </button>
    </th>
</tr>

--If you want to associate fields that increase or decrease dynamically, you need to devise a little to specify th: field. --Simply $ {row.value} will result in an error --Each element needs to be accessed by indexing rows --Furthermore, index specification uses the ** preprocessing ** mechanism. --Pre-processing is a mechanism that allows you to evaluate before a normal expression, and it is described by enclosing it in __. --__ $ {loop.index} __ part --This allows * {rows [__ $ {loop.index} __] .value} to be evaluated as * {rows [0] .value} and then further evaluated inside * {}. Become so --It seems to work as simply * {rows [loop.index] .value}, but unfortunately it doesn't work. --This is due to the fact that SpEL expressions cannot be specified in parentheses that specify the index of the list.

FormController.java


    @PostMapping(params="appendRow")
    public String appendRow(MyForm form) {
        form.appendRow();
        this.printRows(form);
        return "form";
    }
    
    @PostMapping(params="removeIndex")
    public String submit(MyForm form, @RequestParam int removeIndex) {
        form.removeRow(removeIndex);
        this.printRows(form);
        return "form";
    }

―― To identify whether the Append Rowbutton was clicked or theRemove button was clicked, specify the nameattribute for the button and switch the method call of the controller with or without the parameter. Are --You can control "execute if there is a parameter" withparams of annotations for mapping (@PostMapping, @ GetMapping`, etc.)

Validation

Spring MVC provides an input checking mechanism using Bean Validation. Thymeleaf provides a mechanism for checking this error result and displaying an error message.

MyForm.java


package sample.thymeleaf.web;

import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

public class MyForm {
    @Size(min=3)
    private String text;
    @Min(100)
    private Integer number;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public Integer getNumber() {
        return number;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }
}

FormController.java


package sample.thymeleaf.web;

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.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/form")
public class FormController {
    
    @GetMapping
    public String init(Model model) {
        model.addAttribute(new MyForm());
        return "form";
    }
    
    @PostMapping
    public String submit(@Validated MyForm form, BindingResult result) {
        System.out.println("********************************************************");
        System.out.println("form = " + form);
        System.out.println("result = " + result);
        System.out.println("********************************************************");
        return "form";
    }
}

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <div>
                <input type="text" th:field="*{text}" />
                <ul th:each="error : ${#fields.errors('text')}">
                    <li th:text="${error}"></li>
                </ul>
            </div>
            <div>
                <input type="text" th:field="*{number}" />
                <ul th:each="error : ${#fields.errors('number')}">
                    <li th:text="${error}"></li>
                </ul>
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

MyForm.java


import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

...

    @Size(min=3)
    private String text;
    @Min(100)
    private Integer number;

--Annotate Form fields with Bean Validation annotations and define check contents --Refer to here for how to use Bean Validation.

FormController.java


import org.springframework.validation.annotation.Validated;

...

    @PostMapping
    public String submit(@Validated MyForm form, BindingResult result) {

--Input checking is enabled by annotating the form object with @Validated in the argument of the controller method.

form.html


  <div>
      <input type="text" th:field="*{text}" />
      <ul th:each="error : ${#fields.errors('text')}">
          <li th:text="${error}"></li>
      </ul>
  </div>
  <div>
      <input type="text" th:field="*{number}" />
      <ul th:each="error : ${#fields.errors('number')}">
          <li th:text="${error}"></li>
      </ul>
  </div>

--Error messages generated by each property can be accessed with $ {# fields.errors ('[property name]')} --The results are listed, so you can get each error message by looping around.

Check if the item is an error

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <div>
                <input type="text" th:field="*{text}" />
                <span th:if="${#fields.hasErrors('text')}">It's an error!</span>
            </div>
            <div>
                <input type="text" th:field="*{number}" />
                <span th:if="${#fields.hasErrors('number')}">It's an error!</span>
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--You can check if there are any errors in the property with $ {# fields.hasErrors ('[property name]')}

Specify the style when there is an error

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <div>
                <input type="text" th:field="*{text}" class="always-assigned" th:errorclass="error-style" />
            </div>
            <div>
                <input type="text" th:field="*{number}" class="always-assigned" th:errorclass="error-style" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--If you specify the th: errorclass attribute, the specified class attribute will be added only when an error occurs in that property. --If you want to apply an error style, it's simpler and easier to understand than doing something like $ {# fields.hasErrors ('text')?'Error-style'}

Check if there is at least one error

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <h3 th:if="${#fields.hasAnyErrors()}">There is an error</h3>
            
            <div>
                <input type="text" th:field="*{text}" />
            </div>
            <div>
                <input type="text" th:field="*{number}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

form.html


<h3 th:if="${#fields.hasAnyErrors()}">There is an error</h3>

--You can check if there is at least one error with # fields.hasAnyErrors () --You can get the same result with # fields.hasErrors ('*') or # fields.hasErrors ('all')

Get all error messages

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <ul>
                <li th:each="error: ${#fields.allErrors()}">
                    [[${error}]]
                </li>
            </ul>
            
            <div>
                <input type="text" th:field="*{text}" />
            </div>
            <div>
                <input type="text" th:field="*{number}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--You can get all error messages with # fields.allErrors () --This works the same if you use # fields.errors ('*') or # fields.errors ('all').

Global error

Errors that are not tied to a particular property are called global errors (probably).

Create your own validation to perform correlation check and generate a global error.

MyValidation.java


package sample.thymeleaf.validation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = MyValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyValidation {
    String message() default "It's an error!";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

MyValidator.java


package sample.thymeleaf.validation;


import sample.thymeleaf.web.MyForm;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class MyValidator implements ConstraintValidator<MyValidation, MyForm> {
    @Override
    public void initialize(MyValidation constraintAnnotation) {
        System.out.println("MyValidator initialize");
    }

    @Override
    public boolean isValid(MyForm value, ConstraintValidatorContext context) {
        System.out.println("MyValidator isValid");
        if (value == null) {
            return true;
        }

        Integer number = value.getNumber();
        if (number == null) {
            return true;
        }
        
        String text = value.getText();
        
        if (number == 500) {
            return "500".equals(text);
        }
        
        return true;
    }
}

--Validator to check that text is also"500"if number is 500 --For information on how to create your own Bali data [here](http://qiita.com/opengl-8080/items/3926fbde5469c0b330c2#%E8%87%AA%E4%BD%9C%E3%81%AE%E3% 83% 90% E3% 83% AA% E3% 83% 87% E3% 83% BC% E3% 82% BF% E3% 81% A8% E5% 88% B6% E7% B4% 84% E3% 82% A2% E3% 83% 8E% E3% 83% 86% E3% 83% BC% E3% 82% B7% E3% 83% A7% E3% 83% B3% E3% 82% 92% E4% BD% 9C% See E6% 88% 90% E3% 81% 99% E3% 82% 8B)

MyForm.java


package sample.thymeleaf.web;

import sample.thymeleaf.validation.MyValidation;

import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

@MyValidation
public class MyForm {
    @Size(min=3)
    private String text;
    @Min(100)
    private Integer number;

    ...
}

--Annotate MyForm with @MyValidation

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <h3>[[${#fields.hasGlobalErrors()}]]</h3>
            
            <div>
                <input type="text" th:field="*{text}" />
            </div>
            <div>
                <input type="text" th:field="*{number}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

Set number to something other than 500 to make an error

thymeleaf.jpg

Change number to 500 to make an error

thymeleaf.jpg

Description

form.html


<h3>[[${#fields.hasGlobalErrors()}]]</h3>

--You can check if there are global errors with # fields.hasGlobalErrors () --This is the same result with # fields.hasErrors ('global')

Get global error messages

form.html


<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Form Sample</title>
    </head>
    <body>
        <form th:action="@{/form}" method="post" th:object="${myForm}">
            <h3>[[${#fields.globalErrors()}]]</h3>
            
            <div>
                <input type="text" th:field="*{text}" />
            </div>
            <div>
                <input type="text" th:field="*{number}" />
            </div>
            
            <input type="submit" value="Submit" />
        </form>
    </body>
</html>

Execution result

thymeleaf.jpg

Description

--You can get all global error messages with # fields.globalErrors () --This gives the same result with # fields.errors ('global')

Change error message

Main.java


package sample.thymeleaf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ResourceBundleMessageSource;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
    
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.addBasenames("messages/Messages", "messages/validation-messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}

MyWebConfig.java


package sample.thymeleaf;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MyWebConfig extends WebMvcConfigurerAdapter {
    private MessageSource messageSource;

    public MyWebConfig(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource);
        return validator;
    }
}

src/main/resources/messages/validation-messages_ja.properties


javax.validation.constraints.Min.message = {value}Please enter above

Execution result

thymeleaf.jpg

Description

--Due to Bean Validation specifications, you can overwrite the default message by placing a property file with ValidationMessages as the base name directly under the classpath. --However, it is a little inconvenient because you have to encode the property file with native2ascii. --If you use Spring's MessageSource mechanism, you can write messages in UTF-8, which is convenient.

Main.java


    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.addBasenames("messages/Messages", "messages/validation-messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

--Added message file for Bean Validation with ʻaddBasenames () `

validation-messages_ja.properties


javax.validation.constraints.Min.message = {value}Please enter above

--Check the default message file to find out what the key is --Hibernate Validator is included in the dependency, so you can see the list of default messages by looking at ValidationMessages.properties in that jar.

ValidationMessages.properties


javax.validation.constraints.AssertFalse.message = must be false
javax.validation.constraints.AssertTrue.message  = must be true
javax.validation.constraints.DecimalMax.message  = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message  = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message      = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Future.message      = must be in the future
javax.validation.constraints.Max.message         = must be less than or equal to {value}
javax.validation.constraints.Min.message         = must be greater than or equal to {value}
javax.validation.constraints.NotNull.message     = may not be null
javax.validation.constraints.Null.message        = must be null
javax.validation.constraints.Past.message        = must be in the past
javax.validation.constraints.Pattern.message     = must match "{regexp}"
javax.validation.constraints.Size.message        = size must be between {min} and {max}

org.hibernate.validator.constraints.CreditCardNumber.message        = invalid credit card number
org.hibernate.validator.constraints.EAN.message                   = invalid {type} barcode
org.hibernate.validator.constraints.Email.message                   = not a well-formed email address
org.hibernate.validator.constraints.Length.message                  = length must be between {min} and {max}
org.hibernate.validator.constraints.LuhnCheck.message               = The check digit for ${validatedValue} is invalid, Luhn Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod10Check.message              = The check digit for ${validatedValue} is invalid, Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod11Check.message              = The check digit for ${validatedValue} is invalid, Modulo 11 checksum failed
org.hibernate.validator.constraints.ModCheck.message                = The check digit for ${validatedValue} is invalid, ${modType} checksum failed
org.hibernate.validator.constraints.NotBlank.message                = may not be empty
org.hibernate.validator.constraints.NotEmpty.message                = may not be empty
org.hibernate.validator.constraints.ParametersScriptAssert.message  = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.Range.message                   = must be between {min} and {max}
org.hibernate.validator.constraints.SafeHtml.message                = may have unsafe html content
org.hibernate.validator.constraints.ScriptAssert.message            = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.URL.message                     = must be a valid URL

org.hibernate.validator.constraints.br.CNPJ.message                 = invalid Brazilian corporate taxpayer registry number (CNPJ)
org.hibernate.validator.constraints.br.CPF.message                  = invalid Brazilian individual taxpayer registry number (CPF)
org.hibernate.validator.constraints.br.TituloEleitoral.message      = invalid Brazilian Voter ID card number

reference

Recommended Posts