Thymeleaf: How to fix updating #ManyToMany table - spring-boot

I have created a form using thymeleaf for updating books information.
Authors and Book have a #ManyToMany relationship. The problem is that when I save my changes, the #ManytoMany table (Book_Authors) row is deleted.
I am not trying to change the values of authors for the edited book, I simply want to
preserve the old ones.
I have tried saving each author ID in a 'input type = "hidden" ' since
this worked for saving values for #OneToMany relationship like
'PUBLISHER'
This works (Publisher old value is saved in database):
<div class="form-group">
<label class="col-lg-3 control-label">Publisher:</label>
<div class="col-md-8">
<input type="hidden" th:field="*{publisher.id}" />
<input class="form-control" type="text" value="" th:field="
{publisher.name}">
</div>
</div>
This doesn't work:
<div th:each = "author : ${authors}">
<input type="hidden" th:field="*{author.id}" />
</div>

<tr th:each="vehicle, itemStat : ${form.vehicles}">
<td>
<input hidden th:name="|vehicles[${itemStat.index}].id|" th:value="${vehicle.getId()}"/>
</td>
<td>
<input th:name="|vehicles[${itemStat.index}].brand|" th:value="${vehicle.getBrand()}"/>
</td>
<td>
<input th:name="|vehicles[${itemStat.index}].price|" th:value="${vehicle.getPrice()}"/>
</td>
</tr>
further reading : -
https://www.baeldung.com/thymeleaf-list
http://forum.thymeleaf.org/I-have-problem-in-binding-the-list-of-objects-contained-inside-a-object-on-the-form-using-thymeleaf-td3525038.html
and also u have to change cascadeType.ALL
if use #OneToMany use orphanRemoval=true

Related

Spring MVC editable table (Thymeleaf)

I have 2 entities - Invoice and InvoiceProduct. Invoice class has a field List of InvoiceProduct because Invoice has #OneToMany mapping on InvoiceProduct.
I want to send the List of InvoiceProduct to Thymeleaf with with editable fields with a Submit button. When I click on Submit button, the new edited List should be returned to the controller. However, I am able to send the list to the view, but when I send the edited list to controller, it is being passed as 'null'.
update-invoice.html:
<form action="#" th:action="#{/api/invoice/saveInvoice}" th:object="${invoice}" method="POST">
<table class="table table-bordered table-striped">
<thead class="thead-dark">
<tr>
<th>PID</th>
<th>Quantity</th>
<th>Unit Price</th>
</tr>
</thead>
<tbody>
<tr th:each="temp : ${invoice?.invoiceProducts}" >
<td><input name="invoiceProducts[${tempStat.index}].productId" th:value="${temp.productId}"/></td>
<td><input name="invoiceProducts[${tempStat.index}].quantity" th:value="${temp.quantity}"/></td>
<td><input name="invoiceProducts[${tempStat.index}].price" th:value="${temp.price}"/></td>
</tr>
</tbody>
</table>
<button type="submit" class="btn btn-info col-2">Submit</button>
</form>
InvoiceController:
#PostMapping("/saveInvoice")
public String postInvoice(#ModelAttribute("invoice") Invoice invoice) {
logger.info("invoice products " + invoice.getInvoiceProducts());
return "dummy";
}
What else do I need to add so that my update-invoice.html passes the list to my controller ?
You can try something like below as an example for thymleaf:-
<form method="post" th:action="#{/users/}" th:object="${userInfo}" class="col card p-3 mb-5">
<div class="form-group">
<label for="firstName">First Name</label>
<input id="firstName" placeholder="Enter First Name" required type="text" th:field="*{firstName}"
class="form-control"/>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input id="lastName" placeholder="Enter Last Name" required type="text" th:field="*{lastName}"
class="form-control"/>
</div>
<div class="form-group">
<label for="role">Role</label>
<select id="role" required th:field="*{role}" class="form-control ">
<option value="" hidden>Select a role</option>
<option th:each="role : ${T(com.springhow.examples.springboot.thymeleaf.domain.entities.Role).values()}"
th:value="${role}"
th:text="${role}">
</option>
</select>
</div>
<input type="submit" class="btn btn-primary" value="Create User">
</form>
and into Controller:-
#RequestMapping(value = "/", method = RequestMethod.POST)
public String createUser(Model model, #ModelAttribute UserInfo userInfo) {
UserInfo user = userService.createUser(userInfo);
return "redirect:/users/";
}

How to access a list property of an object using thymleaf

How can I access a list variable contained inside the object in HTML using Thymleaf
Eg :All the aClient properties are String objects.They can be accessed as
<tr th:each="aClient : ${clientList}">
<td th:text="${aClient.firstName}"/>
<td th:text="${aClient.lastName}"/>
<td th:text="${aClient.address}"/>
<td th:text="${aClient.clientType}"/>
But for property like
List<Service> services.
How can I access it?
Tried doing below after some reading. But it did not pick up the names of services.
<div th:each="service, serv : *{services}">
<input type="text" th:field="*{service[__${serv.index}__].name}" />
</div>
"name" is the property in Service class with getters and setters.
The contents of an input field are identified by the value attribute.
Also, you don't need to use the Thymeleaf serv iterator tracker, since in this case, you are already iterating each Service object - and you just want to get the value of each object's name field.
This can all be simplified to the following:
<div th:each="service : ${services}">
<input type="text" th:value="${service.name}" />
</div>
(It's basically the same approach as you used for your th:text data.)
The generated HTML is as follows:
<div>
<input type="text" value="service 1 name">
</div>
<div>
<input type="text" value="service 2 name">
</div>
This assumes you want each input field in its own div.
Needed to insert the serivce names in the column.This is achieved by
<td>
<div th:each="service : ${aClient.services}" th:text="${service.name}"></div>
</td>

Thymeleaf : How to edit a field?

In my class Test I have a list of Versions like this :
Test Class:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "idTest")
private List<Versions> version;
In thymeleaf I code this to display the list of version
<tr th:each ="test : ${testList}">
<td th:each="p : ${test.version}" th:text="${p.getVersions().version}" > </td>
</tr>
This work for me.
Now I would edit it (Version field), my code :
<form class="form-horizontal" th:object="${update}" th:action="#{/update}" method="post">
<div class="col-sm-10">
<th:block th:each="p : ${version}" >
<input type="text" class="form-control" th:field="*{p.getVersions().version}"/>
</th:block>
</div>
</form>
It display only the label Version, and it doesn't show the version input
Have you any idea ?
Thanks.
It seeems you're not iterating the object correctly. Try this:
<tr th:each ="test : ${testList}">
<form class="form-horizontal" th:object="${update}" th:action="#{/update}" method="post">
<div class="col-sm-10">
<th:block th:each="p : ${test.version}" >
<input type="text" class="form-control" th:field="*{p.getVersions().version}"/>
</th:block>
</div>
</form>
</tr>

Populating a DropDown Menu From a MySQL Database Using Hibernate in Spring MVC

I've looked into many sources to find the exact answer that I need, but unfortunately, I couldn't understand or they never hit the exact spot that I need.
Basicly, I am developing a spring-mvc web application and I am going to allow user to add post to the website. While adding this post, he/she is going to identify some features of the post, and one of the feature is category. Everything is working fine but category. I've tried to implement this category field with a dropdown menu, but I get nothing populated in dropdown when I run the project.
Here is my jsp page:
<%#taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%#include file="/WEB-INF/views/template/header.jsp" %>
<div id="page">
<div id="main">
<div class="row">
<div class="three columns"></div>
<div class="six columns">
<form:form action="${pageContext.request.contextPath}/addPost" method="post" commandName="post"
enctype="multipart/form-data">
<div class="form-group">
<label>Kategori</label>
<form:select path="category" id="category">
<form:option value="NONE" label="--- Seçiniz ---"/>
<form:options items="${category}"></form:options>
</form:select>
<!--select name="category">
<option name="category_id" value="0">Seçiniz</option>
</select-->
</div>
<div class="form-group">
<label>İlan Başlığı</label> <form:errors path="postTitle" cssStyle="color: #ff0000"/>
<form:input path="postTitle" id="title" class="form-Control"/>
</div>
<div class="form-group">
<label>İlan Açıklaması</label>
<form:input path="description" id="description" class="form-Control"/>
</div>
<!--div class="form-group">
<label>Etiketler</label>
<input type="text" class="asdasd" name="title" placeholder="İlanınız ile ilgili etiketler" required>
</div-->
<div class="form-control">
<label>Fiyat</label> <form:errors path="price" cssStyle="color: #ff0000"/>
<form:input path="price" id="price" class="form-Control"/>
</div>
<div class="form-group">
<label>Lokasyon</label>
<form:input path="postAddress" id="address" class="form-Control"/>
</div>
<div class="form-group">
<label class="control-label" for="postImage">Fotoğraf Yükle</label>
<form:input name="file" path="postImage" id="postImage" type="file" class="form:input-large"/>
</div>
<div class="form-group">
<button type="submit" value="submit" class="btn btn-send-message pull-right">İLAN OLUŞTUR</button>
</div>
</form:form>
</div>
<div class="three columns"></div>
</div>
</div>
And here is my Controller function:
#RequestMapping(value= "/addPost", method = RequestMethod.GET)
public String addPost(Model model) {
Post post = new Post();
post.setActive(true);
List<Category> category = categoryService.getAllCategories();
model.addAttribute("category", category);
model.addAttribute("post", post);
return "addPost";
}
What I want to do here is, I want to get the necessary data from the database table that I've created by the help of Hibernate, and send it to dropdown menu.
Many examples here in SO and some other blogs shows that feature like filling the dropdown menu from the controller by manually. This is the one that I don't want. So far, the closest answer that I can find is this. But since I am new in Spring, I couldn't get it.
If anyone can even point me in the right direction on how to set this up that would be a great help.
Thanks in advance.
From what I can see, it looks like you have two model attributes, the post and list of categories. The JSP has a form, this form is bound to the post bean, and you want to have the list of categories appear in a dropdown on the form.
As far as I know, and google this is not possible, as long as you have two separate model attributes. The problem is that <form:select path="category"..>
is scoped by your commandName bean, so it is looking for category as a property on post, and not in the view model.
I think you need to make a new class FormBean which has two properties, category and post, and then bind that to the from using commandName="formBean", and then use path="post.postTitle" for a single property on the post object, and <form:select path="category"...> for the dropdown.
Hope this helps

Dynamically added input elements are not preserving entered data when creating new input elements

I've created a test app to learn more about asp.net mvc. The app is supposed to allow the use to add an "unlimited" number of inputs via jQuery using partial views and editor templates. I've managed to get the adding of the new input elements but I'm having issues preserving the entered data from the user when adding newer input elements.
My strongly-typed view
#model TestApp.Models.ItemViewModel
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.ItemName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ItemName)
#Html.ValidationMessageFor(model => model.ItemName)
</div>
<div class="editor-label">
Click to add rates
</div>
<div class="editor-field">
Add Rate
<input type="hidden" value="0" id="rateCounter" />
</div>
<br />
<span id="itemRates"></span>
<p>
<input type="submit" value="Save" />
</p>
}
My javascript
When the user clicks on the NewRate element above, I fire this jQuery to get the partial view. I also keep track of the number of rows created by the user via a hidden element rateCounter which I increment everytime the click event fires.
$("#NewRate").click(function () {
var count = parseInt($("#rateCounter").val());
$("#rateCounter").val(count + 1);
$.ajax({
type: 'GET',
url: '/Item/AddRate',
data: {
rateCount: $("#rateCounter").val()
},
success: function (data) {
$('#itemRates').html(data);
}
});
});
My controller
This accepts the number of rows to create and passes to the partial view.
public PartialViewResult AddRate(int rateCount)
{
var itemVM = new ItemViewModel { Rates = new List<ItemRatesViewModel>() };
for (int i = 0; i < RateCount; i++)
{
itemVM.Rates.Add(new ItemRatesViewModel());
}
return PartialView("_ItemRates", itemVM);
}
My strongly-typed partial view
The partial view just uses an editor template
#model TestApp.Models.ItemViewModel
#Html.EditorFor(model => model.Rates)
My editor template
The editor template basically displays each rate
#model TestApp.Models.ItemRatesViewModel
<tr>
<td>
#Html.EditorFor(model => model.Rate)
#Html.ValidationMessageFor(model => model.Rate)
</td>
<td>
#Html.EditorFor(model => model.StartDate)
#Html.ValidationMessageFor(model => model.StartDate)
</td>
<td>
#Html.EditorFor(model => model.EndDate)
#Html.ValidationMessageFor(model => model.EndDate)
</td>
</tr>
On the first click
Using Google Chrome's developer tools, I see that the response is like this. Which should be correct. When I submit the form, the model binder picks up the entered data.
<tr>
<td>
<input class="text-box single-line" data-val="true" data-val-number="The field Rate must be a number." data-val-required="*" id="Rates_0__Rate" name="Rates[0].Rate" type="text" value="0.00" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].Rate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__StartDate" name="Rates[0].StartDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].StartDate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__EndDate" name="Rates[0].EndDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].EndDate" data-valmsg-replace="true"></span>
</td>
</tr>
On the second click
The entered data on the first set of input element (index [0]) is discarded and replaced by newly initialized one since I am using $('#itemRates').html(data); in my javascript instead of $('#itemRates').append(data);. Below is what I get. When I submit the form, the model binder picks up the entered data correctly in the Rates collection of the view model.
<tr>
<td>
<input class="text-box single-line" data-val="true" data-val-number="The field Rate must be a number." data-val-required="*" id="Rates_0__Rate" name="Rates[0].Rate" type="text" value="0.00" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].Rate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__StartDate" name="Rates[0].StartDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].StartDate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__EndDate" name="Rates[0].EndDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[0].EndDate" data-valmsg-replace="true"></span>
</td>
</tr>
<tr>
<td>
<input class="text-box single-line" data-val="true" data-val-number="The field Rate must be a number." data-val-required="*" id="Rates_1__Rate" name="Rates[1].Rate" type="text" value="0.00" />
<span class="field-validation-valid" data-valmsg-for="Rates[1].Rate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__StartDate" name="Rates[1].StartDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[1].StartDate" data-valmsg-replace="true"></span>
</td>
<td>
<input data-datepicker-future="True" data-val="true" data-val-required="*" id="Rates_0__EndDate" name="Rates[1].EndDate" type="text" value="01/01/0001" />
<span class="field-validation-valid" data-valmsg-for="Rates[1].EndDate" data-valmsg-replace="true"></span>
</td>
</tr>
FINALLY, my question
Is there a way to get just the 2nd row (index [1]) in the generated response then use jQuery's append instead of replacing the whole html with the new rows? What is the correct way of doing this? I know I'm close but a little guidance would go a long way. :)
I probably wouldn't be going back to the server and getting the whole view with all the rows this way.
The way MVC parses the sent data once posting the form is the array of elements sent back (specified by the number in [] brackets in the name attribute of the elements.
The way I have done it in the past is cloned the row by jquery and used a regex function to replace the[0] with [1] and cleared the values out also. Then just append it.
This will also save you from doing a call to the server every time the add is clicked.

Resources