Error handling with Spring CommandName - spring

when using Spring framework and binding a form on a commandName object to add let's say a person with the following fields.
<form method="POST" action="addPerson.htm" commandName="person">
<input id="firstname" name="firstname"value="${person.firstname}"/>
<br>
<input id="name" name="name" value="${person.name}"/>
<br>
<input id="age" name="age" value="${person.age}"/>
</form>
On the server I've got the following code
#RequestMapping(value="/addPerson", method={RequestMethod.POST})
public String addPerson(#ModelAttribute(value="person") Person p){
service.addPerson(p);
return "redirect:/overview.htm";
}
How do I do error handling with this, so let's say for example the age must be a positive number and firstname can be left empty.

You need to create a new Validator class, implementing Validator interface.
Your validator can be something like this:
public class PersonValidator implements Validator {
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
Person person=(Person)target;
if ( person.getAge() < 0 ) {
errors.rejectValue("age", "age_positive");
// Or you can use this approach as well to parametrize the error message:
//errors.rejectValue("age", "age_positive", ArrayParametersIfNeeded, "DefaultMessage");
}
}
}
You also need to have a error.properties file to hold the errors messages allowing i18n.
You can have all needed information about validators here: Validators
When you have it, you need to create the initBinder method in the controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.addValidators(yourValidator);
}
Then, in the controller method, you can pass this additional parameter: BindingResult result and there you will have the information of the errors if you need it.
Also, you need to add this bit to the form:
<form:errors path="age"/></c:set>
Below your <input id="age" name="age" value="${person.age}"/>
I suggest you to change your <input> elements for the <form:input> tag.

Related

Request method 'GET' not supported Spring Booth with Thymeleaf

I am building a very simple crud app using Spring boot, Jpa and Thymeleaf, but I am stuck at a "Request method 'GET' not supported" problem. I get this error whenever I want to access the /add page through which I can add a new student. The snippets associated with this error are as below:
Thymeleaf form:
<h1>Form</h1>
<form action="#" th:action="#{/add}" th:object="${addStudent}"
method="post">
<p>Full name: <input type="text" th:field="*{fname}" /></p>
<p>Major: <input type="text" th:field="*{major}" /></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
Controller addNewStudentMethod
#PostMapping("/add")
public String addNewStudent( #ModelAttribute StudentEntity studentEntity, Model model) {
model.addAttribute("addStudent",studentRepository.save(studentEntity) );
return "/allstudents";
}
The error I get:
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'GET' not supported
Thanks,
In your controller you only have a method which has mapped to POST request "/add". You have to have a GET request mapped to a different method OR change the #PostMapping("/add") to #RequestMapping("/add").
Please note:
#PostMapping is for mapping POST request only.
#GetMapping is for mapping GET request only.
#RequestMapping maps all request types
You have some issues with how you have it set up. What you may want is:
#GetMapping("/add")
public String addNewStudent(Model model) {
model.addAttribute("studentEntity", new StudentEntity()); //create a new bean so that your form can bind the input fields to it
return "add"; //let's say add.html this is the name of your form
}
#PostMapping("/add")
public String addNewStudent( #ModelAttribute StudentEntity studentEntity, Model model) {
//call any service methods to do any processing here
studentRepository.save(studentEntity);
return "redirect:/allstudents"; //this would be your confirmation page
}
Your add.html form would have something like:
<form th:object="${studentEntity}" th:action="#{/add}" method="post" action="allstudents.html">
<!-- input fields here --->
</form>
Note that the th:object is what you added to the model in the #GetMapping method.
change your Controller methods's #PostMapping("/any-url") to either #GetMapping("/any-url") or #RequestMapping("/any-url")
In simple words, change your above controller's method to
#RequestMapping("/add")
public String addNewStudent( #ModelAttribute StudentEntity studentEntity, Model model) {
model.addAttribute("addStudent",studentRepository.save(studentEntity) );
return "/allstudents";
}

Persist radio checked value in Thymeleaf Spring

When searching, if we select a given field to search within and submit, our choice is forgotten. How could someone modify the view template to keep the previous search field selected when displaying results?
I have read many other SO questions and the thymeleaf documentation here but have not found a suitable answer yet.
One can hard code a string (like employer) with the following:
search.html snippet
<span th:each="column : ${columns}">
<input
type="radio"
name="searchType"
th:id="${column.key}"
th:value="${column.key}"
th:checked="${column.key == 'employer'}"/>
<label th:for="${column.key}" th:text="${column.value}"></label>
</span>
SearchController.html snippet
#RequestMapping(value = "")
public String search(Model model) {
model.addAttribute("columns", columnChoices);
return "search";
}
How can I persist the user selected radio value upon POST in Thymeleaf Spring?
(and default to the first, value on the GET)
http://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#creating-a-form
Command object.
public class Search {
// default "employer"
private String type = "employer";
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Controller
#GetMapping
public String search(Model model) {
model.addAttribute("columns", columnChoices);
model.addAttribute("search", new Search());
return "search";
}
#PostMapping
public String search(#ModelAttribute Search search) {
// Search's attributes match form selections.
}
With search as your command object, a radio buttons should look like this:
<form th:object="${search}">
<th:block th:each="column: ${columns}">
<input type="radio" th:value="${column.key}" th:id="${column.key}" th:field="*{type}" />
<label th:for="${column.key}" th:text="${column.value}" />
</th:block>
</form>

Desing of form in spring

I am using spring framework. This question mainly concerns about design and implementation.
In my project, I have to use many forms and most of them are different. What is the recommended way of implementing forms in spring. Using Model?
I want to use ajax for forms submissions. The forms in the project are really huge having 8-15 fields. Is it a good way to use ajax for such huge forms? If yes, how can I do it? Can I use model attribute?
A typical way to do this is to use form backing objects. For example, with a form like
<form>
<input type="text" name="username">
<input type="password" name="password">
<input type="text" name="email">
<input type="submit" name="submit">
</form>
You would create a DTO class
public class UserForm {
private String username;
private String password;
private String email;
public UserForm() {}
// getters and setters
}
Then your #Controller handler method can map as
#RequestMapping(method = RequestMethod.POST)
public String handleFormSubmit(#ModelAttribute UserForm userForm /*, more */) {
/* logic and return */
}
And Spring will be able to bind the value from the name attribute of input elements to the fields of the class.
AJAX doesn't care about the size of your forms.

Redirect on Spring Tiles Causing Parameters to be Appended to the URL

I am using Spring 3 and Tiles 3. Below is just a simplified example I made. I have a test controller where I list all the SimpleEntity objects. And there is an input field on the JSP to add a new entity via a POST. Here is the controller.
#Controller
#RequestMapping(value="/admin/test")
public class TestAdminController {
private String TEST_PAGE = "admin/test";
#Autowired
private SimpleEntityRepository simpleEntityRepository;
#ModelAttribute("pageName")
public String pageName() {
return "Test Administration Page";
}
#ModelAttribute("simpleEntities")
public List<SimpleEntity> simpleEntities() {
return simpleEntityRepository.getAll();
}
#RequestMapping(method=RequestMethod.GET)
public String loadPage() {
return TEST_PAGE;
}
#RequestMapping(method=RequestMethod.POST)
public String addEntity(#RequestParam String name) {
SimpleEntity simpleEntity = new SimpleEntity();
simpleEntity.setName(name);
simpleEntityRepository.save(simpleEntity);
return "redirect:/" + TEST_PAGE;
}
}
Everything works fine. However, when I submit the form, the URL adds the pageName parameter, so it goes from /admin/test to /admin/test?pageName=Test+Administration+Page. Is there anyway to prevent this from happening when the page reloads?
UPDATE
Here is the JSP form.
<form:form action="/admin/test" method="POST">
<input type="text" name="name" />
<input type="submit" name="Save" />
</form:form>

Spring framework bind form array property

// my form
public class myForm {
private double[] myField;
public double[] getMyField(){
return myField;
}
public void setMyField(double[] myField){
this.myField = myField;
}
}
// my jsp
...
...
<c:set var="i" value="0"/>
<c:forEach items="${myList}" var="data">
<form:input path="myField[${$i}]"/>
<c:set var="i">${i + 1}</c:set>
</c:forEach>
...
...
After spring render jsp generate this code ;
<input type="text" value="0.0" name="myField0" id="myField0"/>
<input type="text" value="0.0" name="myField1" id="myField1"/>
<input type="text" value="0.0" name="myField2" id="myField2"/>
...
...
Spring cant bind my form on controller , because form names not valid (myField0, myField1..) . If i change names with firebug (as myField[0], myField[1] etc.) initBinder works and i catch my form data on controller. How can i solve this?
Thanks.
Use a Collection in your form instead of an array :
public class myForm {
private Collection<Double> myField;
public Collection<Double> getMyField(){
return myField;
}
public void setMyField(Collection<Double> myField){
this.myField = myField;
}
}

Resources