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!
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.
We will use the familiar Spring Initializr. This time, we will specify "** Web ", " MySQL ", " MyBatis ", " Lombok ", and " Thymeleaf **".
After selecting the above, click ** Generate Project ** to reflect ** build.gradle ** in the downloaded zip to the project. Don't forget ** Reflesh **.
Last time Copy the created GetAddressApiClient and ResponseHeaderInterceptor and bring them as they are. Oh, don't forget to bring your data class.
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.
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, ** ʻidand
name 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! !!
BootRun to access http: // localhost: 8080 /. Click ** Search by zip code ** to move to the search screen.
The HTML source after rendering looks like this.
th: field
is expanded, ** ʻid and
nameare variable names, and
value` is empty **.
Now enter the correct value and click ** Search **.
First, if the result is singular.
Then when there are multiple results.
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.
If you enter an invalid value and click ** Search ** ...
You have successfully displayed the error message.
By the way, this message is a solid character string written in the default of ZipCode.message
.
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