Spring boot thymeleaf (field.hasError always false) - spring

I am building a web application and as part of my application, I am using Spring boot with Thymeleaf.
Spring version: 2.5.6, JDK: 17
When I try to submit form with some expected errors in input, the binding result identifies the errors and prints in the controller but in Thymeleaf when I check for errors nothing shows up.
POM.XML
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<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-data-jpa</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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
Application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/unknown
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.sql.init.mode=never
spring.thymeleaf.cache=false
spring.thymeleaf.check-template=true
spring.thymeleaf.check-template-location=true
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
logging.level.org.springframework=INFO
logging.level.org.thymeleaf=ERROR
logging.level.org.springframework.boot=ERROR
HMTL with Thymeleaf:
<form class="row mb-0" action="#" th:action="#{/post}" th:object="${formData}" method="post">
<div class="col-md-6 form-group">
<label for="name">Name <small>*</small></label>
<label th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="validation-message"></label>
<input type="text" id="name" th:field="*{name}" class="sm-form-control" />
</div>
<div class="col-md-6 form-group">
<label for="email">Email <small>*</small></label>
<label th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="validation-message"></label>
<input type="email" id="email" th:field="*{email}" class="sm-form-control" />
</div>
<div class="w-100"></div>
<div class="col-md-6 form-group">
<label for="phone">Phone</label>
<label th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}" class="validation-message"></label>
<input type="text" id="phone" th:field="*{phone}" class="sm-form-control" />
</div>
<div class="col-md-6 form-group">
<label for="service">Services</label>
<label th:if="${#fields.hasErrors('service')}" th:errors="*{service}" class="validation-message"></label>
<select id="service" class="sm-form-control" th:field="*{service}" >
<option value="">-- Select One --</option>
<option th:each="service : ${T(com.utterlydesi.web.constant.Service).values()}"
th:value="${service}"
th:text="${service}">service
</option>
</select>
</div>
<div class="w-100"></div>
<div class="col-12 form-group">
<label for="title">Title <small>*</small></label>
<div class="alert alert-warning" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></div>
<input type="text" id="title" th:field="*{title}" class="sm-form-control" />
</div>
<div class="col-12 form-group">
<label for="description">Description <small>*</small></label>
<div class="alert alert-warning" th:if="${#fields.hasErrors('description')}" th:errors="*{description}"></div>
<textarea class="required sm-form-control" id="description" name="template-contactform-message" rows="6" cols="30" th:field="*{description}"></textarea>
</div>
<div class="col-12 form-group">
<button class="button button-3d m-0" type="submit" id="add-post-submit" value="submit" th:text="Submit"></button>
</div>
<div th:if="${result != null}">
<span class="alert alert-primary" role="alert" th:utext="${result}"></span>
</div>
<ul th:if="${#fields.hasErrors('*')}">
<li th:each="error : ${#fields.errors('*')}" th:text="${error}">error</li>
</ul>
<ul th:if="${#fields.hasErrors('global')}">
<li th:each="error : ${#fields.errors('global')}" th:text="${error}">error</li>
</ul>
</form>
Controller:
#Controller
public class PostController {
#GetMapping(value = {"/post"})
public String post(Model model) {
model.addAttribute("formData", new PostAdd());
return "post";
}
#PostMapping("/post")
public String addPost(#ModelAttribute("formData") #Valid PostAdd formData, BindingResult bindingResult, Model model) {
try {
System.out.println(formData.toString());
if (bindingResult.hasErrors()) {
System.out.println("2");
System.out.println(bindingResult.hasErrors() + " " + bindingResult.hasFieldErrors("description"));
System.out.println(model.asMap());
model.addAttribute("result", "There was an issue with registration. Please corrected the fields highlighted in red");
return "post";
} else {
System.out.println("3");
//model.addAttribute(POST_ADD_OBJ_KEY, new PostAdd());
//model.addAttribute("result", "Post added successfully");
}
System.out.println(bindingResult.hasErrors() + " " + bindingResult.hasFieldErrors("description"));
return "redirect:/index";
} catch (Exception e) {
e.printStackTrace();
return "redirect:/index";
}
}
public PostController() {
}
}
PostAdd.java
#Data
public class PostAdd {
#NotEmpty(message = "*Please provide a name")
private String name;
#Email(message = "*Please provide a valid Email")
#NotEmpty(message = "*Please provide an email")
private String email;
#Length(min = 10, message = "*Your phone number must have at least 10 characters")
#NotEmpty(message = "*Please provide a valid phone number")
private String phone;
#NotNull(message = "*Please select a valid service")
private Service service;
#Length(min = 5, message = "*Please add a more descriptive title")
#NotEmpty(message = "*Please provide a title")
private String title;
#Length(min = 100, message = "*Please make your description more descriptive. At least 100 characters is required")
#NotEmpty(message = "*Please enter description")
private String description;
}
SOP output from controller:
PostAdd(name=First Middle Last, email=fml#gmail.com, phone=11111111111, service=null, title=11, description=111)
2
true true
{formData=PostAdd(name=First Middle Last, email=fml#gmail.com, phone=10123456789, service=null, title=11, description=111), org.springframework.validation.BindingResult.formData=org.springframework.validation.BeanPropertyBindingResult: 3 errors
Field error in object 'formData' on field 'description': rejected value [111]; codes [Length.formData.description,Length.description,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [formData.description,description]; arguments []; default message [description],2147483647,100]; default message [*Please make your description more descriptive. At least 100 characters is required]
Field error in object 'formData' on field 'service': rejected value [null]; codes [NotNull.formData.service,NotNull.service,NotNull.com.utterlydesi.web.constant.Service,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [formData.service,service]; arguments []; default message [service]]; default message [*Please select a valid service]
Field error in object 'formData' on field 'title': rejected value [11]; codes [Length.formData.title,Length.title,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [formData.title,title]; arguments []; default message [title],2147483647,5]; default message [*Please add a more descriptive title]}
What I have tried so far based on some other posts I have seen on Stackoverflow?
Downgrade to JDK 8
Downgrade to Spring boot less than 2.3.0
Add #EnableWebMVC
Replaced #PostMapping with #RequestMapping
Switched places of #Valid with #ModelAttribute

Related

Spring boot maven mysql crud in single html page

I created a spring boot crud application using a single HTML form. I want to display data in the text field for modification when I click the edit button. But print particular data in the console not appeared in the text field. I can not found what the problem.
Person Entity class
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "persion_id")
private int persondId;
#Column(name = "persion_name")
private String personName;
#Column(name = "persion_contact_no")
private String personContactNo;
#Column(name = "persion_address")
private String personAddress;
public Person() {
}
public Person(int persondId, String personName, String personContactNo, String personAddress) {
this.persondId = persondId;
this.personName = personName;
this.personContactNo = personContactNo;
this.personAddress = personAddress;
}
public int getPersondId() {
return persondId;
}
public void setPersondId(int persondId) {
this.persondId = persondId;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
public String getPersonContactNo() {
return personContactNo;
}
public void setPersonContactNo(String personContactNo) {
this.personContactNo = personContactNo;
}
public String getPersonAddress() {
return personAddress;
}
public void setPersonAddress(String personAddress) {
this.personAddress = personAddress;
}
#Override
public String toString() {
return "Person [persondId=" + persondId + ", personName=" + personName + ", personContactNo=" + personContactNo
+ ", personAddress=" + personAddress + "]";
}
}
Controller Class
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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.obydul.spring.model.Person;
import com.obydul.spring.service.PersonService;
#Controller
public class PersonController {
#Autowired
private PersonService personService;
#RequestMapping("/")
public String home() {
return "person";
}
#RequestMapping("/person")
public String showPerson(Model model) {
Person person = new Person();
model.addAttribute("person", person);
model.addAttribute("persons", personService.getAllPersons());
return "person";
}
#RequestMapping(value = "/person_save", method = RequestMethod.POST)
public String savePerson(#ModelAttribute Person person, Model model) {
personService.savePerson(person);
// model.addAttribute("persons", personService.getAllPersons());
return "redirect:/person";
}
#RequestMapping("/person_edit/{persondId}")
public String editPerson(#PathVariable int persondId, Model model) {
model.addAttribute("person", personService.getPersonById(persondId));
System.out.println("person edit :: " + personService.getPersonById(persondId));
return "redirect:/person";
}
#GetMapping("/persons/{persondId}")
public String deleteStudent(#PathVariable int persondId) {
personService.deletePersonById(persondId);
return "redirect:/person";
}
}
Person.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Person Information Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
</head>
<body>
<br>
<div class="container">
<div class="col-lg-10 col-md-8 col-sm-6 container justify-content-center card">
<h3 class="text-center">Person Info</h3>
<div class="card-body">
<form action="#" th:action="#{/person_save}" th:object="${person}"
method="post">
<div class="form-group">
<label>Person Name</label> <input type="text" name="personName"
th:field="*{personName}" class="form-control" />
</div>
<div class="form-group">
<label>Contact No</label> <input type="text"
name="personContactNo" th:field="*{personContactNo}"
class="form-control" />
</div>
<div class="form-group">
<label>Address</label> <input type="text" name="personAddress"
th:field="*{personAddress}" class="form-control" />
</div>
<div class="box-footer">
<button type="submit" class="btn btn-success">Save</button>
<button type="reset" class="btn btn-info">Reset</button>
</div>
<input type="hidden" id="persondId" th:field="*{persondId}"/>
<br>
<table class="table table-striped table-bordered">
<thead class="table-dark">
<tr>
<th class="text-center">Person Id</th>
<th class="text-center">Person Name</th>
<th class="text-center">Contact No</th>
<th class="text-center">Address</th>
<th class="text-center">Edit</th>
<th class="text-center">Delete</th>
</tr>
</thead>
<tbody>
<tr th:each="person: ${persons}">
<td class="text-center" th:text="${person.persondId}" />
<td th:text="${person.personName}" />
<td class="text-center" th:text="${person.personContactNo}" />
<td th:text="${person.personAddress}" />
<td class="text-center">
<a th:href="#{/person_edit/{persondId}(persondId=${person.persondId})}" class="btn btn-primary">Edit</a>
</td>
<td class="text-center">
<a th:href="#{/persons/{persondId}(persondId=${person.persondId})}" class="btn btn-danger">Delete</a>
</td>
</tr>
</tbody>
</table>
</form>
</div>
</div>
</div>
</body>
</html>
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.obydul.spring </groupId>
<artifactId>SpringBoot_Hibernate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>SpringBoot_Hibernate</name>
<description>spring boot hibernate one to many relationship</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</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>
</plugin>
</plugins>
</build>
</project>
Person Info Screen
Console output
person edit :: Person [persondId=8, personName=John, personContactNo=021450000, personAddress=Dhaka]
replace this to line
#RequestMapping("/person_edit/{persondId}")
with this one:
#GetMapping("/person_edit/{persondId}")
Accoring to https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#inputs, you shouldn't be specifyin the name attribute on the form input elements. So instead of
<input type="text" name="personName" th:field="*{personName}" class="form-control" />
You should use
<input type="text" th:field="*{personName}" class="form-control" />
thymeleaf will populate the name attribute for you. I'm not sure if that's the reason why the value isn't being set though.
You've written person*d*Id not personId
<a th:href="#{/person_edit/{persondId}(persondId=${person.persondId})}" class="btn btn-primary">Edit</a>
try this instead:
<a th:href="#{/person_edit/{personId}(personId=${person.personId})}" class="btn btn-primary">Edit</a>
And it might work
It's work. I changed the editing part from the controller like this.
#GetMapping("/person_edit/{persondId}")
public String editPerson(#PathVariable int persondId, Model model) {
model.addAttribute("person", personService.getPersonById(persondId));
model.addAttribute("persons", personService.getAllPersons());
return "person";
}

Show Springboot validation results in Thymeleaf template

I am getting started with Springboot and am unable to propagate validation results (errors) back to the thyme template. I have a typical setup: #Entity object, #Service, and #Repository. Here are the sections of my Controller and index template (and its form). UserVital, UserBlood, etc. are the data objects mapped to the DB tables using hibernate. Hope this information is enough for the members to point me in the right direction.
Data Object
#Entity
#Table(name = ".....")
public class UserVital {
#NotNull(message = "Height (Feet) cannot be null")
#Range(min = 0, max = 15, message = "Height (Feet) must be greater than 0")
#Column(name = "feet", nullable = false)
private int heightInFeet;
.............
}
Controller
#GetMapping("/")
public String getUsers(Model model) {
UserVital vital = new UserVital();
UserGrithMeasurements grith = new UserGrithMeasurements();
UserBloodChemistry blood = new UserBloodChemistry();
List<Category> categories = categoryService.getAllCategories();
model.addAttribute("categories", categories.get(0));
model.addAttribute("vital", vital);
model.addAttribute("grith", grith);
model.addAttribute("blood", blood);
return "index";
}
#PostMapping("/add")
public String addData(#Valid UserVital vital, BindingResult vitalValidationResult,
#Valid UserGrithMeasurements grith, BindingResult grithValidationResult, #Valid UserBloodChemistry blood,
BindingResult bloodValidationResult, Model model) {
if (vitalValidationResult.hasErrors() || grithValidationResult.hasErrors()
|| bloodValidationResult.hasErrors()) {
return "index";
} else {
model.addAttribute("successMsg", "Details saved successfully!!");
return "index";
}
}
Thyme Form
<form class="tab-content" method="POST" th:action="#{/add}">
<div class="form-group row">
<label for="height" class="col-sm-2 control-label" th:text="#{height}"></label>
<div class="col-sm-2">
<input type="number" class="form-control" id="feet" th:attr="placeholder=#{feet}"
th:field="${vital.heightInFeet}">
</div>
<div class="form-group col-sm-12">
<label for="neck" class="col-sm-2 control-label" th:text="#{neck}"></label>
<div class="col-sm-2">
<input type="number" class="form-control" id="systolic" th:attr="placeholder=#{inches}"
th:field="${grith.neck}">
<div class="col-sm-2">
<input type="number" class="form-control" id="ldl" th:field="${blood.ldl}">
.....
</form>
Question: As you can see I have multiple BindingResult objects. Each BindingResult object holding the validation results of the respective data object (vitalValidationResult holds validation result of UserVital object vital, etc.). Now, how do I write "th:if" statements in the template that will allow me to check if there are any errors in the fields.
Thanks.
I have solved the problem by encapsulating the required form fields in a div and then placing "th:object=${objectName}" in the div.
<form >
<div class="tab-pane active text-center" id="tab_1_1" th:object=${vital}>
<div class="tab-pane active text-center" id="tab_1_2" th:object=${grith}>
</form>

Unable to fetch UI due to bean validation error getting BindingException

I am getting below error when I am hitting '/signup' API after '/index' API. I have gone through many links. I found mentioned in various resources that the issue is the order of #Valid and BindingResult param needs to be maintained and I see it already in my code still I am unable to find what exactly the problem is. Please help me resolve this issue:
2021-05-31 19:37:40.721 WARN 10224 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 5 errors
Field error in object 'journeyFoodOrder' on field 'contactNoOfGuide': rejected value [null]; codes [NotNull.journeyFoodOrder.contactNoOfGuide,NotNull.contactNoOfGuide,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [journeyFoodOrder.contactNoOfGuide,contactNoOfGuide]; arguments []; default message [contactNoOfGuide]]; default message [Mobile No Of Guide is mandatory]
Field error in object 'journeyFoodOrder' on field 'nameOfCenter': rejected value [null]; codes [NotBlank.journeyFoodOrder.nameOfCenter,NotBlank.nameOfCenter,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [journeyFoodOrder.nameOfCenter,nameOfCenter]; arguments []; default message [nameOfCenter]]; default message [Name Of Center is mandatory]
Field error in object 'journeyFoodOrder' on field 'mealRetrievalTime': rejected value [null]; codes [NotNull.journeyFoodOrder.mealRetrievalTime,NotNull.mealRetrievalTime,NotNull.java.util.Date,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [journeyFoodOrder.mealRetrievalTime,mealRetrievalTime]; arguments []; default message [mealRetrievalTime]]; default message [Meal retrieval time is mandatory]
Field error in object 'journeyFoodOrder' on field 'nameOfGuide': rejected value [null]; codes [NotBlank.journeyFoodOrder.nameOfGuide,NotBlank.nameOfGuide,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [journeyFoodOrder.nameOfGuide,nameOfGuide]; arguments []; default message [nameOfGuide]]; default message [Name Of Guide is mandatory]
Field error in object 'journeyFoodOrder' on field 'dateOfDeparture': rejected value [null]; codes [NotNull.journeyFoodOrder.dateOfDeparture,NotNull.dateOfDeparture,NotNull.java.util.Date,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [journeyFoodOrder.dateOfDeparture,dateOfDeparture]; arguments []; default message [dateOfDeparture]]; default message [Departure date is mandatory]]
Controller Code
#Controller
public class JourneyFoodOrderController {
private final JourneyFoodOrderRepository journeyFoodOrderRepository;
#Autowired
public JourneyFoodOrderController(JourneyFoodOrderRepository journeyFoodOrderRepository) {
// TODO Auto-generated constructor stub
this.journeyFoodOrderRepository = journeyFoodOrderRepository;
}
#GetMapping("/signup")
public String showSignUpForm(#Valid JourneyFoodOrder journeyFoodOrder) {
return "add-journeyFoodOrder";
}
#PostMapping("/addJourneyFoodOrder")
//#Valid AccountForm form, BindingResult result, (binding=false) Account account
public String addJourneyFoodOrder(#Valid JourneyFoodOrder journeyFoodOrder, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-journeyFoodOrder";
}
model.addAttribute("journeyFoodOrder", journeyFoodOrder);
journeyFoodOrderRepository.save(journeyFoodOrder);
return "redirect:/index";
}
#GetMapping("/index")
public String showJourneyFoodOrderList(Model model) {
model.addAttribute("journeyFoodOrders", journeyFoodOrderRepository.findAll());
return "index";
}
}
View Code
<body>
<div class="container">
<div class="row">
<div class="col-md-4 mt-5">
<div id="successMessage"class="alert alert-success" role="alert">
Order saved successfully
</div>
<div id="failureMessage" class="alert alert-danger" role="alert">
Invalid data
</div>
<form action="#" th:action="#{/addJourneyFoodOrder}" th:object="${journeyFoodOrder}"method="post">
<h2 class="mb-5">New Order</h2>
<div class="form-group">
<label for="nameOfCenter">Name of Center</label>
<input type="text" th:field="*{nameOfCenter}" class="form-control" placeholder="Name of Center">
<span th:if="${#fields.hasErrors('nameOfCenter')}" th:errors="*{nameOfCenter}"></span>
</div>
<div class="form-group">
<label for="nameOfGuide">Name of Guide/Teacher</label>
<input type="text" th:field="*{nameOfGuide}" class="form-control" placeholder="Name of Guide">
<span th:if="${#fields.hasErrors('nameOfGuide')}" th:errors="*{nameOfGuide}"></span>
</div>
<div class="form-group">
<label for="headCount">Head count</label>
<input type="number" th:field="*{headCount}" class="form-control" placeholder="Head count">
<span th:if="${#fields.hasErrors('headCount')}" th:errors="*{headCount}"></span>
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
</div>
<div class="form-group">
<label for="dateOfJourney">Mobile No Of Guide</label>
<input type="text" th:field="*{contactNoOfGuide}" class="form-control" placeholder="Mobile No Of Guide">
<span th:if="${#fields.hasErrors('contactNoOfGuide')}" th:errors="*{contactNoOfGuide}"></span>
</div>
<div class="form-group">
<label for="dateOfDeparture">Date of departure</label>
<div class="input-group date" id="datetimepicker2" data-target-input="nearest">
<input type="text" class="form-control datetimepicker-input" data-target="#datetimepicker2" th:field="*{dateOfDeparture}" id="date" placeholder="Choose departure date"/>
<span th:if="${#fields.hasErrors('dateOfDeparture')}" th:errors="*{dateOfDeparture}"></span>
<div class="input-group-append" data-target="#datetimepicker2" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar-alt"></i></div>
</div>
</div>
</div>
<div class="form-group">
<label for="date">Meal retrieval time:</label>
<div class="input-group date" id="datetimepicker1" data-target-input="nearest">
<input type="text" class="form-control datetimepicker-input" data-target="#datetimepicker1" th:field="*{mealRetrievalTime}" id="date" placeholder="Choose meal retrieval date and time"/>
<span th:if="${#fields.hasErrors('mealRetrievalTime')}" th:errors="*{mealRetrievalTime}"></span>
<div class="input-group-append" data-target="#datetimepicker1" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar-alt"></i></div>
</div>
</div>
</div>
Model
#Entity
public class JourneyFoodOrder{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotBlank(message="Name Of Center is mandatory")
private String nameOfCenter;
#NotBlank(message="Name Of Guide is mandatory")
private String nameOfGuide;
#NotNull(message="Head Count is mandatory")
private int headCount;
#NotNull(message="Mobile No Of Guide is mandatory")
private String contactNoOfGuide;
#NotNull(message="Departure date is mandatory")
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date dateOfDeparture;
#NotNull(message="Meal retrieval time is mandatory")
#DateTimeFormat(pattern = "dd/MM/yyyy h:mm a")
private Date mealRetrievalTime;
#NotNull(message="Thepla Count is mandatory")
private int thepla;
#NotNull(message="Puri Count is mandatory")
private int puri;
#NotNull(message="Roti Count is mandatory")
private int roti;
#NotNull(message="Achar count is mandatory")
private int achar;
#NotNull(message="Jam count is mandatory")
private int jam;
#NotNull(message="Bread count is mandatory")
private int bread;
#NotNull(message="Other items count is mandatory")
private int others;
//..............setters and getters
}
The problem is that you have #Valid annotation on the #GetMapping method:
#GetMapping("/signup")
public String showSignUpForm(#Valid JourneyFoodOrder journeyFoodOrder) {
return "add-journeyFoodOrder";
}
This indicates that JourneyFoodOrder already should be valid when showing the form for the first time, which is never true. Use this in stead:
#GetMapping("/signup")
public String showSignUpForm(JourneyFoodOrder journeyFoodOrder) {
return "add-journeyFoodOrder";
}
I personally find it clearer to do this:
#GetMapping("/signup")
public String showSignUpForm(Model model) {
model.addAttribute("journeyFoodOrder", new JourneyFoodOrder());
return "add-journeyFoodOrder";
}
But both will work fine.
Additional notes:
You need to use #ModelAttribute for your form data object parameter in the #PostMapping method:
public String addJourneyFoodOrder(#Valid #ModelAttribute("journeyFoodOrder") JourneyFoodOrder journeyFoodOrder, BindingResult result, Model model) {
...
}
After fixing the #GetMapping method, you will run into another problem because add-journeyFoodOrder.html has this line that should be removed:
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
You might want to use a dedicated form data object to bind to the HTML form instead of using your entity directly. See Form handling with Thymeleaf for more information on that.

Spring with Thymeleaf binding date in html form

I have a simple snippet of form like:
<form th:action="#{'/save'}" th:object="${account}" method="post">
<div class="form-group">
<label for="expirationDate">Expiration date</label>
<input type="datetime-local" class="form-control" id="expirationDate"
placeholder="Expiration date" th:field="*{expirationTime}"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
I'm passing there a filled object account and expirationTime is a LocalDateTime field. The problem is that the expirationTime is not bind with the value passed to the form (object which is passed is 100% correct).
Any idea why?
Edit: Simply put, Spring/Thymeleaf doesn't format a Java 8 date correctly for the datetime-local input type. Tell Spring how to format the value correctly with #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm").
The annotation #DateTimeFormat tells Thymeleaf how to format the date string when parsing/formatting. That includes producing the value= annotation in the HTML input, and reading in POST data submitted by the form. The datetime-local input type expects a value formatted yyyy-MM-dd'T'HH:mm, so we use that as the formatter in the model class.
Model Class:
public class DateContainer {
private String name;
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime dateTime;
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Controller:
#RequestMapping("/dateTest")
public String dateTest(final DateContainer dateContainer) {
if (dateContainer.getDateTime() == null) {
dateContainer.setDateTime(LocalDateTime.now());
}
return "dateTest";
}
Template:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org/">
<head></head>
<body>
<h1>Hello World!</h1>
<form th:action="#{/dateTest}" th:object="${dateContainer}">
<label>Name: </label><input type="text" th:field="*{name}"/>
<label>Date Time: </label><input type="datetime-local" th:field="*{dateTime}"/>
<input type="submit" value="Submit"/>
</form>
</body>
</html>
HTML Produced:
<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>Hello World!</h1>
<form action="/dateTest">
<label>Name: </label><input type="text" id="name" name="name" value="" />
<label>Date Time: </label><input type="datetime-local" id="dateTime" name="dateTime" value="2017-08-28T13:01" />
<input type="submit" value="Submit" />
<input type="hidden" name="_csrf" value="437b30dc-a103-44d0-b4e9-791d8de62986" /></form>
</body>
</html>
Screenshot:
Screenshot in Chrome 60
Comptibility:
The datetime-local input type is a new HTML5 type and isn't supported in all browsers. See compatibility: https://caniuse.com/#search=datetime-local
In non-compliant browsers, the datetime-local field will simply appear as a text field, and will contain the date and time with the T in between. That may lead to usability issues depending on your use case.
Make sure that you have this dependency:
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
After that just add the org.thymeleaf.extras.java8time.dialect.Java8TimeDialect class to the list of dialects in your TemplateEngine implementation, and you will have the #temporals object available to be used in your templates.
More details can be found here.

Spring Boot ModelAndView - cannot update value to Model

I am trying to create a form with 3 fields within the class LoginForm: usuario, senha, msgLogin.
I receive the fields usuário and senha from the input boxes and then I try to redirect to the same page with the same fields plus the field MsgLogin. But I've not been able to update the msgLogin field and I don't understand why.
These are the code:
HTML:
<form id="frmLogin" class="form col-md-12" action="#" th:action="#{/login}" th:object="${loginForm}" method="post">
<div class="form-group">
<label for="usuario" class="col-md-1">Usuário: </label>
<input type="text" id="usuario" placeholder="Email" th:field="*{usuario}"/>
</div>
<div class="form-group">
<label for="senha" class="col-md-1">Senha: </label>
<input type="password" id="senha" placeholder="senha" th:field="*{senha}"/>
</div>
<div class="row">
<button id="entrar">Entrar</button>
</div>
<div class="row">
<div id="msgLogin"></div>
<p th:text="${loginForm.msgLogin}" />
</div>
</form>
The Controller:
#RequestMapping("/")
public String init(#ModelAttribute LoginForm loginForm) {
Logger.getAnonymousLogger().info("Tela Inicial.");
return "home";
}
#PostMapping("/login")
public ModelAndView entrar(LoginForm loginForm) throws IOException {
Logger.getAnonymousLogger().info("Entrando no Internet Banking.");
service.login(usuario, senha);
ModelMap model = new ModelMap();
loginForm.setMsgLogin("I want to update that value!");
model.addAttribute("loginForm", loginForm);
return new ModelAndView("redirect:/", model);
}
Class using Lombok:
#Getter
#Setter
public class LoginForm {
private String usuario;
private String senha;
private String msgLogin;
}
A redirect send a 302 HTTP status back to the browser to resubmit using a new url, so it resubmits the same two fields to the new url. There's no expectation of payload data in the 302 response.
In your Controller, change the last line in your entrar method to: return new ModelAndView("/login", model);

Resources