JAVA-3524: Moved spring-mvc-forms-thymeleaf inside spring-web-modules
This commit is contained in:
9
spring-web-modules/spring-mvc-forms-thymeleaf/README.md
Normal file
9
spring-web-modules/spring-mvc-forms-thymeleaf/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## Spring MVC Forms Thymeleaf
|
||||
|
||||
This module contains articles about Spring MVC Forms using Thymeleaf
|
||||
|
||||
### Relevant articles
|
||||
|
||||
- [Session Attributes in Spring MVC](https://www.baeldung.com/spring-mvc-session-attributes)
|
||||
- [Binding a List in Thymeleaf](https://www.baeldung.com/thymeleaf-list)
|
||||
|
||||
45
spring-web-modules/spring-mvc-forms-thymeleaf/pom.xml
Normal file
45
spring-web-modules/spring-mvc-forms-thymeleaf/pom.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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-mvc-forms-thymeleaf</artifactId>
|
||||
<name>spring-mvc-forms-thymeleaf</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>spring forms examples using thymeleaf</description>
|
||||
|
||||
<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-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- runtime and test scoped -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
|
||||
<start-class>com.baeldung.sessionattrs.SessionAttrsApplication</start-class>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.baeldung.listbindingexample;
|
||||
|
||||
public class Book {
|
||||
|
||||
private long id;
|
||||
|
||||
private String title;
|
||||
|
||||
private String author;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((author == null) ? 0 : author.hashCode());
|
||||
result = prime * result + (int) (id ^ (id >>> 32));
|
||||
result = prime * result + ((title == null) ? 0 : title.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Book other = (Book) obj;
|
||||
if (author == null) {
|
||||
if (other.author != null)
|
||||
return false;
|
||||
} else if (!author.equals(other.author))
|
||||
return false;
|
||||
if (id != other.id)
|
||||
return false;
|
||||
if (title == null) {
|
||||
if (other.title != null)
|
||||
return false;
|
||||
} else if (!title.equals(other.title))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Book [id=" + id + ", title=" + title + ", author=" + author + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.baeldung.listbindingexample;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface BookService {
|
||||
|
||||
List<Book> findAll();
|
||||
|
||||
void saveAll(List<Book> books);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baeldung.listbindingexample;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BooksCreationDto {
|
||||
|
||||
private List<Book> books;
|
||||
|
||||
public BooksCreationDto() {
|
||||
this.books = new ArrayList<>();
|
||||
}
|
||||
|
||||
public BooksCreationDto(List<Book> books) {
|
||||
this.books = books;
|
||||
}
|
||||
|
||||
public List<Book> getBooks() {
|
||||
return books;
|
||||
}
|
||||
|
||||
public void setBooks(List<Book> books) {
|
||||
this.books = books;
|
||||
}
|
||||
|
||||
public void addBook(Book book) {
|
||||
this.books.add(book);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.baeldung.listbindingexample;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.thymeleaf.templatemode.TemplateMode;
|
||||
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
|
||||
import org.thymeleaf.templateresolver.ITemplateResolver;
|
||||
|
||||
@EnableWebMvc
|
||||
@Configuration
|
||||
public class Config implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController("/")
|
||||
.setViewName("index");
|
||||
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ITemplateResolver templateResolver() {
|
||||
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
|
||||
resolver.setPrefix("templates/books/");
|
||||
resolver.setSuffix(".html");
|
||||
resolver.setTemplateMode(TemplateMode.HTML);
|
||||
resolver.setCharacterEncoding("UTF-8");
|
||||
return resolver;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.baeldung.listbindingexample;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class InMemoryBookService implements BookService {
|
||||
|
||||
static Map<Long, Book> booksDB = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public List<Book> findAll() {
|
||||
return new ArrayList<>(booksDB.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAll(List<Book> books) {
|
||||
long nextId = getNextId();
|
||||
for (Book book : books) {
|
||||
if (book.getId() == 0) {
|
||||
book.setId(nextId++);
|
||||
}
|
||||
}
|
||||
|
||||
Map<Long, Book> bookMap = books.stream()
|
||||
.collect(Collectors.toMap(Book::getId, Function.identity()));
|
||||
|
||||
booksDB.putAll(bookMap);
|
||||
}
|
||||
|
||||
private Long getNextId() {
|
||||
return booksDB.keySet()
|
||||
.stream()
|
||||
.mapToLong(value -> value)
|
||||
.max()
|
||||
.orElse(0) + 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.baeldung.listbindingexample;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = { SecurityAutoConfiguration.class, DataSourceAutoConfiguration.class })
|
||||
public class ListBindingApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ListBindingApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.baeldung.listbindingexample;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/books")
|
||||
public class MultipleBooksController {
|
||||
|
||||
@Autowired
|
||||
private BookService bookService;
|
||||
|
||||
@GetMapping(value = "/all")
|
||||
public String showAll(Model model) {
|
||||
model.addAttribute("books", bookService.findAll());
|
||||
|
||||
return "allBooks";
|
||||
}
|
||||
|
||||
@GetMapping(value = "/create")
|
||||
public String showCreateForm(Model model) {
|
||||
BooksCreationDto booksForm = new BooksCreationDto();
|
||||
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
booksForm.addBook(new Book());
|
||||
}
|
||||
|
||||
model.addAttribute("form", booksForm);
|
||||
|
||||
return "createBooksForm";
|
||||
}
|
||||
|
||||
@GetMapping(value = "/edit")
|
||||
public String showEditForm(Model model) {
|
||||
List<Book> books = new ArrayList<>();
|
||||
bookService.findAll()
|
||||
.iterator()
|
||||
.forEachRemaining(books::add);
|
||||
|
||||
model.addAttribute("form", new BooksCreationDto(books));
|
||||
|
||||
return "editBooksForm";
|
||||
}
|
||||
|
||||
@PostMapping(value = "/save")
|
||||
public String saveBooks(@ModelAttribute BooksCreationDto form, Model model) {
|
||||
bookService.saveAll(form.getBooks());
|
||||
|
||||
model.addAttribute("books", bookService.findAll());
|
||||
|
||||
return "redirect:/books/all";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.annotation.ScopedProxyMode;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.thymeleaf.templatemode.TemplateMode;
|
||||
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
|
||||
import org.thymeleaf.templateresolver.ITemplateResolver;
|
||||
|
||||
@EnableWebMvc
|
||||
@Configuration
|
||||
public class Config implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController("/").setViewName("index");
|
||||
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope(
|
||||
value = WebApplicationContext.SCOPE_SESSION,
|
||||
proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||
public TodoList todos() {
|
||||
return new TodoList();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ITemplateResolver templateResolver() {
|
||||
ClassLoaderTemplateResolver resolver
|
||||
= new ClassLoaderTemplateResolver();
|
||||
resolver.setPrefix("templates/sessionattrs/");
|
||||
resolver.setSuffix(".html");
|
||||
resolver.setTemplateMode(TemplateMode.HTML);
|
||||
resolver.setCharacterEncoding("UTF-8");
|
||||
return resolver;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(
|
||||
exclude = {SecurityAutoConfiguration.class,
|
||||
DataSourceAutoConfiguration.class})
|
||||
public class SessionAttrsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SessionAttrsApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/scopedproxy")
|
||||
public class TodoControllerWithScopedProxy {
|
||||
|
||||
private TodoList todos;
|
||||
|
||||
public TodoControllerWithScopedProxy(TodoList todos) {
|
||||
this.todos = todos;
|
||||
}
|
||||
|
||||
@GetMapping("/form")
|
||||
public String showForm(Model model) {
|
||||
if (!todos.isEmpty()) {
|
||||
model.addAttribute("todo", todos.peekLast());
|
||||
} else {
|
||||
model.addAttribute("todo", new TodoItem());
|
||||
}
|
||||
|
||||
return "scopedproxyform";
|
||||
}
|
||||
|
||||
@PostMapping("/form")
|
||||
public String create(@ModelAttribute TodoItem todo) {
|
||||
todo.setCreateDate(LocalDateTime.now());
|
||||
todos.add(todo);
|
||||
return "redirect:/scopedproxy/todos.html";
|
||||
}
|
||||
|
||||
@GetMapping("/todos.html")
|
||||
public String list(Model model) {
|
||||
model.addAttribute("todos", todos);
|
||||
return "scopedproxytodos";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/sessionattributes")
|
||||
@SessionAttributes("todos")
|
||||
public class TodoControllerWithSessionAttributes {
|
||||
|
||||
@GetMapping("/form")
|
||||
public String showForm(
|
||||
Model model,
|
||||
@ModelAttribute("todos") TodoList todos) {
|
||||
if (!todos.isEmpty()) {
|
||||
model.addAttribute("todo", todos.peekLast());
|
||||
} else {
|
||||
model.addAttribute("todo", new TodoItem());
|
||||
}
|
||||
return "sessionattributesform";
|
||||
}
|
||||
|
||||
@PostMapping("/form")
|
||||
public RedirectView create(
|
||||
@ModelAttribute TodoItem todo,
|
||||
@ModelAttribute("todos") TodoList todos,
|
||||
RedirectAttributes attributes) {
|
||||
todo.setCreateDate(LocalDateTime.now());
|
||||
todos.add(todo);
|
||||
attributes.addFlashAttribute("todos", todos);
|
||||
return new RedirectView("/sessionattributes/todos.html");
|
||||
}
|
||||
|
||||
@GetMapping("/todos.html")
|
||||
public String list(
|
||||
Model model,
|
||||
@ModelAttribute("todos") TodoList todos) {
|
||||
model.addAttribute("todos", todos);
|
||||
return "sessionattributestodos";
|
||||
}
|
||||
|
||||
@ModelAttribute("todos")
|
||||
public TodoList todos() {
|
||||
return new TodoList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class TodoItem {
|
||||
|
||||
private String description;
|
||||
private LocalDateTime createDate;
|
||||
|
||||
public TodoItem(String description, LocalDateTime createDate) {
|
||||
this.description = description;
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
public TodoItem() {
|
||||
// default no arg constructor
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateDate() {
|
||||
return createDate;
|
||||
}
|
||||
|
||||
public void setCreateDate(LocalDateTime createDate) {
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TodoItem [description=" + description + ", createDate=" + createDate + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TodoList extends ArrayDeque<TodoItem>{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
server.port=8081
|
||||
|
||||
logging.level.root=INFO
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,40 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>All Books</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1> Books </h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Title </th>
|
||||
<th> Author </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:if="${books.empty}">
|
||||
<td colspan="2"> No Books Available </td>
|
||||
</tr>
|
||||
<tr th:each="book : ${books}">
|
||||
<td><span th:text="${book.title}"> Title </span></td>
|
||||
<td><span th:text="${book.author}"> Author </span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<a class="btn btn-success" href="#" th:href="@{/books/create}"> Add Books </a>
|
||||
<a class="btn btn-primary" href="#" th:href="@{/books/edit}" th:classappend="${books.empty} ? 'disabled' : ''"> Edit Books </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Add Books</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1> Add Books </h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<a class="btn btn-info" href="#" th:href="@{/books/all}"> Back to All Books </a>
|
||||
<form action="#" class="form-horizontal"
|
||||
th:action="@{/books/save}"
|
||||
th:object="${form}"
|
||||
method="post">
|
||||
<fieldset>
|
||||
<span class="pull-right">
|
||||
<input type="submit" id="submitButton" class="btn btn-success" th:value="Save">
|
||||
<input type="reset" id="resetButton" class="btn btn-danger" th:value="Reset"/>
|
||||
</span>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Title</th>
|
||||
<th> Author</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="book, itemStat : *{books}">
|
||||
<td><input th:placeholder="Title + ' ' + ${itemStat.count}" th:field="*{books[__${itemStat.index}__].title}" required/></td>
|
||||
<td><input th:placeholder="Author + ' ' + ${itemStat.count}" th:field="*{books[__${itemStat.index}__].author}" required/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Edit Books</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1> Add Books </h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<a class="btn btn-info" href="#" th:href="@{/books/all}"> Back to All Books </a>
|
||||
<form action="#" class="form-horizontal"
|
||||
th:action="@{/books/save}"
|
||||
th:object="${form}"
|
||||
method="post">
|
||||
<fieldset>
|
||||
<span class="pull-right">
|
||||
<input type="submit" id="submitButton" class="btn btn-success" th:value="Save">
|
||||
<input type="reset" id="resetButton" class="btn btn-danger" th:value="Reset"/>
|
||||
</span>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th> Title</th>
|
||||
<th> Author</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="book, itemStat : ${form.books}">
|
||||
<td><input hidden th:name="|books[${itemStat.index}].id|" th:value="${book.getId()}"/></td>
|
||||
<td><input th:placeholder="Title + ' ' + ${itemStat.count}" th:name="|books[${itemStat.index}].title|" th:value="${book.getTitle()}"
|
||||
required/></td>
|
||||
<td><input th:placeholder="Author + ' ' + ${itemStat.count}" th:name="|books[${itemStat.index}].author|"
|
||||
th:value="${book.getAuthor()}" required/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
|
||||
<head>
|
||||
<title>Binding a List in Thymeleaf</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>
|
||||
<h3>Binding a List in Thymeleaf - Example</h3>
|
||||
</p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<a class="btn btn-info" href="#" th:href="@{/books/all}"> Books List </a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
|
||||
<head>
|
||||
<title>Session Attributes in Spring MVC</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>
|
||||
<h3>Session Scope in Spring MVC - Example</h3>
|
||||
</p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="/scopedproxy/form" class="btn btn-sm btn-primary btn-block float-right" role="button">Session Scoped Proxy Example</a></div>
|
||||
<div class="col">
|
||||
<a href="/sessionattributes/form" class="btn btn-sm btn-primary btn-block float-right" role="button">Session Attributes Example</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
|
||||
<head>
|
||||
<title>Session Attributes in Spring MVC</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>
|
||||
<h3>Scoped Proxy Example</h3>
|
||||
</p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h5>Enter a TODO</h5>
|
||||
<form action="#" th:action="@{/scopedproxy/form}" th:object="${todo}" method="post">
|
||||
<div class="input-group">
|
||||
<input type="text" th:field="*{description}" class="form-control" placeholder="TODO" required autofocus/>
|
||||
<button class="btn btn-sm btn-primary form-control text-center" type="submit" style="max-width: 80px;">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
|
||||
<head>
|
||||
<title>Session Attributes in Spring MVC</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"
|
||||
crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>
|
||||
<h3>Scoped Proxy Example</h3>
|
||||
</p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<br/>
|
||||
<div class="row">
|
||||
<a th:href="@{/scopedproxy/form}" class="btn btn-sm btn-primary btn-block" role="button"
|
||||
style="width: 10em;">Add New</a>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col card" style="padding: 1em;">
|
||||
<div class="card-block">
|
||||
<h5 class="card-title">TODO List</h5>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="75%">Description</th>
|
||||
<th width="25%" >Create Date</th>
|
||||
</tr>
|
||||
<tr th:each="todo : ${todos}">
|
||||
<td th:text="${todo.description}">Description</td>
|
||||
<td th:text="${#temporals.format(todo.createDate, 'MM-dd-yyyy HH:mm:ss')}">Create Date</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
|
||||
<head>
|
||||
<title>Session Attributes in Spring MVC</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>
|
||||
<h3>Session Attributes Example</h3>
|
||||
</p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h5>Enter a TODO</h5>
|
||||
<form action="#" th:action="@{/sessionattributes/form}" th:object="${todo}" method="post">
|
||||
<div class="input-group">
|
||||
<input type="text" th:field="*{description}" class="form-control" placeholder="TODO" required autofocus/>
|
||||
<button class="btn btn-sm btn-primary form-control text-center" type="submit" style="max-width: 80px;">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
|
||||
<head>
|
||||
<title>Session Attributes in Spring MVC</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"
|
||||
crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>
|
||||
<h3>Session Attributes Example</h3>
|
||||
</p>
|
||||
</div>
|
||||
<div class="container">
|
||||
<br/>
|
||||
<div class="row">
|
||||
<a th:href="@{/sessionattributes/form}" class="btn btn-sm btn-primary btn-block" role="button"
|
||||
style="width: 10em;">Add New</a>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col card" style="padding: 1em;">
|
||||
<div class="card-block">
|
||||
<h5 class="card-title">TODO List</h5>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="75%">Description</th>
|
||||
<th width="25%" >Create Date</th>
|
||||
</tr>
|
||||
<tr th:each="todo : ${todos}">
|
||||
<td th:text="${todo.description}">Description</td>
|
||||
<td th:text="${#temporals.format(todo.createDate, 'MM-dd-yyyy HH:mm:ss')}">Create Date</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.listbindingexample;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import com.baeldung.listbindingexample.ListBindingApplication;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = {ListBindingApplication.class})
|
||||
public class SpringContextTest {
|
||||
|
||||
@Test
|
||||
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class SessionAttrsApplicationIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import com.baeldung.sessionattrs.SessionAttrsApplication;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = {SessionAttrsApplication.class})
|
||||
public class SpringContextTest {
|
||||
|
||||
@Test
|
||||
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import org.springframework.beans.factory.config.CustomScopeConfigurer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.SimpleThreadScope;
|
||||
|
||||
@Configuration
|
||||
public class TestConfig {
|
||||
|
||||
@Bean
|
||||
public CustomScopeConfigurer customScopeConfigurer() {
|
||||
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
|
||||
configurer.addScope("session", new SimpleThreadScope());
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Before;
|
||||
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.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
|
||||
@AutoConfigureMockMvc
|
||||
@Import(TestConfig.class)
|
||||
public class TodoControllerWithScopedProxyIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext wac;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenFirstRequest_thenContainsUnintializedTodo() throws Exception {
|
||||
MvcResult result = mockMvc.perform(get("/scopedproxy/form"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("todo"))
|
||||
.andReturn();
|
||||
|
||||
TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
|
||||
assertFalse(StringUtils.isEmpty(item.getDescription()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenSubmit_thenSubsequentFormRequestContainsMostRecentTodo() throws Exception {
|
||||
mockMvc.perform(post("/scopedproxy/form")
|
||||
.param("description", "newtodo"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
MvcResult result = mockMvc.perform(get("/scopedproxy/form"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("todo"))
|
||||
.andReturn();
|
||||
TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
|
||||
assertEquals("newtodo", item.getDescription());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.baeldung.sessionattrs;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Before;
|
||||
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.MvcResult;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.FlashMap;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
|
||||
@AutoConfigureMockMvc
|
||||
public class TodoControllerWithSessionAttributesIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext wac;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenFirstRequest_thenContainsUnintializedTodo() throws Exception {
|
||||
MvcResult result = mockMvc.perform(get("/sessionattributes/form"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("todo"))
|
||||
.andReturn();
|
||||
|
||||
TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
|
||||
assertTrue(StringUtils.isEmpty(item.getDescription()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenSubmit_thenSubsequentFormRequestContainsMostRecentTodo() throws Exception {
|
||||
FlashMap flashMap = mockMvc.perform(post("/sessionattributes/form")
|
||||
.param("description", "newtodo"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn().getFlashMap();
|
||||
|
||||
MvcResult result = mockMvc.perform(get("/sessionattributes/form")
|
||||
.sessionAttrs(flashMap))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("todo"))
|
||||
.andReturn();
|
||||
TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
|
||||
assertEquals("newtodo", item.getDescription());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user