Springboot Thymeleaf form validation not working - forms

I've trying to validate the fields in Thymeleaf but the errors are not showing in the view page.
My Controller
#Controller
public class UserController {
private static final String ADD_NEW_USER="user/addUser";
#Autowired
UserService userService;
#RequestMapping(value="/user/new", method=RequestMethod.GET)
public String addUser(Registration register, Model model){
model.addAttribute("register",register);
return ADD_NEW_USER;
}
#RequestMapping(value="/user/new", method=RequestMethod.POST)
public String addUser(#Valid Registration register, BindingResult result, Model model){
model.addAttribute("register",register);
if(result.hasErrors()){
List<FieldError> err=result.getFieldErrors();
for(FieldError e:err){
System.out.println("Error on object ---> "+e.getObjectName()+" on field ---> "+e.getField()+". Message ---> "+e.getDefaultMessage());
}
return ADD_NEW_USER;
}
return INDEX_PAGE;
}
}
View template
<form th:action="#{/user/new}" th:method="post" th:object="${register}" id="addUser" role="form">
<fieldset>
<legend>
<p>Field with <span class="required">*</span> are required</p>
</legend>
<div class="form-group">
<label for="name"><span class="required">* </span>Name: </label>
<input type="text" th:field="*{name}" class="form-control" />
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="alert alert-danger">
<p>Name is invalid</p>
</div>
<p class="help-block">Please provide full name for the user</p>
</div>
<div class="form-group">
<label for="email"><span class="required">* </span> Email Address: </label>
<input class="form-control" type="email" th:field="*{email}"/>
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="alert alert-danger">
<p>Email is invalid</p>
</div>
<p class="help-block">Please provide a valid email address. Activation link will be sent to this email</p>
</div>
<div class="form-group">
<label for="password"><span class="required">* </span> Password: </label>
<input type="password" th:field="*{password}" class="form-control"/>
</div>
<div class="form-group">
<input class="btn btn-success" type="submit" name="Register" value="Register"/>
</div>
</fieldset>
</form><!-- ends register form -->
Modal
#Entity
public class Registration {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#NotEmpty
#Size(min=3, max=50)
#Column(nullable=false)
private String name;
#Email
#NotEmpty
#Column(nullable=false)
private String email;
private String password;
//getters and setters
}
I can output the console error like this.
Error on object ---> registration on field ---> name. Message ---> size must be between 3 and 50
Error on object ---> registration on field ---> email. Message ---> may not be empty
Error on object ---> registration on field ---> name. Message ---> may not be empty
I might be missing something but unable to find.

the problem is that the name of the entity is Registration and that the name of the object that you use is register.
In order this to work add #ModelAttribute("register") next yo your #valid annotation
namely
#RequestMapping(value="/user/new", method=RequestMethod.POST)
public String addUser(#Valid #ModelAttribute("register") Registration register, BindingResult result, Model model){
Hope this helps

I was also facing the same issue. With may hit/trial approach I found that #Valid annotation doesn't work with spring 2.3.0.RELEASE
If you are using spring 2.3.0.RELEASE then add below dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Hope this helps.

I also faced the same issue. My bindingResult.hasErrors() was always returning true in spite of pulling no values in the form. I had to change my validation from #NotNull to #NotEmpty.
#NotNull will only validate if the object is null, #NotEmpty will check if the object has empty strings.

Related

Using One Thymeleaf Form for Partial Table Update and #OneToMany Persistence with Validation

I have a user table and a user_details table. The user table gets partially populated by a script and the remaining age and birthplace values get added manually by a user via a Thymeleaf form. As a part of the form I also want to add details in the user_details table. The relationship is a #OneToMany.
I am running into a few issues with Validation that I am not able to figure out properly:
Since only 2 fields of the 5 are in the form to save to table user, is using hidden inputs the best way to pass all the User field values to the save method in the event of a validation failure or is the a cleaner way (i.e., a JPA annotation type way, etc.)?
On the initial loading of the user, the UserDetails are iterated through in the user.html file with th:each="detail: ${user.userDetailsById}". However, when the save method fails validation and the user.html file is returned from the result.hasErrors() block, those details revert to null and do not display. Do I have to manually query those again?
The object in the form is a User object. If I want to pass a user_details comment in the form input how would this be structured and persisted into user.user.userDetailsById.comment?
I've included the code below.
create table user
(
id varchar(10) not null
primary key,
first_name varchar(100) not null,
last_name varchar(100) not null,
age int unsigned null,
birthplace varchar(100) null,
constraint user_id_uindex
unique (id)
);
create table user_details
(
id int unsigned auto_increment
primary key,
user varchar(10) null,
comments longtext null,
constraint user_details_id_uindex
unique (id),
constraint user_details_user_id_fk
foreign key (user) references user (id)
);
#Entity
#Data
#Table(name = "user", schema = "demo")
public class User {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id", nullable = false, length = 10)
private String id;
#Basic
#Column(name = "first_name", nullable = false, length = 100)
private String firstName;
#Basic
#Column(name = "last_name", nullable = false, length = 100)
private String lastName;
#Basic
#Column(name = "age", nullable = true)
private Integer age;
#Basic
#NotEmpty
#Column(name = "birthplace", nullable = true, length = 100)
private String birthplace;
#OneToMany(mappedBy = "userByUser")
private Collection<UserDetails> userDetailsById;
}
#Entity
#Data
#Table(name = "user_details", schema = "demo")
public class UserDetails {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id", nullable = false)
private int id;
#Basic
#Column(name = "user", nullable = true, length = 10)
private String user;
#Basic
#Column(name = "comments", nullable = true)
private String comments;
#ManyToOne
#JoinColumn(name = "user", referencedColumnName = "id", insertable = false, updatable = false)
private User userByUser;
}
#Controller
public class UserController {
private final UserRepository userRepository;
private final UserDetailsRepository userDetailsRepository;
public UserController(UserRepository userRepository, UserDetailsRepository userDetailsRepository) {
this.userRepository = userRepository;
this.userDetailsRepository = userDetailsRepository;
}
#GetMapping("/all")
public String getAllUsers(Model model) {
model.addAttribute("users", userRepository.findAll());
return "users";
}
#GetMapping("/{id}")
public String getUser(#PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "user";
}
#PostMapping("/save")
public String saveUser(#Valid User user, BindingResult result) {
if (result.hasErrors()) {
return "user";
}
userRepository.save(user);
return "redirect:/all";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Users</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css">
</head>
<body>
<main class="container p-5">
<section>
<div class="row">
<div class="col">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Age</th>
<th>Birthplace</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td><a class="text-dark" th:href="#{'/' + ${user.id}}" th:text="${user.id}"></a></td>
<td th:text="${user.firstName}"></td>
<td th:text="${user.lastName}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthplace}"></td>
</tbody>
<tfoot></tfoot>
</table>
</div>
</div>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Users</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css">
</head>
<body>
<main class="container p-5">
<section>
<p th:text="'User: ' + ${user.firstName + ' ' + user.lastName}"></p>
<!-- THE USER DETAILS BECOME NULL WHEN THE VALIDATION RETURNS BACK TO THE FORM -->
<div th:if="${user.userDetailsById}">
<div th:each="detail: ${user.userDetailsById}">
<p th:text="'Comment: ' + ${detail.comments}"></p>
</div>
</div>
</section>
<section>
<form action="#" th:action="#{'/save'}" th:object="${user}" method="post">
<!-- ARE MULTIPLE HIDDEN INPUTS THE BEST WAY ENSURE THAT WHEN VALIDATION FAILS THE TABLE FIELDS GET PASSED BACK TO THE FORM? -->
<input type="hidden" th:field="*{id}">
<input type="hidden" th:field="*{firstName}">
<input type="hidden" th:field="*{lastName}">
<div class="row">
<div class="col">
<div class="mt-3">
<label class="form-label" for="age">Age</label>
<input class="form-control" id="age" type="number" th:field="*{age}">
<div class="text-danger mt-2" th:if="${#fields.hasErrors('age')}"
th:errors="*{age}"></div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mt-3">
<label class="form-label" for="birthplace">Birthplace</label>
<input class="form-control" id="birthplace" type="text" th:field="*{birthplace}">
<div class="text-danger mt-2" th:if="${#fields.hasErrors('birthplace')}"
th:errors="*{birthplace}"></div>
</div>
</div>
</div>
<!-- HOW DO I IMPLEMENT THIS 'ADD USER DETAILS' INPUT TO POPULATE A NEW COMMENT TO THE USER DETAILS? -->
<div class="row">
<div class="col">
<div class="mt-3">
<label class="form-label" for="userDetail">User Details</label>
<input class="form-control" id="userDetail" type="text" th:field="*{????????}">
<div class="text-danger mt-2" th:if="${#fields.hasErrors('userDetail')}"
th:errors="*{??????????}"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-2">
<div class="mt-3">
<button class="btn btn-dark w-100" type="submit">Save</button>
</div>
</div>
<div class="col-2">
<div class="mt-3">
<a class="btn btn-secondary w-100" th:href="#{/all}"
role="button">Cancel</a>
</div>
</div>
</div>
</form>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
When offering a form with a partial edit on an entity I would personally use a seperate bean for the form submit with only the editable fields and a hidden id field, then in the service layer find the entity with that id and edit it's values depending on what was submitted in the form.
No you don't have to manualy query the fields again. The reason those fields are empty is because they'r not correctly bound when posting the form aslong as you post them with your form they wil come back when you return an error.
List fields need a name like *{userDetailsById__[iter.index]__.comments} where iter comes from the each loop th:each="iter, detail: ${user.userDetailsById}". for more details on how to do this you can look at https://www.baeldung.com/thymeleaf-list#list-selection-expression
see 2
P.s. You don't need the SQL in your question nor the overview page html as they do not relate to the question your asking. Just the edit page html, controller and entities (and service if relevant) is enough I'd say

Redirect to page a certain amount of times - Spring Boot

I am trying to make a Quiz Creation application.
In the beginning, I ask the user to enter the quiz title, description, and the number of questions included in the quiz.
Based on the number of questions I want to redirect the user to the 'question and answers' page. I thought of adding another variable named 'count' which would keep the number of times the page is accessed so I can show the next or submit button.
I am not sure how to calculate the number of times the page is redirected and how to redirect the code to a certain page based on the number of questions.
This is the saveQuiz method in the QuizController class:
#PostMapping("/saveQuiz/{cid}")
public String saveQuiz(#PathVariable("cid") Long cid, #Valid #ModelAttribute Quiz quiz,
Model model, #RequestParam("questionNumber") int noOfQuestions) throws ParseException {
Chapter chapter = chapterService.findChapterById(cid);
quiz.setQuizName(quiz.getQuizName());
quiz.setGuidelines(quiz.getGuidelines());
quiz.setChapter(chapter);
quizService.saveQuiz(quiz);
model.addAttribute("quiz", quiz);
model.addAttribute("noOfQuestions", noOfQuestions);
return "redirect:/add_quiz_questions/"+quiz.getId();
}
Then in my QuestionController class I have the below methods
#Controller
public class QuestionController {
#Autowired
QuizService quizService;
#Autowired
QuestionService questionService;
#Autowired
AnswerService answerService;
private static int count = 0;
#GetMapping("/add_quiz_questions/{qid}")
public String addQuestions(#PathVariable("qid") Long qid, Model model) {
count++;
Quiz quiz = quizService.findQuizById(qid);
model.addAttribute("quiz", quiz);
model.addAttribute("count", count);
return "add_quiz_questions";
}
#PostMapping("/saveQuizQuestion/{qid}")
public String saveQuestions(#PathVariable("qid") Long qid, #Valid #ModelAttribute QuizForm quizForm,
Model model, #RequestParam("noOfQuestions") int noOfQuestions) throws ParseException {
Quiz quiz = quizService.findQuizById(qid);
Question question = new Question();
question.setQuestion(quizForm.getQuestion());
//Add answers
Set<Answer> answers = new HashSet<>();
Answer a = new Answer();
a.setAnswer(quizForm.getOption1());
a.setCorrect(1);
answers.add(a);
a.setAnswer(quizForm.getOption2());
a.setCorrect(0);
answers.add(a);
a.setAnswer(quizForm.getOption3());
a.setCorrect(0);
answers.add(a);
answerService.saveAnswers(answers);
question.setAnswers(answers);
questionService.saveQuestion(question);
Chapter chapter = quiz.getChapter();
Course course = chapter.getCourse();
Set<File> files = chapter.getFiles();
int nrFiles = files.size();
model.addAttribute("chapter", chapter);
model.addAttribute("course", course);
model.addAttribute("files", files);
model.addAttribute("numberOfFiles", nrFiles);
model.addAttribute("quiz", quiz);
if(count == noOfQuestions) //check if the page has been redirected as many times as there were questions then redirect to chapter page
return "redirect:/chapter_details/"+chapter.getId();
else
return "redirect:/add_quiz_questions/"+quiz.getId();
}
}
This is the Thymeleaf page:
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<div class="card">
<h5 class="card-header info-color white-text text-center py-4">
<strong>Quiz questions</strong>
</h5>
<div class="card-body px-lg-5">
<!-- Form -->
<form class="text-center" style="color: #757575;" th:action="#{/saveQuizQuestion/{qid}(qid=${quiz.id})}" method="post" th:object="${quizForm}">
<p>Create your quiz</p>
<!-- Question -->
<div class="md-form mt-3">
<input type="text" id="question" class="form-control" name="question">
<label for="question">Question</label>
</div>
<!-- Right answer -->
<div class="md-form">
<input type="text" id="ans1" class="form-control" name="option1">
<label for="ans1">Answer 1</label>
</div>
<!-- Answer 2 -->
<div class="md-form">
<input type="text" id="ans2" class="form-control" name="option2">
<label for="ans2">Answer 2</label>
</div>
<!-- Answer 3 -->
<div class="md-form">
<input type="text" id="ans3" class="form-control" name="option3">
<label for="ans3">Answer 3</label>
</div>
<input type="hidden" th:value="${count}" name="count"/>
<input type="hidden" th:value="${noOfQuestions}" name="noOfQuestions"/>
<button th:if="${noOfQuestions < count}" class="btn btn-outline-info btn-rounded btn-block z-depth-0 my-4 waves-effect" type="submit">Next</button>
<button th:if="${noOfQuestions == count}" class="btn btn-outline-info btn-rounded btn-block z-depth-0 my-4 waves-effect" type="submit">Submit</button>
</form>
<!-- Form -->
</div>
</div>
</html>
I believe the way that I am using the count variable is wrong but it's there just to give an idea. If anyone could help me clarify the question that I have, I'd be grateful.
Thank you in advance.
you can make a count variable in session with #SessionAttribute annotation.
and whenever they submit you will again set count variable to default value.

BindingResult nor plain target object for bean name 'fileName' available as request attribute " while trying to update a raw in my db,

I'm getting this exception while trying to update a row in my database. I performed a lot of research in Google all I find is that I should add #modelAttribute which is already done.
I also found that I need to add bindind result after #ModelAttribute but this also didn't work so I removed it. I'm using JPA for persistence to manipulate my data, spring boot and thymeleaf for my views.
These are my Controllers one for updating and rendering views
#GetMapping("/edit/{id}")
public ModelAndView UpdateList(#PathVariable(name="id") String id) {
ModelAndView mav = new ModelAndView("updateList");
com.pfe.ClientRest.model.Files files = fileServ.get(id);
mav.addObject("Files", files);
return mav ;
}
#PostMapping("/Save")
public String saveRepport(#ModelAttribute("Files") com.pfe.ClientRest.model.Files dbfile) {
fileServ.save(dbfile);
return "/redirect:/ListFile";
}
this is my entity class I have getter setters and constructors
#Table( name="Files")
#Entity
public class Files {
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Id
private String id;
private String FileName;
private String Verif;
public String getId() {
return id;
}
this is my template.
<div class="container">
<h1> Modifier les informations du Rapports</h1>
<form action="#" th:action="#{/Save}" th:objects="${Files}"
method="post" >
<input type="text" th:field=*{id} readonly="readonly"/>
<input type="text" th:field="*{fileName}" placeholder="Nom du Fichier"
class="form-control mb-4
col-4">
<input type="text" th:field="*{verif}" placeholder="Accepted/Rejected"
class="form-control mb-4
col-4">
<button type="submit" class="btn btn-info col-2"> Mettre à jour</button>
</form>
</div>
The field names in the html page and entity class does not match. These are case sensitive. So the correct one should be
private String fileName;
private String verif;

Bean property 'empname' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?

I am trying to perform a simple submit operation from a form. I use spring boot framework with thyme leaf template for my project. Language used is java in eclipse IDE.
All I am looking to do is to take the empname and empid (refer Employee class) from the form and store it in a java object.
When I run the application, the application opens and when i navigate to edit.html, i get this error message in the browser -
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Jun 18 16:14:40 EDT 2018
There was an unexpected error (type=Internal Server Error, status=500).
An error happened during template parsing (template: "class path resource [templates/edit.html]")
I also get this error message on the console -
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'empname' of bean class [com.cardinalcommerce.model.Employee]: Bean property 'empname' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:622) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:612) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:104) ~[spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE]
at org.springframework.validation.AbstractBindingResult.getFieldValue(AbstractBindingResult.java:228) ~[spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE]
at org.springframework.web.servlet.support.BindStatus.(BindStatus.java:129) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
at org.springframework.web.servlet.support.RequestContext.getBindStatus(RequestContext.java:903) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
at org.thymeleaf.spring5.context.webmvc.SpringWebMvcThymeleafRequestContext.getBindStatus(SpringWebMvcThymeleafRequestContext.java:227) ~[thymeleaf-spring5-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.spring5.util.FieldUtils.getBindStatusFromParsedExpression(FieldUtils.java:305) ~[thymeleaf-spring5-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.spring5.util.FieldUtils.getBindStatus(FieldUtils.java:252) ~[thymeleaf-spring5-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.spring5.util.FieldUtils.getBindStatus(FieldUtils.java:226) ~[thymeleaf-spring5-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.spring5.processor.AbstractSpringFieldTagProcessor.doProcess(AbstractSpringFieldTagProcessor.java:174) ~[thymeleaf-spring5-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
... 67 common frames omitted
This is my snippet of the html document where the error occurs.
<form class="form-horizontal" action="#" th:action="#{/employee/edit}" th:object="${employee}" method="POST">
<div class="form-group">
<label class="control-label col-sm-3">File Prefix:</label>
<div class="col-sm-7">
<input type="text" class="form-control" th:field="*{empname}" placeholder="Enter employee name" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">File Prefix:</label>
<div class="col-sm-7">
<input type="text" class="form-control" th:field="*{empid}" placeholder="Enter the employee ID" />
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-7">
<button type="submit" class="btn btn-default" id="blackButton" th:value="Submit">Submit</button>
<button type="reset" class="btn btn-default" id="blackButton" th:value="Reset">Cancel</button>
</div>
</div>
This is my class where with the setters and getters -
public class Employee {
private String empid;
private String empname;
public String getEmployeeId() {
return empid;
}
public void setEmployeeId(String empid) {
this.empid = empid ;
}
public String getEmployeeName() {
return empname;
}
public void setEmployeeName(String empname) {
this.empname = empname;
}
}
This is the controller snippet -
#Controller
#RequestMapping(value="/")
public class GreetingController {
private static final Logger logger = LoggerFactory.getLogger(GreetingController.class);
#Autowired
private SomeRecord someRecord;
#GetMapping("/")
public String greeting() {
return "about";
}
#RequestMapping("/about")
public String about() {
return "about";
}
#GetMapping("/edit")
public ModelAndView edit() {
ModelAndView modelAndView = new ModelAndView("edit");
modelAndView.addObject("employee", new Employee());
return modelAndView;
}
#PostMapping("/edit")
public ModelAndView createRecord(#Valid Employee employee, BindingResult result) {
ModelAndView modelAndView = new ModelAndView();
if (result.hasErrors()) {
logger.info("Validation errors while submitting form.");
modelAndView.setViewName("CreateRecord");
modelAndView.addObject("employee", employee);
return modelAndView;
}
someRecord.addRecord(employee);
modelAndView.addObject("allRecords", someRecord.getAllRecordData());
modelAndView.setViewName("recordsInfo");
logger.info("Form submitted successfully.");
return modelAndView;
}
#GetMapping("/view")
public String view() {
return "view";
}
}
Let me know if anything else is required.
Thanks for your help.
You should use *{employeeName} and *{employeeId} rather than *{empname} and *{empid}. (Matching the getters and setters, rather than your private variables.)

[Spring MVC - Thymeleaf]- Form validation and error messages

I´m trying to create a form and validate its data via #Valid on the command object.
The validation performs well, but an error is ocurring going back to web.
This is what I have:
HTML
<div id="content" layout:fragment="contenido">
<div sec:authorize="isAnonymous()">
<form class="form-horizontal" action="#" th:action="#{register}" th:object="${userForm}" method="post">
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
<fieldset>
<label for="alias" th:text="#{form.register.alias}">Alias</label>
<input id="alias" type="text" th:field="*{alias}" placeholder="Su alias" required="required" autofocus="autofocus"/>
<label for="pass" th:text="#{form.register.password}">Contraseña</label>
<input id="pass" type="password" th:field="*{password}" pattern="[\w\d-_]{5,15}" required="required" th:title="#{form.error.password}"/>
<p th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Error en el dato ingresado</p>
<button type="submit" name="save" class="btn btn-primary" th:text="#{control.register}">Registrarme</button>
</fieldset>
</form>
</div>
</div>
Controller
#RequestMapping(value = "/register", params = {"save"}, method = RequestMethod.POST)
public String register (final ModelMap model, #Valid final UsuarioForm userForm, final BindingResult result) {
if (result.hasErrors()) {
return "register";
} else {
return "redirect:/" + HomeController.PAGE_NAME;
}
}
When Clicking on "submit" the "register" method is called, result.hasErrors() is true so the same page should be displayed, but this error occurs.
Stack
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'userForm' available as request attribute
org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:144)
org.thymeleaf.spring4.util.FieldUtils.getBindStatusFromParsedExpression(FieldUtils.java:396)
org.thymeleaf.spring4.util.FieldUtils.getBindStatus(FieldUtils.java:323)
org.thymeleaf.spring4.util.FieldUtils.getBindStatus(FieldUtils.java:289)
org.thymeleaf.spring4.processor.attr.AbstractSpringFieldAttrProcessor.processAttribute(AbstractSpringFieldAttrProcessor.java:98)
org.thymeleaf.processor.attr.AbstractAttrProcessor.doProcess(AbstractAttrProcessor.java:87)
org.thymeleaf.processor.AbstractProcessor.process(AbstractProcessor.java:212)
org.thymeleaf.dom.Node.applyNextProcessor(Node.java:1017)
org.thymeleaf.dom.Node.processNode(Node.java:972)
If I add "userForm" to the model in the Controller this way:
Controller Modified
#RequestMapping(value = "/register", params = {"save"}, method = RequestMethod.POST)
public String register (final ModelMap model, #Valid final UsuarioForm userForm, final BindingResult result) {
if (result.hasErrors()) {
model.addAttribute("userForm", userForm); //THIS LINE IS ADDED
return "register";
} else {
return "redirect:/" + HomeController.PAGE_NAME;
}
}
The error disappears, BUT... the expression in the HTML ${#fields.hasErrors('password')} results false, so I cant show the error messages to the user.
Any idea of why this behaviour is happening?
Thanks in advance!
PS: I am using Spring MVC 4.1.2 with Thymeleaf 2.1.4
This
public String register(final ModelMap model,
#Valid final UsuarioForm userForm,
final BindingResult result)
should be:
public String register(final ModelMap model,
#ModelAttribute("userForm") #Valid final UsuarioForm userForm,
final BindingResult result)
Notice the #ModelAttribute annotation.