Spring Thymelaf in form table list - java

I pass multiple object from my GET request page. And one of them is ReplacedPartList as a list of ReplacedPart.
ReplacedPart.java
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "replaced_part_id")
private Long replacedPartId;
#Column(name = "maintain_id")
private Long maintainId;
#Column(name = "part_serial_no")
private String partSerialNo;
#Column(name = "quantity")
private Long quantity;
#Column(name = "unit_price")
private Double unitPrice;
#Column(name = "total_price")
private Double totalPrice;
//GETTERS and SETTERS
Part of my controller's GET method
List<ReplacedPart> replacedPartList = new ArrayList<>();
for (int i = 0; i < 7; i++) {
ReplacedPart replacedPart = new ReplacedPart();
replacedPartList.add(replacedPart);
}
model.addAttribute("replacedPartList", replacedPartList);
part of my returned form
<tr th:each="replacedPart, stat : ${replacedPartList}">
<td th:text="${__${stat.index}__}"></td>
<td><input type="text" th:value="${replacedPartList[__${stat.index}__].partSerialNo}" th:field="${replacedPartList[__${stat.index}__].partSerialNo}"></td>
<td><input type="text" th:value="${replacedPartList[__${stat.index}__].quantity}" th:field="${replacedPartList[__${stat.index}__].quantity}"></td>
<td><input type="text" th:value="${replacedPartList[__${stat.index}__].unitPrice}" th:field="${replacedPartList[__${stat.index}__].unitPrice}"></td>
<td><input type="text" th:value="${replacedPartList[__${stat.index}__].totalPrice}" th:field="${replacedPartList[__${stat.index}__].totalPrice}"></td>
</tr>
error message
Neither BindingResult nor plain target object for bean name 'replacedPartList[0]' available as request attribute
And iy is just GET request not even POST. How can I solve this problem?

Your are not using the correct syntax as specified in the doc
Try this :
<tr th:each="replacedPart, rpStat : *{replacedPartList}">
<td th:text="${rpStat.index}"></td>
<td><input type="text" th:value="*{replacedPartList[__${rpStat.index}__].partSerialNo}" th:field="*{replacedPartList[__${rpStat.index}__].partSerialNo}"></td>
<td><input type="text" th:value="*{replacedPartList[__${rpStat.index}__].quantity}" th:field="*{replacedPartList[__${rpStat.index}__].quantity}"></td>
<td><input type="text" th:value="*{replacedPartList[__${rpStat.index}__].unitPrice}" th:field="*{replacedPartList[__${rpStat.index}__].unitPrice}"></td>
<td><input type="text" th:value="*{replacedPartList[__*{rpStat.index}__].totalPrice}" th:field="*{replacedPartList[__${rpStat.index}__].totalPrice}"></td>
</tr>
When using a list whether you want to show it so you have to not use it in a form. Forms ar binded to an object with the 'th:object' attribute. So if you will fill it it has to be a part of your model Maintain class.
Here is a full example on how to manipulate lists.

I solved my problem with MaintainFormDto class. I created as a form object and send this to view. And then I use * for the object binding like this.
MaintainFormDto
#Valid
private Maintain maintain;
#Valid
private Demand demand;
private List<ReplacedPart> replacedPartList;
public MaintainFormDto(Maintain maintain, Demand demand, List<ReplacedPart> replacedPartList) {
this.maintain = maintain;
this.demand = demand;
this.replacedPartList = replacedPartList;
}
//GETTER and SETTERS
MaintainController
MaintainFormDto formDto = new MaintainFormDto(maintain, demand, replacedPartList);
model.addAttribute("form", formDto);
form.html
<tr th:each="replacedPart, stat : *{replacedPartList}">
<td th:text="${__${stat.index}__}"></td>
<td><input type="text" th:value="*{replacedPartList[__${stat.index}__].partSerialNo}" th:field="*{replacedPartList[__${stat.index}__].partSerialNo}"></td>
<td><input type="text" th:value="*{replacedPartList[__${stat.index}__].quantity}" th:field="*{replacedPartList[__${stat.index}__].quantity}"></td>
<td><input type="text" th:value="*{replacedPartList[__${stat.index}__].unitPrice}" th:field="*{replacedPartList[__${stat.index}__].unitPrice}"></td>
<td><input type="text" th:value="*{replacedPartList[__${stat.index}__].totalPrice}" th:field="*{replacedPartList[__${stat.index}__].totalPrice}"></td>
</tr>

Related

How to post list of objects eg:- List<products> through a table which is inside a form in thymeleaf?

this is my html:
when I am seeing the preview in intelliJ of the same page, it is showing the right page but as soon as I run my program the and click on addInvoice button it is just showing the fields which are above the table only ....the table inputs are not showing in browser.The problem is inside<tr th:each product...>
<input type="hidden" th:field="*{primaryKey}"
placeholder="primaryKey" class="form-control mb-4 col-4">
<input type="text" th:field="*{invoiceNo}"
placeholder="invoiceNo" class="form-control mb-4 col-4">
....
....
<table border="1" class="table table-striped table-responsive-md">
<thead>
<tr>
<th>SNo</th>
<th>Product</th>
<th>Description</th>
<th>Price</th>
<th>Qty</th>
<th>Tax%</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr th:each="product,listOfProductsStat:*{listOfProducts}">
<td th:text="${listOfProductsStat.count}">1</td>
<td>
<input type="text" th:field="*{listOfProducts[__${listOfProductsStat.index}__].productName}">
</td>
<td>
<textarea class="form-control rounded-0" name="description" id="description" rows="3"
maxlength="500"
placeholder="Description"
th:field="*{listOfProducts[__${listOfProductsStat.index}__].description}"></textarea>
</td>
<td>
<input type="number" name="price" id="price" class="form-control"
th:field="*{listOfProducts[__${listOfProductsStat.index}__].price}" placeholder="Price">
</td>
<td>
<input type="number" name="qty" class="form-control" id="qty" placeholder="Qty"
th:field="*{listOfProducts[__${listOfProductsStat.index}__].qty}">
</td>
<td>
<input type="number" name="taxPercent" class="form-control" id="taxPercent" placeholder="Tax(%)"
th:field="*{listOfProducts[__${listOfProductsStat.index}__].taxPercent}">
</td>
<td>
<input type="number" name="total" class="form-control" id="total" placeholder="Total"
th:field="*{listOfProducts[__${listOfProductsStat.index}__].total}">
</tr>
</tbody>
</table>
this is my entities:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class InvoiceInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long primaryKey;
// private int SNo;
private String invoiceNo;
private String billTo;
private String status;
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date invoiceDate;
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date dueDate;
private double grandTotal;
#OneToMany(mappedBy = "invoiceInfo",cascade = CascadeType.ALL)
private List<Product> listOfProducts=new ArrayList<>();
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long productId;
private String productName;
private String description;
private double price;
private int qty;
private int taxPercent;
private double total;
// private double subTotal;
// private int totalTax;
// private String invoiceNote;
#ManyToOne
#JoinColumn(name = "invoice_info_primary_key")
private InvoiceInfo invoiceInfo;
this is my controller:
#Controller
public class InvoiceInfoController {
#Autowired
private InvoiceInfoService invoiceInfoService;
#GetMapping("")
public String InvoiceInfoListPage(Model model) {
model.addAttribute("InvoiceInfoList", invoiceInfoService.getAllInvoiceInfo());
model.addAttribute("products",invoiceInfoService.getAllProducts());
return "Home";
}
#GetMapping("/openNewInvoiceInfoForm")
public String openNewInvoiceInfoForm(Model model) {
InvoiceInfo invoiceInfo = new InvoiceInfo();
model.addAttribute("invoiceInfo", invoiceInfo);
return "new_invoiceInfo";
}
#PostMapping("saveInvoice")
public String saveInvoiceInfo(#ModelAttribute("invoiceInfo") InvoiceInfo invoiceInfo) {
invoiceInfoService.saveInvoiceInfo(invoiceInfo);
return "redirect:/";
}
#GetMapping("/editInvoiceForm/{primaryKey}")
public String editInvoiceForm(#PathVariable(value = "primaryKey") long primaryKey , Model model) {
InvoiceInfo invoiceInfo= invoiceInfoService.getInvoiceByPrimaryKey(primaryKey);
model.addAttribute("invoiceInfo",invoiceInfo);
return "update_invoiceInfo";
}
#GetMapping("/deleteInvoiceByPrimaryKey/{primaryKey}")
public String deleteInvoiceByPrimaryKey(#PathVariable(value = "primaryKey") long primaryKey){
this.invoiceInfoService.deleteInvoiceByPrimaryKey(primaryKey);
return "redirect:/";
}
}
the problem:
<tr th:each="product,listOfProductsStat:*{listOfProducts}">
my code is not even showing the <td>enter image description here tags in browser ..
How should I associate the listOfProducts instance in InvoiceInfo class with the form inputs and save it in Product table one by one?
Please help!

Not filling all Spring form fields and getting bad request error 400

I have this hibernate model:
#Entity
#Table(name="BlogPost")
public class BlogPost implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
private String title;
#Column
private String text;
#Column(name="creation_date")
private Date creationDate;
#ManyToOne
#JoinColumn(name = "cat_id")
private PostCategory category;
}
and this form:
<form:form action="saveBlogPost" method="post" modelAttribute="blogPost">
<table>
<form:hidden path="id"/>
<tr>
<td>Title:</td>
<td><form:input path="title" /></td>
</tr>
<tr>
<td>Text:</td>
<td><form:input path="text" /></td>
</tr>
<tr>
<td>Category:</td>
<td>
<form:select path="category">
<c:forEach items="${allCats}" var="cat" >
<form:option value="${cat.id}">
${cat.title}
</form:option>
</c:forEach>
</form:select>
</td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="Save"></td>
</tr>
</table>
</form:form>
and this controller method:
#RequestMapping(value = "/saveBlogPost", method = RequestMethod.POST)
public ModelAndView saveEmployee(#ModelAttribute BlogPost blogPost) {
if (blogPost.getId() == 0) {
blogPost.setCategory(postCategoryServiceImpl.getPostCategory(blogPost.getCategory().getId()));
blogPost.setCreationDate(new Date());
blogPostServiceImpl.addBlogPost(blogPost);
} else {
blogPostServiceImpl.updateBlogPost(blogPost);
}
return new ModelAndView("redirect:/");
}
I set my creationDate in the controller and other fields done by Spring form.
I suspect not setting creationDate in the form caused getting bad request error on form submit.
What should I do to avoid this error?
Looks like same issue as Spring MVC Error: Failed to convert property value of type java.lang.String to required type.
That is, <form:select> only submits a single string value, which you're telling Spring to put into a PostCategory object.
Try telling it to put the value into the category sub-object's id field:
<form:select path="category.id">

Save input field in Thymeleaf to Set<>

I would like to save the inputs to the Set interface. I have class Client.java:
#Table(name = "client")
public class Client {
#OneToOne(cascade = CascadeType.ALL)
private ShippingAddress shippingAddress;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Address> shippingAddresses = new HashSet<>();
}
Class ShippingAddress.java:
#Table(name = "address")
public class ShippingAddress {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
Long id;
String street;
String zip;
}
And it's my create form:
<form th:action="#{/add}" method="post" th:object="${client}">
<tr>
<td><input class="form-control" type="text" th:field="*{shippingAddress.street}"/></td>
<td><input class="form-control" type="text" th:field="*{shippingAddress.zip}"/></td>
</tr>
It works fine, but I can only save one street and one zip. I tried to improve it in this way to be able to save more data:
<form th:action="#{/add}" method="post" th:object="${client}">
<tr>
<td><input class="form-control" type="text" th:field="*{shippingAddress[0].street}"/></td>
<td><input class="form-control" type="text" th:field="*{shippingAddress[0].zip}"/></td>
</tr>
<tr>
<td><input class="form-control" type="text" th:field="*{shippingAddress[1].street}"/></td>
<td><input class="form-control" type="text" th:field="*{shippingAddress[1].zip}"/></td>
</tr>
But I get information:
Invalid property 'shippingAddress[0]' of bean class [model.Client]: Property referenced in indexed property path 'shippingAddress[0]' is neither an array nor a List nor a Set nor a Map; returned value was [ShippingAddress(id=null, street=null, zip=null, state=null, city=null, country=null)]
To add values to Set I should use the add method? But how to implement it with Thymeleaf?
Method in Controller (save data):
#Transactional
#RequestMapping(value = "add", method = RequestMethod.POST)
public String saveClient(#ModelAttribute Client client) {
clientRepository.save(client);
return "redirect:/";
}
Method in Controller (open create form)
#RequestMapping("/create")
public String newClient(Model model) {
model.addAttribute("client", new Client());
return "create";
}

Spring mvc handling differences in types between form and model

I have this form
<form:form action="saveCustomer" modelAttribute="customer" enctype="multipart/form-data" method="POST">
<!-- need to associate this data with customer id -->
<form:hidden path="id" />
<table>
<tbody>
<tr>
<td><label>First name:</label></td>
<td><form:input path="firstName" /></td>
</tr>
<tr>
<td><label>Last name:</label></td>
<td><form:input path="lastName" /></td>
</tr>
<tr>
<td><label>Email:</label></td>
<td><form:input path="email" /></td>
</tr>
<tr>
<td><label>Profile Image:</label></td>
<td>
<form:input type="file" path="file" id="file" class="form-control input-sm"/>
</td>
</tr>
<tr>
<td><label></label></td>
<td><input type="submit" value="Save" class="save" /></td>
</tr>
</tbody>
</table>
</form:form>
and this model
#Entity
#Table(name="customer")
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#Column(name="email")
private String email;
#NotEmpty
#Column(name="file")
private String file;
}
In my model, I have decided not to define the file field as MultipartFile and instead I went with String.
I did that to enable me just grab the uploaded files file name and leave spring mvc to upload the file. That works but when I introduce error checking I get this error:
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'customer' on field 'file': rejected value
[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile#2a8400bb];
codes
[typeMismatch.customer.file,typeMismatch.file,typeMismatch.java.lang.String,typeMismatch];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable:
codes [customer.file,file]; arguments []; default message [file]];
default message [Failed to convert property value of type
[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile]
to required type [java.lang.String] for property 'file'; nested
exception is java.lang.IllegalStateException: Cannot convert value of
type
[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile]
to required type [java.lang.String] for property 'file': no matching
editors or conversion strategy found]
My controller:
#RequestMapping(value = "/saveCustomer", method = RequestMethod.POST)
public String saveCustomer(#Valid FileBucket fileBucket,
ModelMap model, #ModelAttribute("customer") Customer theCustomer,BindingResult result) throws IOException {
if (result.hasErrors()) {
System.out.println("validation errors");
return "customer-form";
} else {
System.out.println("Fetching file");
MultipartFile multipartFile = fileBucket.getFile();
// Now do something with file...
FileCopyUtils.copy(fileBucket.getFile().getBytes(), new File( UPLOAD_LOCATION + fileBucket.getFile().getOriginalFilename()));
String fileName = multipartFile.getOriginalFilename();
model.addAttribute("fileName", fileName);
theCustomer.setFile(fileName);
customerService.saveCustomer(theCustomer);
return "redirect:/customer/list";
}
}
How can I handle this error?
In your error part say about this only
typeMismatch.customer.file,typeMismatch.file,typeMismatch.java.lang.String,typeMismatch
but in entity class file is a string
<form:form action="saveCustomer" modelAttribute="customer" enctype="multipart/form-data" method="POST">
In your form
<form:input type="file" path="file" id="file" class="form-control input-sm"/>
but, here your modelAttribute is customer front end its and file input and backend it's a String so that your having problem
private String file is incorrect param because it's a multipart data so you should use MultipartFile in your entity class
#Entity
#Table(name="customer")
public class Customer {
... . . . . ..
private MultipartFile file;
//getters setters
}
I created another field called path and made file transient
#Entity
#Table(name="customer")
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#Column(name="email")
private String email;
#NotEmpty
#Column(name="path")
private String path;
#Transient
private MultipartFile file;
and that worked.

How to save multiple identical entities from one form in Spring Controller?

I have a question, how to save multiple identical entities from one form in Spring Controller?
If I have the following code in html:
<form method="post" action="/dictionary/save">
<table>
... BEGIN jsp foreach function ...
<tr>
<td><input type=hidden name="id" value="${entity.id}"></td>
<td><input type=text name="en" value="${entity.en}"></td>
<td><input type=text name="lv" value="${entity.lv}"></td>
<td><input type=text name="ru" value="${entity.ru}"></td>
</tr>
... END jsp foreach function ...
</table>
<input type=submit value="Save">
</form>
In JSP listing can be till 50 entities. How to save its all in one request?
Create a modelAttribute of a domain object say .. dictionary which would have a list of some element (that you say can be 50 in the JSP)
in the JSP, use the modelAttribute in the form:form tag
and instead of input type use:
In Spring
class Dictionary{
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(unique = true, nullable = false)
private String code;
#Column
private String ru;
#Column
private String lv;
#Column
private String en;
}
In Controller
List<Dictionary> diction=new ArrayList<Dictionary>();
model.addattribute("dictionary",diction);
In JSP
<form:form method="post" action="/dictionary/save" modelAttribute="dictionary">
<table><tr>
<td>
<form:input path="diction["+rowNum+"].code" />
<form:input path="diction["+rowNum+"].ru" />
<form:input path="diction["+rowNum+"].lv" />
<form:input path="diction["+rowNum+"].en" />
</td>
//code to add next td (either through javascript or jquery)
</form:form>
*Please see that
1. tags wont work in javascript or jquery and you can have simple input tags as *
<input type="text" name="code"/>
This name input could be as many as you want

Categories