Thymeleaf
Motivation
- A und B loggen sich in WebUntis ein, beide sehen ihren Stundenplan
- ⟹ dynamisches html
- Thymeleaf ist eine template engine
Multi/Single Page Application
- Multi
- Flow durch Controller & mehrere html
- Back-button ✔️
- Single
- Eine html Seite, Flow durch js (+ REST)
- Back-button 🤔
Statisches html
- sollte in
resources\static\liegen
- wird automatisch ausgeliefert

Spring MVC

Controller
@Controller
@RequestMapping("/greetings")
public record GreetingController {
@GetMapping("/greeting")
public String greeting(
@RequestParam(name = "name",
required = false,
defaultValue = "World") String name,
Model model) {
model.addAttribute("name", name);
return "greeting";
}
}
Template
<!DOCTYPE HTML> <!-- greeting.html -->
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<title>Greetings</title>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8"/>
</head>
<body>
<p th:text="'Hello, ' + ${name} + '!'"/>
</body>
</html>
${xxx} greift auf Modelattribut xxx zu

Views
@GetMapping(value = "/")
public String all(Model model) {
model.addAttribute("students", studentRepository.findAll());
return "student-list";
}
...
<head>,
<body>,
<table>
<tr>
<th>Name</th>
<th>Id</th>
<th>School</th>
</tr>
<tr th:each="student: ${students}">
<td th:text="${student.id}">Id</td>
<td>
<a th:text="${student.name}"
th:href="@{/students/{id} (id=${student.id})}">Name</a>
</td>
<td th:if="${student.school}"
th:text="${student.school.name}">School
</td>
</tr>
Forms
@GetMapping(value = "/new")
public String enrollStudentForm(Model model) {
model.addAttribute("student", new Student());
model.addAttribute("interests", Interest.getCommons());
model.addAttribute("schools", schoolRepository.findAll());
return "student-enroll";
}
Form
...
<head>,
<body>
<form method="post"
th:action="@{/students/new}"
// @ = baseurl
th:object="${student}"> // ${student.name} = *{name}
...
</form>
Text
...
<form th:object="${student}">
<div>
<label th:for="name">Name: </label>
<input type="text" th:field="*{name}"/>
</div>
Checkboxen
Sports
Movies
Programming
Movies
Programming
...
<form th:object="${student}">
<div>
<label>Interests: </label>
<ul>
<li th:each="interest: ${interests}">
<input type="checkbox"
th:field="*{interests}"
th:value="${interest.id}"
th:text="${interest.description}"/>
</li>
</ul>
</div>
Select
...
<form th:object="${student}">
<div>
<label th:for="school">School: </label>
<select th:field="*{school}">
<option th:each="school: ${schools}"
th:value="${school.id}"
th:text="${school.name}"></option>
</select>
</div>
Action-Endpoint
@PostMapping(value = "/new")
public String addStudent(@Valid Student student,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("interests", Interest.getCommons());
model.addAttribute("schools", schoolRepository.findAll());
return "student-enroll";
}
var saved = studentRepository.save(student);
return "redirect:/students/%d".formatted(saved.getId());
}Fehler
<div th:if="${#fields.hasErrors('*')}">
<h1>Errors</h1>
<ul>
<li th:each="err : ${#fields.errors('*')}"
th:text="${err}">Error
</li>
</ul>
</div>
<div>
<label for="name">Name: </label>
<input type="text" id="name" th:field="*{name}"/>
<span th:if="${#fields.hasErrors('name')}"
th:errors="*{name}"/>
</div>
Fragments
<body>
<div th:insert="footer :: copy"></div>
<div th:replace="fragments/header :: header"></div>
</body>
Syntax: templatename :: selector
...
selector wie css
htmx - Motivation
Why should only <a> & <form> make HTTP requests?
Why should only click & submit events trigger them?
Why should only GET & POST methods be available?
Why can you only replace the entire screen?
htmx
<a href="/blog">Blog</a>
When a user clicks on this link, issue an HTTP GET request to ‘/blog’ and load the response content into the browser window.
<button hx-post="/clicked"
hx-trigger="click"
hx-target="#parent-div"
hx-swap="outerHTML"
>
When a user clicks on this button, issue an HTTP POST request to ‘/clicked’ and use the content from the response to replace the element with the id parent-div
<head>
<script src="https://unpkg.com/htmx.org@2.0.1">
@PostMapping("/clicked")
public String clicked(Model model) {
model.addAttribute("now", LocalDateTime.now().toString());
return "clicked :: result";
}
@PostMapping("/clicked")
public String clicked(Model model) {
model.addAttribute("now", LocalDateTime.now().toString());
return "clicked :: result";
}
<!-- clicked.html -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<title>fragments</title>
</head>
<body>
<div th:fragment="result" id="parent-div">
<p th:text="${now}"></p>
</div>
</body>
</html>