Fields object functions (Spring)

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Fields object functions (Spring)

meyertee
Hi,

I'm having a hard time getting the error messages function to work.

I'm validating the form-backing-bean with validator-annotations. The form-controller's BindingResult.hasErrors() returns true and the error messages are also set. But the #fields.hasErrors('*') function in the template always returns false and the messages are not set.. Is there anything I need to setup for this to work? Is there a problem when using annotations without custom validator?

I'm fairly new to both Spring and Thymeleaf, so please excuse me if the problem is obvious... Here's my controller method (which gets executed correctly):
@RequestMapping(value="/form", method=RequestMethod.POST)
public String post(@Valid FormData data, BindingResult result, Model model){
	model.addAttribute("data", data);
	System.out.println("Has errors="+result.hasErrors()); // Output: Has errors=true
	for (FieldError err:result.getFieldErrors()){
		System.out.println(err.getDefaultMessage()); // Output: must be greater than or equal to 10
	}
	if (result.hasErrors()){
		return "form";
	}
	return "redirect:/home";
}
This is my form backing bean with annotations as demoed here
import javax.validation.constraints.Min;
public class FormData {
	@Min(10)
	private int something;

	public FormData() {}

	public int getSomething() {
		return something;
	}

	public void setSomething(int something) {
		this.something = something;
	}
}
And finally, this is what I have in my template file (form.html). The errors message never shows:
<form action="#" th:action="@{/form}" th:object="${data}" method="post">
    <div th:if="${#fields.hasErrors('*')}">errors</div>
    <input type="text" th:field="*{something}"/>
    <button type="submit">Submit</button>
</form>
Any help would be appreciated, I'm a bit stuck right now...
Best, Thomas.
Reply | Threaded
Open this post in threaded view
|

Re: Fields object functions (Spring)

danielfernandez
Administrator
Hi,

Yours is a Spring MVC problem, not a Thymeleaf one ;-) And I must admit, a difficult one to find...

The problem is that, when you declare a controller like:

    public String post(@Valid FormData data, BindingResult result, Model model){

Spring MVC automatically computes the name of the form-backing bean model variable from its class name --with the first letter in lower case--, not its argument name (method argument names are lost in runtime, in Java). This is, it will use "formData", and not "data". And it will bind the BindingResult object containing the validation errors --which #fields.hasErrors(...) will query-- to that "formData" name, not "data".

The problem is that, after you execute the forward back to the .html in:

        if (result.hasErrors()){
            return "form";
        }

Thymeleaf will execute a #fields.hasErrors(...) inside a <form> tag which th:object has the value "${data}":

<form action="#" th:action="@{/form}" th:object="${data}" method="post">
    <div th:if="${#fields.hasErrors('*')}">errors</div>

And it will find that there are no errors bound to the "data" variable name. Because they are bound to the "formData" variable name.

That's why your code doesn't work :-). Just modify "data" by "formData", and remove this line from your controller, which you do not need:

        model.addAttribute("data", data);


Regards,
Daniel.
Reply | Threaded
Open this post in threaded view
|

Re: Fields object functions (Spring)

aurelije
In reply to this post by meyertee
Nice question and nice answer :)

I just wonder what would happen if we put @ModelAttribute("data") in front of FormData data parameter?
Reply | Threaded
Open this post in threaded view
|

Re: Fields object functions (Spring)

meyertee
In reply to this post by danielfernandez
Hi,

thank you so much, not only for the good catch but also for your explanation!
It works and I have a better understanding of what's going on under the hood now.. I was wondering why I had to add the "data" attribute to the model...

aurelije - adding the @ModelAttribute("data") annotation also works, so thank you too :)

To recap:

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}"
}

Best wishes, Thomas.
Reply | Threaded
Open this post in threaded view
|

Re: Fields object functions (Spring)

ankuj
In reply to this post by meyertee
Thank you very much for this answer! I searched high and low only to tumble upon this. Not only does it work like a charm, I understand the functioning a lot better! Thanks again!