04. I made a front end with SpringBoot + Thymeleaf

Overview

This time I will try to make a front end (** Thymeleaf **) instead of API. You may be wondering, "Is it possible to suddenly create a front end even though it was an API until the last time?" My feeling is that the implementation of the API and front end is not so different **.

If you follow some rules (file placement, etc.), Spring Boot will do what you want, so The main changes are ** Controller layer ** and ** View layer is added to it **. In other words, the ** Model (Service, Mapper) layer can be configured and implemented in the same way as the API **.

Let's actually look at the source!

Main subject

I used the same project until last time, but This time it will be the front end, so I will cut a new project. (I will omit the procedure) As a fake, let's simply request the postal code search API as before and return the result as html. Since the main part of this time is the implementation of View by Thymeleaf, I will mainly explain that.

1. Add a dependency

We will use the familiar Spring Initializr. This time, we will specify "** Web ", " MySQL ", " MyBatis ", " Lombok ", and " Thymeleaf **". 依存追加.png

After selecting the above, click ** Generate Project ** to reflect ** build.gradle ** in the downloaded zip to the project. Don't forget ** Reflesh **.

2. Make a Model

Last time Copy the created GetAddressApiClient and ResponseHeaderInterceptor and bring them as they are. Oh, don't forget to bring your data class.

3. Create Controller and View

3-1. Controller and View that display the top screen

It is the top screen for the time being. ** Redirect ** and ** Forward ** are available in Spring Boot. This time I'm using a redirect.

TopController.java


@Controller
public class TopController {
    @GetMapping("/top")
    public String top() {
        return "/top";
    }
    @GetMapping("/")
    public String index() {
        return "redirect:/top"; //With this description/You will be able to redirect to top
    }
}

Next is html, but since it is not enough to put it, I will omit it here. Just use a simple anchor to jump to the search screen.

3-2. Create Controller and View to display search and results

Let's implement validation at once. First from Controller.

AddressSearchController.java


@Controller
@RequiredArgsConstructor
public class AddressSearchController {
    private final GetAddressApiClient client;

    @GetMapping("/address/search")
    public String search(@ModelAttribute SearchForm form) { // (1)
        return "/address/search";
    }

    @PostMapping("/address/confirm")
    public String confirm(@ModelAttribute @Valid SearchForm form,
                          BindingResult bindingResult,
                          Model model) {
        if (bindingResult.hasErrors()) { // (2)
            return "/address/search";
        }
        GetAddressApiResponse response = client.request(form.getPostalCode());
        model.addAttribute("addressList", response.getResults()); // (3)
        return "/address/confirm";
    }
}

Let's briefly explain the main points. (1). Form is fetched from Model with @ModelAttribute as an argument. ** Since the instance does not exist in Model at the timing of initial display, Spring new and passes the object **. It is the same if you explicitly new in the method andmodel.addAttribute (), but if you write this, it is convenient because you can inherit the value with forward! (2). If the zip code entered in the familiar bindingResult.hasErrors () is incorrect, it will return to the search screen. By the way, the annotation is the same as the Zip Code created last time. (3). If the entered zip code is correct, get the address information via the previously created Client and pass the value to View with the name ʻaddressList`.

Next is the html of the search screen.

search.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <!-- (1) -->
<head>
    <meta charset="UTF-8">
    <title>Sample Search</title>
</head>
<body>
<h2>Search screen</h2>
<form th:action="@{/address/confirm}" th:method="post" th:object="${searchForm}">
    <label th:for="*{postalCode}">Postal code</label><input th:field="*{postalCode}">
    <span th:if="${#fields.hasErrors('postalCode')}" th:errors="*{postalCode}">error message</span><br/> <!-- (2) -->
    <button type="submit">Search for</button>
</form>
</body>
</html>

First of all, let's explain each th tag and description method that appear in this html.

-** Variable expression $ {} **… The description method used when using the value of the instance passed from Controller. ** If you simply want to display the value, use it ! - Link expression @ {} … A description method that can be used when describing a link. It complements the context path. Although not introduced here, you can also embed the get parameter with a variable. - Select variable expression * {} **… The description method used to display the value of a nested variable. Think of this html as accessing the postalCode nested in an instance expanded withth: object = "$ {searchForm}". --th: action… Specify the action attribute. --Specify the for attribute of th: for… label. --th: field… ** This is a tag that you will often use. ** With this description, ** ʻidandname attributes will contain the variable name, and html will be created with value empty **. Often used when mapping form and html fields. --th: if… ** This is a tag that you will often use. ** If the result of evaluating the value is true`, the tag will be drawn.

(1). This is a description for using the thymeleaf tag th. If you don't mention this, you won't be able to use thymeleaf, so don't forget. (2). $ {# fields.hasErrors ('postalCode')} will return true if a validation violation occurs with the value of postalCode. You can display error messages for a field by specifying the field name with th: errors. In other words, ** You can output an error message when there is a validation violation with this sentence **.

Finally, it is the html of the confirmation screen.

confirm.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Sample Confirm</title>
</head>
<body>
<h2>Search result screen</h2>
<table border="1">
    <tr>
        <th>Postal code</th>
        <th>Prefecture code</th>
        <th>Address 1</th>
        <th>Address 2</th>
        <th>Address 3</th>
        <th>Address Kana 1</th>
        <th>Address Kana 2</th>
        <th>Address Kana 3</th>
    </tr>
    <tr th:each="result : ${addressList}">
        <td th:text="${result.zipcode}"></td>
        <td th:text="${result.prefcode}"></td>
        <td th:text="${result.address1}"></td>
        <td th:text="${result.address2}"></td>
        <td th:text="${result.address3}"></td>
        <td th:text="${result.kana1}"></td>
        <td th:text="${result.kana2}"></td>
        <td th:text="${result.kana3}"></td>
    </tr>
</table>
<a th:href="@{/address/search}">Return to search screen</a>
</body>
</html>

This time, the result will be returned as a list, so I made it output in a table. The tags used on this screen are as follows.

--th: each… ** This is a tag that you will often use. ** I think it will be easier to understand if you can imagine the extended for statement of java. The values are fetched in order from $ {addressList} so that they can be used with the name result. --th: text… ** This is a tag that you will often use. ** As the name suggests, it is used when you want to display it on the screen, and the value is expanded to <td> </ td> after rendering. It is compatible with XSS and escapes metacharacters, so you can use it with confidence.

Is it such a place? Finally, let's hit it from the browser! !!

4. Try to hit it

4-1. Top screen

BootRun to access http: // localhost: 8080 /. トップ画面.png Click ** Search by zip code ** to move to the search screen.

4-2. Search screen

検索画面.png

The HTML source after rendering looks like this. 検索画面_html.png th: field is expanded, ** ʻid and nameare variable names, andvalue` is empty **. Now enter the correct value and click ** Search **.

4-3. Result screen

First, if the result is singular. 結果画面.png

Then when there are multiple results. 結果画面_複数.png th: each is working properly and you can see all of them even if there are multiple.

Finally, let's look at the case of validation error.

4-4. Validation error

検索画面_不正値.png If you enter an invalid value and click ** Search ** ...

検索画面_不正値_エラー.png You have successfully displayed the error message. By the way, this message is a solid character string written in the default of ZipCode.message.

Finally

It was insanely simple, but it was a front-end implementation. ** thymeleaf is deep, and the tags and description methods I'm touching are just the tip of the iceberg. ** ** You can do more and more, so check it out!

I think that css and javascript will be needed after that, but recently I had the opportunity to touch ** semantic ui **. It can be used on the CDN, and it will be decorated by specifying the class name. If you are interested, please feel free to contact us!

Thank you for watching until the end! !!

Recommended Posts

04. I made a front end with SpringBoot + Thymeleaf
I made a GUI with Swing
I made a risky die with Ruby
I made a rock-paper-scissors app with kotlin
I made a rock-paper-scissors app with android
I made a mosaic art with Pokemon images
I made a gender selection column with enum
I made a LINE bot with Rails + heroku
I made a portfolio with Ruby On Rails
[Ruby] I made a crawler with anemone and nokogiri.
I made a chat app.
I wrote a CRUD test with SpringBoot + MyBatis + DBUnit (Part 1)
I made a development environment with rails6 + docker + postgreSQL + Materialize.
Create a simple CRUD with SpringBoot + JPA + Thymeleaf ③ ~ Add Validation ~
I made a plugin to execute jextract with Gradle task
Make a simple CRUD with SpringBoot + JPA + Thymeleaf ① ~ Hello World ~
I made a mod that instantly calls a vehicle with Minecraft
Make a simple CRUD with SpringBoot + JPA + Thymeleaf ⑤ ~ Template standardization ~
Create a simple DRUD application with Java + SpringBoot + Gradle + thymeleaf (1)
Draw screen with Thymeleaf in SpringBoot
Ruby: I made a FizzBuzz program!
Implement text link with Springboot + Thymeleaf
I made a shopify app @java
I made a simple recommendation function.
I made a matching app (Android app)
I made a package.xml generation tool.
[Android] I made a pedometer app.
I made a command line interface with WinMerge Plugin using JD-Core
[Rails] I made a simple calendar mini app with customized specifications.
Create a simple CRUD with SpringBoot + JPA + Thymeleaf ④ ~ Customize error messages ~
I made a simple search form with Spring Boot + GitHub Search API.
[Ruby] I made a simple Ping client
Create a simple CRUD with SpringBoot + JPA + Thymeleaf ② ~ Screen and function creation ~
I made an app to scribble with PencilKit on a PDF file
I made an eco server with scala
I tried using OnlineConverter with SpringBoot + JODConverter
I tried playing with BottomNavigationView a little ①
I made a plugin for IntelliJ IDEA
I made a calculator app on Android
Deserialize XML into a collection with spring-boot
I made a new Java deployment tool
[LINE BOT] I made a ramen BOT with Java (Maven) + Heroku + Spring Boot (1)
I made a bulletin board using Docker 1
I made a site that summarizes information on carbohydrate restriction with Vue.js
Learn Java with Progate → I will explain because I made a basic game myself
I made a Diff tool for Java files
I made a primality test program in Java
I made blackjack with Ruby (I tried using minitest)
I made a rock-paper-scissors game in Java (CLI)
I made a viewer app that displays a PDF
I made a Docker container to run Maven
I made a Ruby extension library in C
[Rails] I made a draft function using enum
I built a Code Pipeline with AWS CDK.
Show a simple Hello World with SpringBoot + IntelliJ
I tried to break a block with java (1)
I made StringUtils.isBlank
I tried deploying a page made with antique Middleman on Azure Static Web Apps
I made a lock pattern using the volume key with the Android app. Fragment edition
I made a function to register images with API in Spring Framework. Part 1 (API edition)
I made a Docker image of SDAPS for Japanese