Thymeleaf not displaying Spring form error messages - spring

I'm migrating a Spring jsp application to Thymeleaf but having problems displaying form errors.
I'm using the SpringTemplateEngine and ThymeleafViewResolver and rendering of templates works.
Also form values are populated in form input fields.
The only thing so far not working is displaying form error messages.
My controller looks like:
#RequestMapping(method = RequestMethod.POST)
String save(#Valid CustomerForm form, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute("form", form)
return "app/customers/create"
}
....
I printed the bindingResult to verify it contains an error:
binding result = org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'customerForm' on field 'name': rejected value []; codes [customerForm.name.NotBlank,name.NotBlank,java.lang.String.NotBlank,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [customerForm.name,name]; arguments []; default message [name]]; default message [may not be empty]
When I try to display error using:
<ul>
<li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
<span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
<span th:text="${e.message}">The error message</span>
</li>
</ul>
it does not display any error.
I tried various alternatives as documented on http://www.thymeleaf.org/doc/html/Thymeleaf-Spring3.html#validation-and-error-messages but without success.
Am I missing something?
EDIT
Note I'm trying to display the error within a form set via th:object:
<form id="customer-form" action="#" th:action="#{/app/customers}" th:object="${form}" method="post" class="form-horizontal">
<ul>
<li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
<span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
<span th:text="${e.message}">The error message</span>
</li>
</ul>
</form>

I think you may be having the same issue as I did - please see :
Fields object functions (Spring)
There it is answered by Daniel Fernandez. Basically your form object th:object="${form}" is named "form"
but your controller is looking for "customerForm" (class name) not "form" (the variable name)
can be renamed with #ModelAttribute("data")
copied from that link use:
public String post(#Valid FormData formData, BindingResult result, Model model){
// th:object="${formData}"
}
or
public String post(#Valid #ModelAttribute("data") FormData data, BindingResult result, Model model){
// th:object="${data}"
}

This is how I do it in my forms:
For displaying all errors I put this at the beginning of my form:
<div class="alert alert-danger" th:if="${#fields.hasErrors('*')}">
<p th:each="err : ${#fields.errors('*')}" th:text="${err}"></p>
</div>
and for individual error I add this after the field (of course, changing field in hasErrors to correspond to the field tested):
<p th:if="${#fields.hasErrors('vehicle.licensePlate')}" class="label label-danger" th:errors="*{vehicle.licensePlate}">Incorrect LP</p>
Let me know if this works for you?

Adding to #Blejzer answer:
naming of error message inside messages.properties file must follow below naming convention, so message string will be returned instead of message key:
(Constraint Name).(Object Name).(Property Name)
note: Object Name not Class Name
For example, if you have below User class:
class User{
#NotBlank
String username;
#Length(min=6)
String password;
}
suppose in controller, we named object under validation "user", see #ModelAttribute("user"):
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public String signup(#Valid #ModelAttribute("user") User user, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "signup";
}
//do some processing
return "redirect:/index";
}
in above controller snippet, the object name is "user" not "User"
Now, to show custom messages, you have to name the message as below:
NotBlank.user.username=User Name is blank
Length.user.password=Password length must be at least 6 letters

Related

How to save the return value of a get method from controller to jsp view?

Im having the next question. Ive made a get method in my controller thar receive a product and it returns true if the product is in the wishlist and false if its not.
#RequestMapping(value = "/entryExist", method = RequestMethod.GET)
public boolean checkEntryExists(#RequestParam("productCode") final String productCode){
final UserModel user = userService.getCurrentUser();
final Wishlist2Model wishlist = wishListFecade.getUserWishlist(user);
final ProductModel product = productService.getProductForCode( productCode );
return wishListFecade.checkEntryExists(wishlist, product);
}
Then i have a jsp for the view, and i have to get the boolean value from the controller to show differents buttons depends on the value. I want to know how to call the controller method and save the boolean value into a variable.
<c:if test=" BOOLEAN FROM THE CONTROLLER ">
<button onclick="openModal(modal_alert);">
<div class="col-xs-3 col-sm-4">
<img src="${fn:escapeXml(commonResourcePath)}/images/getWishlist.png">
</div>
</button>
</c:if>

Form with a select (drop-down) doesn't show error code

I have a form that contains a select to list all teachers by id in the system but it is not working properly.
Here is the code part of the form
and the corresponding path controller requests
I'm Using Thymeleaf and Spring Boot, so 'pr' corresponds a name for a variable of a repository of teachers.
<form th:action="#{/professor/updateProfessor/}" method="post" th:object="${professor}">
<div class= "form-group">
<label th:for = "id">Id</label>
<select th:field="*{id}">
<option
th:value = "${id}"
th:text = "${professor.id}">
</option>
</select>
</div>
<input type = "submit" value = "Add Professor">Save</button>
</form>
#GetMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView professorSelecao(){
ModelAndView atualizaProfessor = new ModelAndView("/atualizaProfessor");
atualizaProfessor.addObject("Add Professor");
return atualizaProfessor;
}
#PostMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView selecaoProfessor(){
ModelAndView pagSucesso = new ModelAndView("/pagSucesso");
pagSucesso.addObject(pr.findAll());
return pagSucesso;
}
From your controller, send a list of professors as per following to your view. Here you are associating the list of professors to the "professorList" :
model.addAttribute("professorList", pr.findAll());
And then to access above "professorList" in your thymeleaf do (similar to) this :
<option th:each="professor: ${professorList}" th:value="${professor}"> </option>
Not a full code but i hope you got the idea to get started.
For a full example, take a look here and here.
First of all what is not working? because I see a lot of things that may not work maybe because I don't see the all code or I am guessing some things, let's see
When you enter to your controller using
localhost:8080/professor/selecionaProfessor
are you expecting to use the form you put right? (the next code)
<form th:action="#{/professor/updateProfessor/}" method="post" th:object="${professor}">
<div class= "form-group">
<label th:for = "id">Id</label>
<select th:field="*{id}">
<option
th:value = "${id}"
th:text = "${professor.id}">
</option>
</select>
</div>
<input type = "submit" value = "Add Professor">Save</button>
</form>
because if that's correct you have a problem in your method:
#GetMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView professorSelecao(){
ModelAndView atualizaProfessor = new ModelAndView("/atualizaProfessor");
atualizaProfessor.addObject("Add Professor");
return atualizaProfessor;
}
you will get an error saying:
Neither BindingResult nor plain target object for bean name 'professor' available as request attribute
So you're missing to add the Key professor and a List so change:
atualizaProfessor.addObject("Add Professor");
with something like:
atualizaProfessor.addObject("professor", someListOfProfessorHereFromTheService (List<Professor>));
and it should work if your profesor object have the attributes you have on your form.
Now let's suppose that that worked before and the error wasn't that.
When you enter to your form if you see here:
form th:action="#{/professor/updateProfessor/}"
you're using updateProfessor I don't see that on your controller you have
#PostMapping(value = {"/selecionaProfessor"})
So I think that you should change the url mapping inside the html page or the controller and use the same as error 1, map the object using a key and value and iterate the list into the html as I showed in the 1st error
Hope it helps

how to display validation errors from another form submit

Imagine a form with a single field where the user provides their email. On POST, the controller action method sends a confirmation code to that email and displays a second form in which the user is supposed to enter the confirmation code they received. If the code does not match I then return the original view and I would like to display an error message next to the email ("email was not confirmed").
Sample code below:
1st view (asking the email)
<div>
Please provide the following information to sign-up:
<form:form modelAttribute="newAccountInfo" action="signup-submit.do" method="POST">
<div><form:label path="email">Email:</form:label>
<form:input type="text" path="email"/><form:errors path="email" cssClass="error" element="div"/>
</div>
...
1st view controller method
#RequestMapping(path="/signup-submit", method=RequestMethod.POST)
public String signupSubmit(HttpServletRequest request
, #ModelAttribute("newAccountInfo") #Valid NewAccountInfo newAccountInfo
, BindingResult result
, Model model) {
String confirmCode = generateRandomSecret();
// send confirmCode by email to newAccountInfo.email (omitted)
model.addAttribute("emailConfirmation" , new EmailConfirmation());
request.getSession().setAttribute("newAccountInfo", newAccountInfo);
request.getSession(false).setAttribute("email-code", confirmCode);
return View.SIGNUP_EMAIL_CONFIRMATION.name;
}
2nd view (asking the confirmation code)
<div>
Enter the confirmation code that was sent to your email:
<form:form modelAttribute="emailConfirmation" action="signup-email-confirmation-submit.do" method="POST">
<form:label path="code">Confirmation code:</form:label>
<form:input type="text" path="code"/>
<input type="submit" value="Submit" />
</form:form>
2nd view controller method
#RequestMapping(path="/signup-email-confirmation-submit", method=RequestMethod.POST)
public String signupEmailConfirmationSubmit(
#ModelAttribute("emailConfirmation") EmailConfirmation emailConfirmation
, BindingResult result
, Model model) {
if (emailConfirmation.getCode().equals(request.getSession(false).getAttribute("email-code")))
return View.SIGNUP_SUCCESS.name;
else {
model.addAttribute("newAccountInfo", request.getSession(false).getAttribute("newAccountInfo"));
request.getSession(false).invalidate();
// TODO - what should I do here ?
return View.SIGNUP.name;
}
Assuming the confirmation code was not correctly entered, what should I do in the second view controller method so that when the first view is displayed (for the second time), there is a field validation error message next to the email with description "email was not confirmed" ?
In the line marked with the TODO comment I 've tried the following:
result.rejectValue("email", null, "email was not confirmed");
… but that results in the following exception:
org.springframework.beans.NotReadablePropertyException: Invalid property 'email' of bean class [EmailConfirmation]: Bean property 'email' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
(which makes sense as the email is not a field of EmailConfirmation).
However, the below also fails (silently, without an exception, the 1st view is displayed, I just don't see the validation error message):
result.addError(new FieldError("newAccountInfo", "email", "email could not be confirmed"));
In the end, the only way I could get this to work was to add a custom model property by adding the following (in the TODO line of the second view controller method always):
model.addAttribute("emailConfirmationError", true);
… and then modifying the 1st view as follows:
<div><form:label path="email">Email:</form:label>
<form:input type="text" path="email"/>
<form:errors path="email" cssClass="error" element="div"/>
<c:if test="${not empty emailConfirmationError}">
<span class="error">The email could not be confirmed</span>
</c:if>
</div>
The above succeeds but feels like a hack as I am not using the validation machinery of Spring MVC.
My questions are:
what is the idiomatic way to achieve the above
are there any other flawed mental models or misunderstandings present in the above code?

Spring MVC: How do I preserve model attributes in spring validation errors

I searched around on Stack Overflow, but could not find the solution to my query. I have a controller function that adds multiple model attributes on a GET request
#RequestMapping(method = RequestMethod.GET, value = "/showdeletesearchqueryform")
public String showDeleteSearchQuery(final Model model) {
if (LOG.isDebugEnabled()) {
LOG.debug("Fetching all the search query results.");
}
ImmutableList<ArtQueryResults> results = this.searchQueriesService
.getSearchQueries(APPNAME);
// Adding model attribute # 1
model.addAttribute("searchResults", results);
if (LOG.isDebugEnabled()) {
LOG.debug("\"searchResults\" model attribute has been intialized from "
+ results);
}
ArtDeleteQueryRequest request = new ArtDeleteQueryRequest();
request.setAppName(APPNAME);
if (LOG.isDebugEnabled()) {
LOG.debug("Model attribute initialized = " + request);
}
// Adding model attribute # 2
model.addAttribute("deletedAttributes", request);
return "deletesearchqueries";
}
My JSP
<div class="column-group">
<form:form method="POST" action="${pageContext.request.contextPath}/arttestresults/showdeletesearchqueryform" modelAttribute="deletedAttributes">
<form:errors path="*" cssClass="alert alert-danger column lg-units-5 units-2" element="div"/>
<form:hidden path="appName" id="appNameId" htmlEscape="true"/>
<div class = "units-1 column lg-units-12">
<!-- Hidden Key for app name. -->
<form:select path="idsToBeDeleted" id="IdsToBeDeletedSelectId">
<c:forEach items="${searchResults}" var="searchResult" varStatus="loop">
<form:option label="${searchResult.searchQuery}" value="${searchResult.id}" />
</c:forEach>
</form:select>
</div>
<div class="units-1 column lg-units-12">
<%-- This is a hack that make sure that form is submitted on a click. Not sure why form is not being submitted. --%>
<button class="button" type="submit" onclick="javascript:$('form').submit();">Delete Selected Queries</button>
</div>
</form:form>
My controller POST function
#RequestMapping(method = RequestMethod.POST, value = "/showdeletesearchqueryform")
public String deleteSearchQueries(
Model model,
#ModelAttribute(value = "deletedAttributes") #Valid final ArtDeleteQueryRequest request,
final BindingResult result) {
if (result.hasErrors()) {
LOG.warn("There are " + result.getErrorCount() + " validation errors.");
return "deletesearchqueries";
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("The ids to be deleted are " + request.getIdsToBeDeleted());
}
this.searchQueriesService.deleteSearchQueriesById(
ImmutableList.copyOf(request.getIdsToBeDeleted()));
return "redirect:/arttestresults/showdeletesearchqueryform";
}
}
If there is a validation failure, the model attribute searchResults is not being picked up when I return a view on error condition? Is there a way to preserve the other defined model attributes as well?
Seems that you need flash attributes which were added in spring 3.1. Please take a look at example/explanation:
http://viralpatel.net/blogs/spring-mvc-flash-attribute-example/
The get and the post are different requests. What you get in the post request, is only what comes from the form, so only the "deletedAttributes" model attribute and only the fields that are <input> in the JSP.
You need to put again the searchResults model attribute explicitely like you did in get method.
As suggested by M. Deinum, if one or more attribute(s) will be used by all methods in a controller, you can use a #ModelAttribute annotated method to put it (them) in model automatically.
You can also use SessionAttributes model attributes, that is attributes that are stored in session and not in request. But it is hard to have them properly cleaned from session if user do not post the form but go into another part of the application. You have an example of usage ofSessionAttributes` in Spring's Petclinic example.

how to do binding in Spring annotated request parameter?

i have a controller that is using annotation for request mapping and requestParam.
the controller is working fine. However when submitting a command object with array, spring will crap out saying array index out of bound. i am guessing there is something wrong with binding but don't know how to fix it.
to be more specific, in eclipse i would set debugger at the beginning of the controller, and when submitting the form (by hitting a input submit button) eclipse debugger will not trigger and i will see array index out of bound error in console.
the controller is something like this:
#RequestMapping(value = {"/internal/pcsearch.dex", "/external/pcsearch.dex"},
method = {RequestMethod.POST, RequestMethod.GET})
public ModelAndView executeProductCatalogSearch(
HttpServletRequest request,
#RequestParam(value = "cat" ,required = false) String cat,
#RequestParam(value = "brand" ,required = false) String brand,
#ModelAttribute("command") ProductCatalogCommand cmd
){
[edit]
and the jsp is like:
<form name="pForm"
id="pForm"
action="<c:url value="psearch.dex"><c:param name="cat" value="${cat}"/></c:url>"
method="POST"
style="display:inline;">
...
...
<c:forEach var="model" items="${models}" varStatus="modelLinkStatus">
<script>
var modelImg<c:out value="${modelLinkStatus.index}"/>Src = '<c:out value="${model.altModelImage}"/>';
</script>
<spring:bind path="command.models[${modelLinkStatus.index}].modelSkusDisplayed">
<input type="hidden" name="<c:out value="${status.expression}"/>" id="<c:out value="${status.expression}"/>" value="<c:out value="${status.value}"/>"/>
</spring:bind>
<spring:bind path="command.updateCartButton">
<input type="submit" value="<spring:message code="orderEntryMessages.ecatalog.button.addToCart" text="Add to Cart" htmlEscape="yes" />" name="<c:out value="${status.expression}"/>" id="<c:out value="${status.expression}"/>" class="sub_buttons"/>
</spring:bind>
...
and the command object declare the model array as:
private List<ModelLink> models = new ArrayList<ModelLink>();
where modelLink is a custom ds.
the first foreach tag handle the the model command object and the 2nd part is the submit button i clicked on.
i think you should use AutoPopulatingList as models to bind list to view and controller. for example please refer link. This might resolve your problem of index.

Resources