JAVA-3545: Move spring-thymeleaf-3 into spring-web-modules

This commit is contained in:
Krzysiek
2020-12-28 22:42:11 +01:00
parent 24e80a6440
commit 6f0c813c8b
26 changed files with 2 additions and 4 deletions

View File

@@ -22,6 +22,7 @@
<module>spring-mvc-forms-jsp</module>
<module>spring-thymeleaf</module>
<module>spring-thymeleaf-2</module>
<module>spring-thymeleaf-3</module>
</modules>
</project>

View File

@@ -0,0 +1,12 @@
## Spring Thymeleaf 3
This module contains articles about Spring with Thymeleaf
## Relevant Articles:
- [Add CSS and JS to Thymeleaf](https://www.baeldung.com/spring-thymeleaf-css-js)
- [Formatting Currencies in Spring Using Thymeleaf](https://www.baeldung.com/spring-thymeleaf-currencies)
- [Working with Select and Option in Thymeleaf](https://www.baeldung.com/thymeleaf-select-option)
- [Conditional CSS Classes in Thymeleaf](https://www.baeldung.com/spring-mvc-thymeleaf-conditional-css-classes)
- [Using Hidden Inputs with Spring and Thymeleaf](https://www.baeldung.com/spring-thymeleaf-hidden-inputs)
- [Thymeleaf Variables](https://www.baeldung.com/thymeleaf-variables)

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-thymeleaf-3</artifactId>
<name>spring-thymeleaf-3</name>
<packaging>war</packaging>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.baeldung.thymeleaf.cssandjs.CssAndJsApplication</mainClass>
<layout>JAR</layout>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>${tomcat7-maven-plugin.version}</version>
<executions>
<execution>
<id>tomcat-run</id>
<goals>
<goal>exec-war-only</goal>
</goals>
<phase>package</phase>
<configuration>
<path>/</path>
<enableNaming>false</enableNaming>
<finalName>webapp.jar</finalName>
<charset>utf-8</charset>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<finalName>spring-thymeleaf-3</finalName>
</build>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<tomcat7-maven-plugin.version>2.2</tomcat7-maven-plugin.version>
</properties>
</project>

View File

@@ -0,0 +1,11 @@
package com.baeldung.thymeleaf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,28 @@
package main.java.com.baeldung.thymeleaf.articles;
public class Article {
private String name;
private String url;
public Article(String name, String url) {
this.name = name;
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}

View File

@@ -0,0 +1,37 @@
package main.java.com.baeldung.thymeleaf.articles;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
import java.util.List;
@Controller
@RequestMapping("/api/articles")
public class ArticlesController {
@GetMapping
public String allArticles(Model model) {
model.addAttribute("articles", fetchArticles());
return "articles/articles-list";
}
private List<Article> fetchArticles() {
return Arrays.asList(
new Article(
"Introduction to Using Thymeleaf in Spring",
"https://www.baeldung.com/thymeleaf-in-spring-mvc"
),
new Article(
"Spring Boot CRUD Application with Thymeleaf",
"https://www.baeldung.com/spring-boot-crud-thymeleaf"
),
new Article(
"Spring MVC Data and Thymeleaf",
"https://www.baeldung.com/spring-mvc-thymeleaf-data"
)
);
}
}

View File

@@ -0,0 +1,23 @@
package com.baeldung.thymeleaf.blog;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Random;
@Controller
public class BlogController {
@GetMapping("/blog/new")
public String newBlogPost(Model model) {
// Set a random ID so we can see it in the HTML form
BlogDTO blog = new BlogDTO();
blog.setBlogId(Math.abs(new Random().nextLong() % 1000000));
model.addAttribute("blog", blog);
return "blog/blog-new";
}
}

View File

@@ -0,0 +1,66 @@
package com.baeldung.thymeleaf.blog;
import java.util.Date;
public class BlogDTO {
private long blogId;
private String title;
private String body;
private String author;
private String category;
private Date publishedDate;
public long getBlogId() {
return blogId;
}
public void setBlogId(long blogId) {
this.blogId = blogId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public Date getPublishedDate() {
return publishedDate;
}
public void setPublishedDate(Date publishedDate) {
this.publishedDate = publishedDate;
}
}

View File

@@ -0,0 +1,15 @@
package com.baeldung.thymeleaf.conditionalclasses;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ConditionalClassesController {
@GetMapping("/conditional-classes")
public String getConditionalClassesPage( Model model) {
model.addAttribute("condition", true);
return "conditionalclasses/conditionalclasses";
}
}

View File

@@ -0,0 +1,11 @@
package com.baeldung.thymeleaf.cssandjs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CssAndJsApplication {
public static void main(String[] args) {
SpringApplication.run(CssAndJsApplication.class, args);
}
}

View File

@@ -0,0 +1,15 @@
package com.baeldung.thymeleaf.cssandjs;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class CssAndJsController {
@GetMapping("/styled-page")
public String getStyledPage(Model model) {
model.addAttribute("name", "Baeldung Reader");
return "cssandjs/styledPage";
}
}

View File

@@ -0,0 +1,22 @@
package com.baeldung.thymeleaf.currencies;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class CurrenciesController {
@GetMapping(value = "/currency")
public String exchange(
@RequestParam(value = "amount", required = false) String amount,
@RequestParam(value = "amountList", required = false) List amountList,
Locale locale) {
return "currencies/currencies";
}
}

View File

@@ -0,0 +1,60 @@
package com.baeldung.thymeleaf.option;
import java.io.Serializable;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
*
* Simple student POJO with few fields
*
*/
public class Student implements Serializable {
private static final long serialVersionUID = -8582553475226281591L;
@NotNull(message = "Student ID is required.")
@Min(value = 1000, message = "Student ID must be at least 4 digits.")
private Integer id;
@NotNull(message = "Student name is required.")
private String name;
@NotNull(message = "Student gender is required.")
private Character gender;
private Float percentage;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
public Float getPercentage() {
return percentage;
}
public void setPercentage(Float percentage) {
this.percentage = percentage;
}
}

View File

@@ -0,0 +1,24 @@
package com.baeldung.thymeleaf.option;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the student model.
*
*/
@Controller
public class StudentController {
@RequestMapping(value = "/student", method = RequestMethod.GET)
public String addStudent(Model model) {
model.addAttribute("student", new Student());
return "option/studentForm.html";
}
}

View File

@@ -0,0 +1,7 @@
function showAlert() {
alert("The button was clicked!");
}
function showName(name) {
alert("Here's the name: " + name);
}

View File

@@ -0,0 +1,18 @@
h2 {
font-family: sans-serif;
font-size: 1.5em;
text-transform: uppercase;
}
strong {
font-weight: 700;
background-color: yellow;
}
p {
font-family: sans-serif;
}
label {
font-weight: 600;
}

View File

@@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf Variables</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<main>
<div th:each="article : ${articles}">
<a th:text="${article.name}" th:href="${article.url}"></a>
</div>
<div th:with="firstArticle=${articles[0]}">
<a th:text="${firstArticle.name}" th:href="${firstArticle.url}"></a>
</div>
<div th:each="article : ${articles}" , th:with="articleName=${article.name}">
<a th:text="${articleName}" th:href="${article.url}"></a>
</div>
<div th:each="article : ${articles}" th:with="articleName=${article.name}, articleUrl=${article.url}">
<a th:text="${articleName}" th:href="${articleUrl}"></a>
</div>
<div id="firstDiv" th:with="firstArticle=${articles[0]}">
<a th:text="${firstArticle.name}" th:href="${firstArticle.url}"></a>
</div>
<div id="secondDiv">
<h2 th:text="${firstArticle.name}"></h2>
</div>
<div id="mainDiv" th:with="articles = ${ { articles[0], articles[1] } }">
<div th:each="article : ${articles}">
<a th:text="${article.name}" th:href="${article.url}"></a>
</div>
</div>
</main>
</body>
</html>

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hidden Input Examples</title>
</head>
<body>
<h2>Hidden Input Example 1</h2>
<form action="#" method="post" th:action="@{/blog}" th:object="${blog}">
<input type="text" th:field="*{title}">
<input type="text" th:field="*{category}">
<textarea th:field="*{body}"></textarea>
<input type="hidden" th:field="*{blogId}" id="blogId">
<input type="submit">
</form>
<h2>Hidden Input Example 2</h2>
<form action="#" method="post" th:action="@{/blog}" th:object="${blog}">
<input type="text" th:field="*{title}">
<input type="text" th:field="*{category}">
<textarea th:field="*{body}"></textarea>
<input type="hidden" th:value="${blog.blogId}" th:attr="name='blogId'"/>
<input type="submit">
</form>
<h2>Hidden Input Example 3</h2>
<form action="#" method="post" th:action="@{/blog}" th:object="${blog}">
<input type="text" th:field="*{title}">
<input type="text" th:field="*{category}">
<textarea th:field="*{body}"></textarea>
<input type="hidden" th:value="${blog.blogId}" name="blogId"/>
<input type="submit">
</form>
</body>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Conditional CSS Classes in Thymeleaf</title>
</head>
<body>
<h2>The Goal</h2>
<p>
<span class="base condition-true">
I have two classes: "base" and either "condition-true" or "condition-false" depending on a server-side condition.
</span>
</p>
<h2>Using th:if</h2>
<p>
<span th:if="${condition}" class="base condition-true">
This HTML is duplicated. We probably want a better solution.
</span>
<span th:if="${!condition}" class="base condition-false">
This HTML is duplicated. We probably want a better solution.
</span>
</p>
<h2>Using th:attr</h2>
<p>
<span th:attr="class=${condition ? 'base condition-true' : 'base condition-false'}">
This HTML is consolidated, which is good, but the Thymeleaf attribute still has some redundancy in it.
</span>
</p>
<h2>Using th:class</h2>
<p>
<span th:class="'base '+${condition ? 'condition-true' : 'condition-false'}">
The base CSS class still has to be appended with String concatenation. We can do a little bit better.
</span>
</p>
<h2>Using th:classappend</h2>
<p>
<span class="base" th:classappend="${condition ? 'condition-true' : 'condition-false'}">
This HTML is consolidated, and the conditional class is appended separately from the static base class.
</span>
</p>
</body>
</html>

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Add CSS and JS to Thymeleaf</title>
<link th:href="@{/styles/cssandjs/main.css}" rel="stylesheet" />
<script th:inline="javascript">
var nameJs = /*[[${name}]]*/;
</script>
<script type="text/javascript" th:src="@{/js/cssandjs/actions.js}"></script>
</head>
<body>
<h2>Carefully Styled Heading</h2>
<p>
This is text on which we want to apply <strong>very special</strong> styling.
</p>
<p><label>Name:</label><span th:text="${name}"></span></p>
<button type="button" th:onclick="showName(nameJs);">Show Name</button>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Currency table</title>
</head>
<body>
<p>Currency format by Locale</p>
<p th:text="${#numbers.formatCurrency(param.amount)}"></p>
<p>Currency Arrays format by Locale</p>
<p th:text="${#numbers.listFormatCurrency(param.amountList)}"></p>
<p>Remove decimal values</p>
<p th:text="${#strings.replace(#numbers.formatCurrency(param.amount), '.00', '')}"></p>
<p>Replace decimal points</p>
<p th:text="${#numbers.formatDecimal(param.amount, 1, 2, 'COMMA')}"></p>
</body>
</html>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Student Form</title>
</head>
<body>
<h1>Student Form</h1>
<form action="#" th:action="@{/saveStudent}" th:object="${student}"
method="post">
<ul>
<li th:errors="*{id}" />
<li th:errors="*{name}" />
<li th:errors="*{gender}" />
<li th:errors="*{percentage}" />
</ul>
<table border="1">
<tr>
<td><label th:text="ID" /></td>
<td><input type="number" th:field="*{id}" /></td>
</tr>
<tr>
<td><label th:text="Name" /></td>
<td><input type="text" th:field="*{name}" /></td>
</tr>
<tr>
<td><label th:text="Gender" /></td>
<td><select th:field="*{gender}">
<option th:value="'M'" th:text="Male"></option>
<option th:value="'F'" th:text="Female"></option>
</select></td>
</tr>
<tr>
<td><label th:text="Percentage" /></td>
<td><select id="percentage" name="percentage">
<option th:each="i : ${#numbers.sequence(0, 100)}" th:value="${i}" th:text="${i}" th:selected="${i==75}"></option>
</select></td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -0,0 +1,13 @@
package com.baeldung.thymeleaf;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ApplicationIntegrationTest {
@Test
public void contextLoads() {
}
}

View File

@@ -0,0 +1,41 @@
package com.baeldung.thymeleaf.cssandjs;
import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = CssAndJsApplication.class)
public class CssAndJsControllerIntegrationTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
public void whenCalledGetStyledPage_thenReturnContent() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.get("/styled-page"))
.andExpect(status().isOk())
.andExpect(view().name("cssandjs/styledPage"))
.andExpect(content().string(containsString("Carefully Styled Heading")));
}
}

View File

@@ -0,0 +1,68 @@
package com.baeldung.thymeleaf.currencies;
import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc(printOnlyOnFailure = false)
public class CurrenciesControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenCallCurrencyWithSpanishLocale_ThenReturnProperCurrency() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "es-ES")
.param("amount", "10032.5"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("10.032,50")));
}
@Test
public void whenCallCurrencyWithUSALocale_ThenReturnProperCurrency() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "en-US")
.param("amount", "10032.5"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("$10,032.50")));
}
@Test
public void whenCallCurrencyWithRomanianLocaleWithArrays_ThenReturnLocaleCurrencies() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "en-GB")
.param("amountList", "10", "20", "30"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("£10.00, £20.00, £30.00")));
}
@Test
public void whenCallCurrencyWithUSALocaleWithoutDecimal_ThenReturnCurrencyWithoutTrailingZeros() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "en-US")
.param("amount", "10032"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("$10,032")));
}
@Test
public void whenCallCurrencyWithUSALocale_ThenReturnReplacedDecimalPoint() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "en-US")
.param("amount", "1.5"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("1,5")));
}
}