Spring MVC forms - model with reference to object - java

I created a form, that has 2 fields (product name and price) and dropdown list of category objects (product's categories).
I have no idea how to make this work, when I have got a of Category objects to be set in Product object.
Product:
public class Product {
private String name;
private Category category;
private int price;
public Product() {
}
public Product(String name, Category category, int price) {
this.name = name;
this.category = category;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
Product controller:
#ModelAttribute("categoryList")
public List<Category> categoryList() {
return categoryService.getCategories();
}
#RequestMapping("/products/add")
public ModelAndView addProductForm() {
ModelAndView mv = new ModelAndView("addProduct");
mv.addObject("product", new Product());
return mv;
}
#RequestMapping(value = "/products/add/process", method = RequestMethod.POST)
public ModelAndView addProduct(#ModelAttribute("product") Product product) {
ModelAndView mv = new ModelAndView("products");
System.out.println("added " + product.getName() + " " + product.getPrice());
return mv;
}
The form:
<form class="form-horizontal" action="#"
th:action="#{/products/add/process}" th:object="${product}"
method="post">
<fieldset>
<!-- Form Name -->
<legend>Add product</legend>
<!-- Text input-->
<div class="form-group">
<label class="col-md-4 control-label" for="textinput">Product
name</label>
<div class="col-md-4">
<input id="textinput" name="textinput" placeholder="Product name"
class="form-control input-md" required="" type="text"
th:field="*{name}"></input>
</div>
</div>
<!-- Select Basic -->
<div class="form-group">
<label class="col-md-4 control-label" for="selectbasic">Category</label>
<div class="col-md-4">
<select th:field="*{category}">
<option th:each="cat : ${categoryList}" th:value="${cat.getId()}"
th:text="${cat.getName()}"></option>
</select>
</div>
</div>
<!-- Text input-->
<div class="form-group">
<label class="col-md-4 control-label" for="textinput">Price</label>
<div class="col-md-4">
<input id="textinput" name="textinput" placeholder=""
class="form-control input-md" required="" type="text"
th:field="*{price}"></input>
</div>
</div>
<!-- Button -->
<div class="form-group">
<label class="col-md-4 control-label" for="singlebutton"></label>
<div class="col-md-4">
<button id="singlebutton" name="singlebutton"
class="btn btn-success">Add product</button>
</div>
</div>
</fieldset>
</form>
Additional Info from comments
When I submit it (see addProduct method - it's a form handler) I get: java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [com.example.shop.Category] for property 'category': no matching editors or conversion strategy found]. I simply can't convert String coming from dropdown to an object

Problem is that Spring doesn't have a built-in conversion ability from String to Category. It knows it needs a Category to use the setCategory(Category category) method of Product, but has no way of converting the String it gets from your posted drop down into one. Thus, you need to be a dear and help Spring some by telling it how to do the conversion and define a converter, see Spring docs for more info.
Easiest option is to use Converter SPI:
package com.example.shop.converter;
final class StringToCategoryConverter implements Converter<String, Category> {
public Category convert(String source) {
Category category;
// Put your conversion logic here
return category;
}
}
In your case, I'd guess you'll want to use: CategoryService.getCategory(int id) or a similar method.
Then you need to configure Spring to actually use your converter, here's an XML example of how to do that:
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.example.shop.converter.StringToCategoryConverter" />
</list>
</property>
</bean>

Related

Bean property 'categoryName' is not readable or has an invalid getter method

Could anyone take a look of this error and help me? I spent 2 hours to find problem but I didn't find solution.
Listing data works, but problem is when adding jsp with action="saveCategory.
org.springframework.beans.NotReadablePropertyException: Invalid property 'categoryName' of bean class [java.util.ArrayList]: Bean property 'categoryName' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
JSP:
<c:forEach var="tempInvoiceCategory" items="${invoiceCategory}">
<tr>
<td>${tempInvoiceCategory.categoryName}</td>
<td>
<button type="button" class="btn btn-info">Edit</button>
<button type="button" class="btn btn-danger">Delete</button>
</td>
</tr>
</c:forEach>
<form:form class="forms-sample" action="saveCategory" modelAttribute="invoiceCategory" method="POST">
<div class="form-group">
<label>Nazwa Kategorii</label>
<form:input path="categoryName" type="text" class="form-control"/>
</div>
<button type="submit" class="btn btn-primary mr-2">Submit</button>
</form:form>
Entity:
#Entity
#Table(name="T_INVOICE_CATEGORY")
public class InvoiceCategory {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="ID_CATEGORY")
private int id;
#Column(name="CATEGORY_NAME")
private String categoryName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
#Override
public String toString() {
return "InvoiceCategory [id=" + id + ", categoryName=" + categoryName + "]";
}
}
Controller:
#Controller
#RequestMapping("/invoice")
public class InvoiceController {
#Autowired
private InvoiceCategoryService invoiceCategoryService;
#GetMapping("/listCategory")
public String listCategory(Model theModel) {
List<InvoiceCategory> invoiceCategory = invoiceCategoryService.getInvoiceCategory();
theModel.addAttribute("invoiceCategory",invoiceCategory);
return "add-invoice-category";
}
#PostMapping("/saveCategory")
public String saveCustomer(#ModelAttribute("invoiceCategory") InvoiceCategory theInvoiceCategory) {
invoiceCategoryService.saveInvoiceCategory(theInvoiceCategory);
return "redirect:/customer/list";
}
}
I think problem is that model attribute invoiceCategory is List<InvoiceCategory> and in same JSP you try build list of items and form which try access to this item, but it is List<InvoiceCategory>:
<form:form class="forms-sample" action="saveCategory" modelAttribute="invoiceCategory" method="POST">
<div class="form-group">
<label>Nazwa Kategorii</label>
<form:input path="categoryName" type="text" class="form-control"/>
</div>
<button type="submit" class="btn btn-primary mr-2">Submit</button>
</form:form>
Try comment this part and run application again.

Error ID is null when should be auto-incremented

I'm trying to create a web form that allows a user to add an item to a database. Problem is when I submit said form I get the following error:
Field error in object 'products' on field 'id': rejected value [null]; codes [typeMismatch.products.id,typeMismatch.id,typeMismatch.int,typeMismatch]; arguments
Here's my jsp page:
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Add Item Page</title>
</head>
<body>
<h1> Add an Item </h1>
<form action="/addItem" method="post">
<div class="form-group">
<label for="name">Name</label>
<input class="form-control" type = "text" id="name" name="name" required minlength="2" autocomplete="off">
</div>
<div class="form-group">
<label for="price">Price</label>
<input class="form-control" type = "text" id="price" name="price" required>
</div>
<div class="form-group">
<label for="quantity">Quantity</label>
<input class="form-control" type="number" id="quantity" name="quantity" required>
</div>
<div class="form-group">
<label for="foodGroup">Food Group</label>
<input class="form-control" type="text" id="foodGroup" name="foodgroup" required>
</div>
<button type="submit" class="btn btn-primary" value="submit">Add Product</button>
</form>
Home
</body>
Here's the relevant part of my Products class (the rest is typical getter/setter/toString. I also left out the package line at the top):
import javax.persistence.*;
#Entity
#Table(name="products")
public class Products
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
int id;
String name;
String foodgroup;
int quantity;
Float price;
String image;
private Products() {}
public Products(int id, String name, int quantity, float price, String foodgroup, String image)
{
this.id = id;
this.name = name;
this.quantity = quantity;
this.foodgroup = foodgroup;
this.price = price;
this.image = image;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
and finally the controller:
#Controller
public class MainController
{
#Autowired
private ProductsDAO productsDAO;
#GetMapping("/")
public ModelAndView showHome()
{
ModelAndView mav = new ModelAndView("index");
return mav;
}
#RequestMapping("/showProducts")
public ModelAndView showProducts()
{
List<Products> leListOfProducts = productsDAO.findAll();
ModelAndView mav = new ModelAndView("showProducts", "product", leListOfProducts);
return mav;
}
#RequestMapping("/addItem")
public ModelAndView showAddItemPage()
{
return new ModelAndView("addItem");
}
#PostMapping("/addItem")
public ModelAndView addProduct(Products product)
{
productsDAO.create(product);
return new ModelAndView("redirect:/index");
}
}
Here's a pic of the table columns and their settings.
I really appreciate any and all help ya'll can provide!
Best,
JBird
I suspect that your constructor is the issue there. Try removing the id in the constructor, as it is being generated by 3rd party means. Also, I don't think you will ever need the setId() method for any sane reason.

Spring Boot Thymeleaf Dropdown List option doesn't display values

It's my first topic at this beautiful site;)
I cant fix this, I am trying to present names of states and products in drop down lists. Intelij is helping me by underlines these fields.
https://ibb.co/wKgV940 (cant add image)
When I am adding th:object to divs or form it doesn't help.
There are racords on my db, and mathod retrieves values properly.
<div class="row justify-content-center">
<div class="form-group col-md-8" >
<label for="firstName" class="col-form-label">Choose State</label>
<select class="form-control" th:field="*{state.id}" id="state">
<options items="${listStates}>"></options>
<option th:each="state : ${states}"
th:value="${state.id}"
th:utext="${state.stateName}"></option>
</select>
</div>
<div class="form-group col-md-8" >
<label for="firstName" class="col-form-label">Choose products</label>
<select class="form-control" th:field="*{product.id}" id="product">
<option th:each="product : ${products}"
th:value="${product.id}"
th:utext="${product.productName}"></option>
</select>
</div>
<div class="form-group col-md-8">
<label for="firstName" class="col-form-label">Start price</label>
<input id="firstName" type="number" value="1" min="0" max="1000" step="1"/>
</div>
<div class="form-group col-md-8">
<label for="firstName" class="col-form-label">Preferred final prize</label>
<input type="text" class="form-control"
id="lastName" placeholder=""/>
</div>
<div class="form-group col-md-8">
<label for="firstName" class="col-form-label">Logistic costs</label>
<input type="text" class="form-control"
id="email" placeholder=""/>
</div>
<div class="col-md-6">
<input type="submit" class="btn btn-primary" value=" Calculate ">
</div>
</div>
</form>
public class CalculateController {
#Autowired
private StateRepository stateService;
#Autowired
private ProductRepository productService;
#RequestMapping(value = { "/calculateForm" }, method = RequestMethod.GET)
public String selectState(Model model) {
List<Product> product = new ArrayList<>();
List<State> state = new ArrayList<>();
model.addAttribute("product", product);
model.addAttribute("state", state);
List<Product> products = productService.findAll();
List<State> states = stateService.findAll();
model.addAttribute("states", states);
model.addAttribute("products", products);
return "calculateForm";
}
}
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private String category;
private Double wholePrice;
public Product() {
}
public Product(String productName, String category, Double wholePrice) {
this.productName = productName;
this.category = category;
this.wholePrice = wholePrice;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public Double getWholePrice() {
return wholePrice;
}
public void setWholePrice(Double wholePrice) {
this.wholePrice = wholePrice;
}
}
I have replaced this:
List<Product> product = new ArrayList<>();
List<State> state = new ArrayList<>();
model.addAttribute("product", product);
model.addAttribute("state", state);
by this:
Product product = new Product();
State state = new State();
model.addAttribute("product", product);
model.addAttribute("state", state);```
now it works. Thank you.

[Failed to convert property value of type 'java.lang.String[]' to required type 'java.util.List' for property

I am working with Thymeleaf and trying to do some object binding, but I do not know how to do it if I have an object with a list. Let me explain:
My model:
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private String name;
#NotNull
#Lob
private String description;
#NotNull
private Date startDate;
private String status;
#ManyToMany
private List<Role> rolesNeeded;
#ManyToMany
private List<Collaborator> collaborators;
public Project() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<Role> getRolesNeeded() {
return rolesNeeded;
}
public void setRolesNeeded(List<Role> rolesNeeded) {
this.rolesNeeded = rolesNeeded;
}
public List<Collaborator> getCollaborators() {
return collaborators;
}
public void setCollaborators(List<Collaborator> collaborators) {
this.collaborators = collaborators;
}
}
My html form:
<form method="post" action="addproject" th:object="${project}">
<div>
<label for="project_name"> Project Name:</label>
<input th:field="*{name}" type="text" name="project_name"/>
</div>
<div>
<label for="project_description">Project Description:</label>
<textarea th:field="*{description}" rows="4" name="project_description"></textarea>
</div>
<div>
<label for="project_status">Project Status:</label>
<div class="custom-select">
<span class="dropdown-arrow"></span>
<select th:field="*{status}" name="project_status">
<option value="active">Active</option>
<option value="archived">Archived</option>
<option value="not_started">Not Started</option>
</select>
</div>
</div>
<div>
<label for="project_roles">Project Roles:</label>
<ul class="checkbox-list">
<li th:each="role : ${roles}">
<input th:field="*{rolesNeeded}" type="checkbox" name="project_roles" th:value="${role}"/>
<span class="primary" th:text="${role.name}"> Developer</span>
</li>
</ul>
</div>
<div class="actions">
<input type="submit" value="Save" class="button"/>
Cancel
</div>
</form>
And I am getting the error:
ERROR!!!!: Field error in object 'project' on field 'rolesNeeded':
rejected value
[com.imprender.instateam.model.Role#145d6cd4,com.imprender.instateam.model.Role#73020d6f];
codes
[typeMismatch.project.rolesNeeded,typeMismatch.rolesNeeded,typeMismatch.java.util.List,typeMismatch];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable:
codes [project.rolesNeeded,rolesNeeded]; arguments []; default message
[rolesNeeded]]; default message [Failed to convert property value of
type 'java.lang.String[]' to required type 'java.util.List' for
property 'rolesNeeded'; nested exception is
java.lang.IllegalStateException: Cannot convert value of type
'java.lang.String' to required type
'com.imprender.instateam.model.Role' for property 'rolesNeeded[0]': no
matching editors or conversion strategy found]
Basically, as far as I understood, the checkbox input returns a String[], but my object needs a list, so the binding cannot be perfomed.
How could I bind the array in the list? (do you have an example?)
Thank you.
If Your Role bean has active boolean property, You could do something like this (simplified):
<ul class="checkbox-list">
<li th:each="role,stat : ${project.rolesNeeded}">
<input th:field="*{rolesNeeded[__${stat.index}__].active}" type="checkbox"/>
<input th:field="*{rolesNeeded[__${stat.index}__].name}" type="hidden"/>
<span class="primary" th:text="${role.name}">Developer</span>
</li>
</ul>
If it does not, you could store the rolesNeeded in the hidden fields and populate them with javascript.

Cant inject checkbox value into spring bean

I am using SpringMVC and tring to read params from form. My bean looks like this :
public class ChannelBean {
private Integer id;
private Integer siteId;
private String name;
private Boolean active;
private Boolean premium;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public Boolean getPremium() {
return premium;
}
public void setPremium(Boolean premium) {
this.premium = premium;
}
public Integer getSiteId() {
return siteId;
}
public void setSiteId(Integer siteId) {
this.siteId = siteId;
}
}
And on the front end i have handlebars template :
<script id="editFormTemplate" type="text/x-handlebars-template">
<form id="channelForm" class="form-horizontal" action="/admin/channel/save.action" method="POST">
<input type="hidden" readonly="readonly" name="siteId" value="${selectedSiteId}">
<input type="hidden" readonly="readonly" name="id" value="{{id}}">
<div class="form-group">
<label class="col-sm-2 control-label channelName">Name<span class="required">*</span></label>
<div class="col-sm-10">
<input class="form-control" type="text" name="name" value="{{name}}" />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Premium</label>
<div class="col-sm-10">
<input type="checkbox" name="premium" {{#if premium}}checked{{/if}} value="{{premium}}">
</div>
</div>
<cms:csrfToken />
<div class="modal-footer">
<button type="submit" class="btn btn-edit btn-primary has-spinner submitEditButton">Ok<span class="spinner"><i class="fa fa-refresh fa-spin"></i></span></button>
<button type="button" class="btn btn-warning" data-dismiss="modal">Cancel</button>
</div>
</form>
</script>
In the controller in Java when i receive Bean, 'name' value is set properly but premium that comes from checkbox is null ?
Since you are using Boolean you need to initialise the value to false when the page loads. So either initialize premium to false on the page load OR
change private Boolean premium in the ChannelBean.java to private boolean premium.

Categories

Resources