handle one to many relationship in thymeleaf using spring mvc - spring

I'm having One entity as Vendor and Another as Address and the relationship between both of them is One To Many form Vendor to Address.
Note : I am using JPA
My Vendor Entity
public class Vendor {
private Integer id;
private String name;
private List<Address> address;
// getter and setters
}
Address class:
public class Address {
private Integer id;
private String addressline1;
private String addressline2;
//getter and setters
}
Now I am using Thymeleaf , I have a scenario where I need to add the address dynamically to a form for the particular vendor.
How do I do Object binding for the Address object in Vendor using Thymeleaf in spring mvc?

Comment if i didn't understand your question correct, it's a bit unclear to me...
In order to access the address(s) of a vendor, you provide a vendor within your controller (something like model.addAttribute("vendor", currentVendor);) and call vendor.address in your html file. Please note that this will give you a list so you need to iterate to show all address:
<tr th:each="address : ${vendor.address}">
<td th:text="${address.id}">1</td>
<td th:text="${address.addressline1}"></td>
<td th:text="${address.addressline2}"></td>
</tr>

Uhhh, that's tricky because binding to form doesn't work in a dynamic way. That means you can't do something like #Viergelenker suggests AND bind each address-object to his own form.
You can add a single address object to the model, e.g.
model.addAttribute("address", addressObject); // Snippet for Model-object
modelAndView.addObject("address", addressObject); // Snippet for ModelAndView object
and then define a form in yout template like:
<form .... method=".." th:object="${address}">
<input type="hidden" th:field="*{id}" >
<input type="text" th:field="*{addressline1}" >
<input type="text" th:field="*{addressline2}" >
</form>
Unfortunately it is not possible to add a array or list to the model and bind each object in that collection to his own form:
/* The following code doesn't work */
<th:block th:each="address : ${addresses}">
<form .... method=".." th:object="${address}">
<input type="text" th:field="*{addressline1}" >
...
</form>
</th:block>
or
/* The following code doesn't work */
<th:block th:each="address, stat : ${addresses}">
<form .... method=".." th:object="${addresses[__stat.index__]}">
<input type="text" th:field="*{addressline1}" >
...
</form>
</th:block>
What you can do is not to use form binding and just send some name-value pairs from forms without the binding (just use the name and the th:value attributes and not the th:field attribute in your forms) to the controller, get them there from the HttpServletRequest object and create/update/delete address-objects ... or bind the whole Vendor object to a form (note the use of stat.index):
<form th:object="${vendor}">
<input type="hidden" th:field="*{id}">
<input type="hidden" th:field="*{name}"> // feel free to make that field editable
<th:block th:each="addr, stat : *{address}">
<input type="hidden" th:field="*{address[__${stat.index}__].id}">
<input type="text" th:field="*{address[__${stat.index}__].addressline1}">
<input type="text" th:field="*{address[__${stat.index}__].addressline2}">
</th:block>
</form>

Related

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?

Thymeleaf th:field doesn't bind the value for input text

I want to send an object to the view for presentation and send it back to controller using springboot and Thymeleaf, however, I encounter a weird problem with Thymeleaf's th:value.
This is my controller:
#GetMapping("/food/buy/{fid}")
public String buyFood(HttpServletRequest request, #PathVariable("fid") Long fid, Model model) {
Food food = consumerService.getFood(fid);
System.out.println("foodid = " + food.getId());
model.addAttribute("food", food);
model.addAttribute("order", new OrderVO());
return "user/direct/f_order";
}
and my view:
<form th:action="#{/user/buy/direct/food}" method="post" th:object="${order}">
<table border="1px">
<tr th:hidden="true">
<td><input type="text" th:value="${food.id}" th:field="*{fid}" th:readonly="true"></td>
</tr>
</table>
</form>
and the VO class:
public class OrderVO {
private Long fid, address;
private Integer amount;
#DateTimeFormat(pattern = "HH:mm")
private Date deliverTime;
}
the problem is, the input field's value is null, but I'm sure that the food's id is not null (I print it in the controller)
I remove the th:field block, and the food.id can be properly presented. If I add the th:field block back, the problem reoccur.
So there may be something wrong with th:field, but I can't figure out. Can somebody point out my mistake?
===========================UPDATE============================
Some friends kindly points out that th:field may overwrite th:value, but I also use them in other views and it works fine:
<tr>
<td>UserName</td>
<td><input type="text" th:value="*{userName}" th:field="*{userName}"></td>
</tr>
The problem is getting incresing weird I think :(
Replace *{fid} with fid
My team had this same issue and it worked
In tabualr form try using th:name instead of th:field to overcome th binding issue
th:name="|order.fid|"
and stick to java naming convention.
Supposing you have to collect a comment to a page. You must transmit to the controller, besides the comment, the name of the page. Ofcourse, the user don't have to re-enter the name of this page. This information must be passed to controller, but th:field only map the values entered by the user, not the values generated by default.
But you can transmit the name of this page to controller as parameter in URL.
In html, you have something like that:
<form th:action="#{/saveComment(lastPage=${lastPage})}" th:object="${comments}" method="post" enctype="multipart/form-data">
<div class="row">
.................................................................................
<h2>Enter your comment</h2>
<textarea th:field="${comments.comment}" rows="10" cols="100" name="comment" id="comment"></textarea>
<label for="comment">Your comment here</label><br />
<input type="submit" name ="submit" value="Submit" />
</div>
</form>
In controller, you put stuff like this:
#PostMapping("/saveComment")
public String saveComment(Comments comments, String lastPage) {
comments.setPage_commented(lastPage);
commentsRepository.save(comments);
return "redirect:/";
}
It works fine to me.

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

Controller Not receiving value from span in HTML using Spring boot and Thymeleaf

I have the following content in my HTML which is using Thymeleaf
<form action="#" th:action="#{/shutDown}" th:object="${ddata}" method="post">
<span>Domain</span>
<span th:text="${domain}" th:field="*{domain}">domain</span>
<input type="Submit" value="close" />
</form>
And I have the following in my Controller which is using Sprint Boot
#RequestMapping(value = "/shutDown", method = RequestMethod.POST)
public ModelAndView shutDownPage(ModelAndView modelAndView, Authentication authentication,
#ModelAttribute("ddata") DInputBean dInputBean) {
String domain = dInputBean.getdomain();
return modelAndView;
}
I'm hoping I'd get value of domain from the HTML in the Controller but it's always null. DInputBean has getters and setters for "domain" field.
The th:field attribute can be used on <input>, <select>, or, <textarea>.
A solution you could possibly replacing you second <span> with a hidden input element.
<form action="#" th:action="#{/shutDown}" th:object="${ddata}" method="post">
<span>Domain</span>
<input type="hidden" th:field="*{domain}" th:value="${domain}" />
<input type="Submit" value="close" />
</form>
If you wanted to keep the second div, just place the <input type="hidden"> inside the second <span> and remove the th:field attribute from the second <span>.
Edit:
If you wanted to add the value of domain in a span.
<form action="#" th:action="#{/shutDown}" th:object="${ddata}" method="post">
<span>Domain</span>
<span th:text="${domain}">domain<span>
<input type="hidden" th:field="*{domain}" th:value="${domain}" />
<input type="Submit" value="close" />
</form>
http://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#inputs
An option is to use a read-only input field:
<input type="text" th:field="*{domain}" th:value="${domain}" readonly="readonly"/>
This both displays the value and sends it on submit.
The key is to add the value of the domain variable to the form:
#GetMapping("/shutDownPage")
public String shutDownPage(Model model) {
model.addAttribute("ddata" new Ddata()); //or however you create your bean
String username = ... //however you get your username
String domain = myRepositoryService.findDomainByUsername(username);
model.addAttribute("domain", domain);
return "shutDownPage";
}
Include an HTML page in the action so that when you open the HTML page in a browser without a server/container, the button will still appear to work:
<form action="confirmationPage.html" th:action="#{/shutDown}" th:object="${ddata}" method="post">
<!-- You can benefit from using a conditional expression -->
<span th:text="${domain != null ? domain : 'No domain supplied'}">[domain]</span>
<input type="hidden" th:field="*{domain}" th:value="${domain}"/>
<input type="Submit" value="close"/>
</form>
And your post method:
#PostMapping("/shutDown") //use shorthand
public String shutDownPagePost(#ModelAttribute("ddata") DInputBean dInputBean {
String domain = dInputBean.getDomain();
//do whatever with it
return "confirmationPage";
}

how to intercept #modelattribute binding

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.

Resources