how to intercept #modelattribute binding - spring

Everyone.
I am using spring mvc framework, and spring form tag. I found unexpected thing when using form dynamically. For example,
public class Person {
public List<Car> myCars = new ArrayList<Car>();
// getter and setter
}
below code is html form
<form:form modelAttribute="car" ...>
<input type="hidden" name="myCars[0].id">
<input type="text" name="myCars[0].name">
<input type="hidden" name="myCars[1].id">
<input type="text" name="myCars[1].name">
<input type="hidden" name="myCars[2].id">
<input type="text" name="myCars[2].name">
</form:form>
and next code is a spring form controller
#Controller
#SessionAttribute({"car"})
public class CarController {
...
#RequestMapping(".....")
public String form(#ModelAttribute Car car, BindingResult result, ...) {
if (result.hasErrors()) {
....
return viewName;
}
....
return "redirect:/" + someWhere;
}
}
1) I entered data into html form.
2) I can confirm that there are 3 Car objects in car.getMyCars()
3) There are some errors as binding, so it's redirected to viewName
4) I changed html form using jQuery like this
<form:form modelAttribute="car" ...>
<input type="hidden" name="myCars[0].id">
<input type="text" name="myCars[0].name">
<input type="hidden" name="myCars[1].id">
<input type="text" name="myCars[1].name">
</form:form>
and, submit. The result of this test is that #ModelAttribue Car car still remains 3rd element in List myCars. I expected to remain 2 Car elements, but it wasn't. I think it remained in session. And it was overwritten new form data to #ModelAttribute Car car object, but last 3rd element wasn't. My test shows that if form elements increase dynamically, binding object using #ModelAttribute have them. But though decreased dynamically, binding object still have them. I hope that Car car object have accurate number of form inputs. What should I do?
Thanks in advance.

Related

Thymeleaf: Partially set values to model object from outside the form tags

I have an model object that I send to front end. I populate that object inside the form. What I want to know is that if I can partially populate that object from a different event before user submits his/her form?
Example:
Entity:
public class Participant {
public String username;
public boolean taskCompleted;
}
Thymeleaf Form:
<form th:object="${participant}" th:action="#{/join}" method="post">
<input type="text" th:field="*{username}" >
<button type="submit">Join!</button>
</form>
Before submitting the form, I give users a task, like clicking a button on a different part of the page. If they do it, I want to do something like taskCompleted = true of the same participant object. Is that possible?
Use a hidden input in your form:
<form th:object="${participant}" method="post">
<input type="text" th:field="*{username}" >
<input type="hidden" th:field="*{taskCompleted}" />
<button type="submit">Join!</button>
</form>
When the user clicks a button, use JavaScript to set the value of the hidden input to true.
<!-- This button flips the value of taskCompleted to true -->
<button onclick="document.getElementById('taskCompleted').value = 'true';">Do the task first!</button>

Multiple similar forms in same page

In Spring Boot with Thymeleaf, I have a page that should handle two forms corresponding to a same model Person. Each form can be submitted separately, so I use two methods in the controller:
#PostMapping(value="/", params={"submitFather"})
public String submitFather(
#ModelAttribute("fatherForm") Person fatherForm,
#ModelAttribute("motherForm") Person motherForm,
Model model) {
// ... Process fatherForm
// Return the updated page
model.addAttribute("fatherForm", fatherForm);
model.addAttribute("motherForm", motherForm);
return "index.html";
}
#PostMapping(value="/", params={"submitMother"})
public String submitMother(
#ModelAttribute("fatherForm") Person fatherForm,
#ModelAttribute("motherForm") Person motherForm,
Model model) {
// ... Process motherForm
// Return the updated page
model.addAttribute("fatherForm", fatherForm);
model.addAttribute("motherForm", motherForm);
return "index.html";
}
In my template:
<form id="fatherForm" th:object="${fatherForm}" method="post">
<label for="firstName">First name</label>
<input type="text" th:field="*{firstName}"><br>
<!-- ... other Person fields -->
<input type="submit" name="submitFather" value="Order">
</form>
<form id="motherForm" th:object="${motherForm}" method="post">
<label for="firstName">First name</label>
<input type="text" th:field="*{firstName}"><br>
<!-- ... other Person fields -->
<input type="submit" name="submitMother" value="Order">
</form>
The issue I have is that this leads to HTML IDs clashes. There will be for example two <input> with id firstName. This causes invalid HTML and interference between the fields from the two forms when submitted.
One solution would be to have two models, Father and Mother with fields named such as fatherFirstName to ensure no duplicate HTML IDs. But this leads to substantial code duplication, and long and different field names (while only the HTML IDs need to be different).
How can this be solved? Is there a way to tell Spring to prefix ids with some string while ensuring I can still use the same Person model for the two forms?

How to handle forms mapped to more than one entity using Thymeleaf + Hibernate + Spring Boot?

I have a form in Thymeleaf that I want to link to two different entity to be persisted to the database using Hibernate
I have the following form using Thymeleaf:
<form th:action="#{/app/handleForm}" th:object="${entity1}"
method="post">
<input type="text" th:field="*{field1}" />
<input type="text" th:field="*{field2}" />
<input type="text" th:field="*{field3}" />
</form>
Let's supposea the first two fields are bound to entity1 and the third field to be bound to entity2 (not entity1) how should I do this?
Also, in the controller method, I have two DAO implementation for persisting them:
#PostMapping("app/handleForm")
public String RHTraiterDemande(Model m, Entity1 entity1, Entity2
entity2) {
entity1Service.add(entity1);
entity2Service.add(entity2);
return "showResults";
}
How to do this?
You could create a custom object with the required information and mapped it using th:object.
New Class
public class MyClass {
private Entity1 entity1;
private Entity2 entity2;
// Getters and setters.
}
Form
<form th:action="#{/app/handleForm}" th:object="${myClass}"
method="post">
<input type="text" th:field="*{entity1.field1}"/>
<input type="text" th:field="*{entity1.field2}"/>
<input type="text" th:field="*{entity2.field3}"/>
</form>
Controller
#PostMapping("app/handleForm")
public String RHTraiterDemande(Model m, MyClass myClass) {
entity1Service.add(myClass.entity1);
entity2Service.add(myClass.entity2);
return "showResults";
}

how to pass parameter from HTML with Thymeleaf to Spring Controller?

I have a controller in Spring Boot that needs to recieve a parameter and an object by POST. The parameter is NOT an object or part of if.
Here is the Thymeleaf form without the parameter. It works fine in a stand-alone page, but the same code does not work when I use it in a page that shows an article:
<form th:action="#{/addcomment}" th:object="${comment}" method="post">
<input type="text" th:field="*{commenttext}" />
<button type="submit">send</button>
</form>
because when I add the
<input type="hidden" th:field="*{id}" th:with="id=1" value="${id}"/>
or
<input type="hidden" th:field="*{id}"/>
or
<input type="hidden" th:field="*{id}" value="1"/>
my Controller prints 0 in the console, when the value should be 1... (and even if I change it to value="true".... this comes from the Chrome console:
<input type="hidden" class="sr-only" value="0" id="id" name="id" />
no matter what I use
#RequestMapping(value = "/addcomment", method = RequestMethod.POST)
ModelAndView addStatus(ModelAndView modelAndView, #Valid Comment comment, #RequestParam("id") Long id, BindingResult result) {
Comment commentform = new Comment();
Announcement announcement = announcementService.readAnnouncement(id);
String sanatizedcommenttext = htmlPolicy.sanitize(comment.getCommenttext());
commentform.setCommenttext(sanatizedcommenttext);
commentform.setDate(new Date());
commentform.setAnnoucements(announcement);
modelAndView.setViewName("addcomment");
if (!result.hasErrors()) {
commentService.createComment(commentform);
modelAndView.getModel().put("comment2th", new Comment());
modelAndView.setViewName("redirect:/addcomment");
}
return modelAndView;
}
By the way, the parameter is in the url (but I would like to know how to send it to the controller independently of where it is located).
thanks for your help!
You only have to add this changes
<input type="hidden" name="paramName" value="1"/>
In your Controller
#RequestMapping(value = "/addcomment", method = RequestMethod.POST)
ModelAndView addStatus(ModelAndView modelAndView, #Valid Comment comment, #RequestParam("paramName") Long id, BindingResult result) {
The problem was, that you were using the thymeleaf tags for input fields, then Spring thinks the attribute id its inside the object Comment, but it's not your case, so you have to use normal input tag.

How to pass only string in thymeleaf form?

I have a little problem. When I have an object with some fields, it's easy to pass these fields through form:
Controller:
#RequestMapping("/")
public String hello(Model model) {
model.addAttribute("test", Test);
return "index";
}
html:
<form th:action="#{/process}" method="post" th:object="${test}">
<input type="text" th:field="*{value}"/>
<input type="submit" />
</form>
But what if I don't want to have an object and pass only string? Something like that:
Controller:
#RequestMapping("/")
public String hello(Model model) {
model.addAttribute("test", "test string");
return "index";
}
html:
<form th:action="#{/process}" method="post">
<input type="text" th:field="${test}"/>
<input type="submit" />
</form>
doesn't work.
Thanks for help!
For next question in comments:
index.html:
<form th:action="#{/process}" method="post">
<textarea th:text="${sourceText}"/>
<input type="submit" />
ggg.html:
<textarea th:text="${sourceText}"/>
controller:
#RequestMapping("/")
public String hello(Model model) {
model.addAttribute("sourceText", "asdas");
return "index";
}
#RequestMapping("/process")
public String process(Model model, #ModelAttribute(value = "sourceText") String sourceText) {
return "ggg";
}
th:field is used only if you declare object like th:object.
<form th:action="#{/process}" method="post">
<input type="text" th:value="${sourceText}" name="sourceText"/>
<input type="submit" />
</form>
Spring matches values by "name" attribute. Just catch it with #RequestParam in controller like
#RequestMapping("/process")
public String process(Model model, #RequestParam String sourceText) {
return "ggg";
}
According how Spring MVC works you can't use a string as an object in a form, you need an object to encapsulate that string, because the form structure is Object and then any field linked to it.
In your case, I would create a view form object for those common situations, something like formView with the String attribute text. And you could use the same object in similar situations.
Other option if you don't want to create this extra object, you could send the data by AJAX, and build the data array to send to the controller in javascript.
Personally I would go for the first option, is more reusable.
Hope this help you

Resources