How to show nested object value in Thymeleaf form - java

I have Book and Author entities that I would like to bind to html form (user will insert data for Book with authors and those data will be sent via html form to the mysql database). I do not know how to bind Book.authors.forename and Book.authors.surname values to my thymeleaf form.
Book.java
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String isbn;
#ManyToMany(cascade = CascadeType.ALL) // to save author's data when adding book to db
#JsonIgnoreProperties("books")
#JoinTable(name = "author_book", joinColumns = #JoinColumn(name = "book_id"),
inverseJoinColumns = #JoinColumn(name = "author_id"))
private Set<Author> authors = new HashSet<>();
Author.java
#Entity
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String forename;
private String surname;
#ManyToMany(mappedBy = "authors", cascade = CascadeType.ALL) // look -> #ManyToMany in Book
#JsonIgnoreProperties("authors")
private Set<Book> books = new HashSet<>();
BookController.java
(...)
#GetMapping("/book/add")
public ModelAndView addGet() {
ModelAndView m = new ModelAndView();
m.addObject("book", new Book());
m.setViewName("addBook");
return m;
}
#PostMapping("/book/add")
public ModelAndView addBook(Book book, BindingResult br) {
ModelAndView m = new ModelAndView("redirect:/index");
bookService.addBook(book);
m.addObject("book", new Book());
return m;
}
addBook.html
<form th:action="#{/book/add}" th:object="${book}" th:method="post">
<label for="author_name"> Author_Name </label>
<input th:field="*{author}" id="author_name" type="text">
<label for="author_surname"> Author_Surn </label>
<input th:field="*{author}" id="author_surname" type="text">
<label for="title"> Title </label>
<input th:field="*{title}" id="title" type="text">
<label for="isbn"> ISBN </label>
<input th:field="*{isbn}" id="isbn" type="text">
<button type="submit"> Add</button>
</form>
Problem is in the author_name and author_surname label and input.

you can try this code only for view purpose......
<span th:each="authors,iterStat : ${student.authors}">
<span th:text="${authors.forename}"/><th:block th:if="${!iterStat.last}">,</th:block>
</span>
according to your code it's a data entry form .but for many-to-many relation it's not a good decision for insert book table data with author name from same time....
you can save book data first than assign author name .....
for more help click this link

Related

Post Object with nested List object in Spring-boot and Thymeleaf

Spring/Thymeleaf beginner sorry in advance but I have 2 entities Employee and MeetingInfo. Employee has a oneToMany relationship with MeetingInfo so basically an Employee can have many MessageInfo. Using psvm I can add a new Employee with multiple MessageInfo to my database using something like this:
Employee employee1 = new Employee("Employee 1");
MeetingInfo mInfo1 = new MeetingInfo(LocalDate.of(2021, 1, 1), "First Random message");
MeetingInfo mInfo2 = new MeetingInfo(LocalDate.of(2021, 2, 2), "Second Random message");
MeetingInfo mInfo3 = new MeetingInfo(LocalDate.of(2021, 3, 3), "Third Random message");
employee1.getMeetingInfo().add(mInfo1);
employee1.getMeetingInfo().add(mInfo2);
employee1.getMeetingInfo().add(mInfo3);
employeeRepository.save(employee1);
But how can I do this with a form in thymeleaf? I can add just an employee, but cant add a new MeetingInfo object. When I do I get a passException error.
My new_employee.html
<form action="#" th:action="#{/ines/saveEmployee}" th:object="${employee}"
method="POST">
<input type="text" th:field="*{name}"
placeholder="Employee Name" class="form-control mb-4 col-4">
*** so if I remove between here***
<input type="date" th:field="*{meetingInfo.meetingDate}"
placeholder="Message Date" class="form-control mb-4 col-4">
<input type="text" th:field="*{meetingInfo.message}"
placeholder="Message" class="form-control mb-4 col-4">
*** and here***
*** how can I include a MessageInfo object with a new Employee?***
<button type="submit" class="btn btn-info col-2">Save Meeting</button>
</form>
My Controller
#GetMapping("/showNewEmployeeForm")
public String showNewEmployeeForm(Model model) {
Employee employee = new Employee();
model.addAttribute("employee", employee);
return "meeting/new_employee.html";
}
#PostMapping("/saveEmployee")
public String saveEmployee(#ModelAttribute("employee") Employee employee) {
employeeService.saveMessage(employee);
return "redirect:/ines/employees";
}
Employee
#Entity
#Table(name = "employee")
public class Employee {
#Id
#Column(name = "employee_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long employeeId;
#Column(nullable = false)
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "employee_id")
private List<MeetingInfo> meetingInfo = new ArrayList<>();
//Constructors, getters and setters
MeetingInfo
#Entity
#Table(name = "meeting_info")
public class MeetingInfo {
#Id
#Column(name = "meeting_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long meetingId;
private String message;
#Column(name = "meeting_date")
private LocalDate meetingDate;
//Constructors, getters and setters
Saving multiple entities with a single request isn't something that you would usually want to do with your Spring Boot app, however, since I understand that this is for practice only, you could do this by using a single DTO object that would hold the information for both entities:
public class EmployeeMeetingDTO {
private String employeeName;
private String meetingMessage;
private LocalDate meetingDate;
}
Your controller could then accept just a single DTO entity from the request:
#PostMapping("/saveEmployee")
public String saveEmployee(#ModelAttribute("employeeDto") EmployeeMeetingDTO employeeDto) {
employeeService.saveMessage(employeeDto);
return "redirect:/ines/employees";
}
And you can separately create both entities in your EmployeeService class. Your Thymeleaf form would then look something like this:
<form action="#" th:action="#{/ines/saveEmployee}" th:object="${employeeDto}"
method="POST">
<input type="text" th:field="*{employeeName}"
placeholder="Employee Name" class="form-control mb-4 col-4">
<input type="date" th:field="*{meetingDate}"
placeholder="Message Date" class="form-control mb-4 col-4">
<input type="text" th:field="*{meetingMessage}"
placeholder="Message" class="form-control mb-4 col-4">
<button type="submit" class="btn btn-info col-2">Save Meeting</button>
</form>

Why got Invalid property 'district' of bean class

While run spring boot project, got error in home page Invalid property 'district' of bean class.
I know why this error is coming because district is property of child entity and i can pass parent entity from Home()method in controller. I could pass Person entity in model in Home() method. but district and city property is from Address entity I am working with OneToOne relationship mapping.
My question are below:
Can we get two entity together in th:object in thymeleaf
Can we send Address and Person entity together using Model from controller to view
Stacktrace:
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'district' of bean class [com.rest.RestApiPojo.Entity.Person]: Bean property 'district' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Here down is my code:
Entity
#Entity
#Table(name = "person_master")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long p_id;
private String name;
private String surname;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Address address;
// getter setter
}
#Entity
#Table(name = "address_master")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long a_id;
private String district;
private String city;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "address")
#JoinColumn(name = "p_id")
private Person person;
// getter setter
}
service
#Override
public Person addPersonAddress(Person person) {
return personRepo.save(person);
}
controller
#RequestMapping(value = "/", method = RequestMethod.GET)
public String Home(Model mdl)
{
mdl.addAttribute("persons", new Person());
return "register";
}
#RequestMapping(value = "/personaddress", method = RequestMethod.POST)
public String addPersonAddress(Model mdl, #ModelAttribute("person") Person person, HttpServletRequest req)
{
Address address = person.getAddress(); // get reference of person from parent table and store in child table
address.setDistrict(req.getParameter("district"));
address.setCity(req.getParameter("city"));
address.setPerson(person);
pojoService.addPersonAddress(person);
return "listofperson";
}
Thymeleaf
<form th:action="#{/personaddress}" th:object="${persons}" method="post">
<div class="container">
<h1 style="text-align: center">Add Person</h1>
<div class="row">
<div class="col-sm-12">
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">Person name</label>
<input type="text" class="form-control" name="name" th:field="*{name}">
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">Person surname</label>
<input type="text" class="form-control" name="surname" th:field="*{surname}">
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">District</label>
<input type="text" class="form-control" name="district" th:field="*{district}">
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">City</label>
<input type="text" class="form-control" name="city" th:field="*{city}">
</div>
<input class="btn btn-primary" type="submit" value="Submit">
</div>
</div>
</div>
</form>
Two entities are not required as they have already been mapped in the person class just write 'address.district' and 'address.city' in the thymeleaf, you will get it
This is regarding boilerplate code. you could add #Data to the class coming from Lombok library.
If you are not using lombok add setter and getter
public String getDistrict() {
return district;
}
public void setDistrict(String district) {
this.district= district;
}

Spring boot Thymeleaf populate dropdown list based on other dropdown list selections

How can I populate a dropdown list based on selections of other dropdown lists?
I have a Unit class, a Size class and a City class. The user must first select a country from a dropdown list of countries, the municipalities list will then display only the municipalities in that country, after that the user must select a city size, and at the end of it all, the user must select a city from a list of cities which are of the selected size, and belong to the selected municipality and country.
My code:
Unit.java
public class Unit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String name
#ManyToOne
#JoinColumn
#ToString.Exclude
private UnitType unitType;
#OneToOne
#JoinColumn
private Unit unit;
#OneToMany(mappedBy = "unit", cascade = CascadeType.ALL)
private Set<City> cities;
}
UnitType.java
public class UnitType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
#Enumerated(EnumType.STRING)
private UnitName uName;
#OneToMany(mappedBy = "unitType", cascade = CascadeType.ALL)
private Set<Unit> units;
public enum UnitName {
COUNTY, MUNICIPALITY
}
}
CitySize.java
public class CitySize {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="naziv", nullable=false)
#Enumerated(EnumType.STRING)
private Size name;
#OneToMany(mappedBy = "citySize", cascade = CascadeType.ALL)
private Set<City> sizes;
public enum Size {
SMALL, MEDIUM, LARGE
}
}
City.java
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String name;
#ManyToOne
#JoinColumn
#ToString.Exclude
private CitySize citySize;
#ManyToOne
#JoinColumn
#ToString.Exclude
private Unit unit;
#OneToMany(mappedBy = "city", cascade = CascadeType.ALL)
private Set<Event> events;
}
Event.java
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private LocalDateTime time;
#ManyToOne
#JoinColumn
#ToString.Exclude
private City city;
}
EventController.java
public class EventController {
....
#GetMapping("/citySearch")
public String citySearch(Model model) {
model.addAttribute("event", new Event());
model.addAttribute("unit", new Unit());
model.addAttribute("citySize", new CitySize());
model.addAttribute("counties", unitRepository.findByUnitTypeId(50001L));
model.addAttribute("municipalities", unitRepository.findByUnitTypeId(50002L));
model.addAttribute("sizes", CitySize.Size.values());
model.addAttribute("cities", cityRepository.findAll());
return "citySearch";
}
#PostMapping("/citySearch")
public String citySearch(Event event, Model model, City city, Unit unit,
CitySize citySize) {
List<Event> foundEvents = eventRepository.findByCity(city);
model.addAttribute("unit", new Unit());
model.addAttribute("citySize", new CitySize());
model.addAttribute("counties", unitRepository.findByUnitTypeId(50001L));
model.addAttribute("municipalities", unitRepository.findByUnitTypeId(50002L));
model.addAttribute("sizes", CitySize.Size.values());
model.addAttribute("cities", cityRepository.findAll());
model.addAttribute("foundEvents", foundEvents);
return "citySearch";
}
}
citySearch.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" th:href="#{/css/style.css}" >
<title>City search</title>
</head>
<body>
<h1>Event search by city</h1>
<form th:object="${unit}" method="post">
<div class="form-group">
<label for="unit">County: </label>
<select th:id="countyOption" th:field="*{unit}">
<option value="" >choose counties</option>
<option th:each="county : ${counties}" th:value="${county.id}" th:text="${county.name}"></option>
</select>
</div>
<div class="form-group">
<label for="unit">Municipality: </label>
<select th:id="municipalityOption" th:field="*{unit}">
<option value="" >choose municipilaties</option>
<option th:each="municipality : ${municipilaties}" th:value="${municipality.id}" th:text="${municipality.name}"></option>
</select>
</div>
</form>
<form th:object="${citySize}" method="post">
<div class="form-group">
<label for="name">City size: </label>
<select th:field="*{name}">
<option value="" >choose a city size</option>
<option th:each="name : ${sizes}" th:value="${id}" th:text="${name}"></option>
</select>
</div>
</form>
<form th:object="${event}" method="post">
<div class="form-group">
<label for="city">City: </label>
<select th:field="*{city}">
<option value="" >choose cities</option>
<option th:each="city : ${cities}" th:value="${city.id}" th:text="${city.name}"></option>
</select>
</div>
<input type="submit" th:value="Search">
</form>
<table>
<tr>
<th>Name</th>
<th>City</th>
<th>Time</th>
</tr>
<tr th:each="event : ${foundEvents}">
<td><span th:text="${event.name}" >EVENT.NAME</span></td>
<td><span th:text="${event.city.name}" >CITY.NAME</span></td>
<td><span th:text="${#temporals.format(event.time, 'dd.MM.yyyy. HH:mm')}" >EVENT.TIME</span></td>
</tr>
</table>
<p><a th:href="#{/search}">Return</a></p>
</body>
</html>
So far, my web search provided information that this can't be done by using only Spring boot and Thymeleaf, only with jQuery. Since I don't know jQuery, I would require some instructions on how to write and implement the method in jQuery. Also, I don't have a WebConfig.java class, since I had no need for it so far in my app, but if I need it now, what does it have to contain?
Spring Boot and Thymeleaf can't do anything client-side, but you can still achieve what you are trying to do without Javascript / JQuery:
You can submit the selected country to the backend, where you calculate the possible municipalities for that country, which you can add to the model and only display those (Or disable all other options). However, you will have to do that for every step that limits the options, that means reloading the page everytime, which can be painful to use.
If you want to accomplish the same without reloading the page, you will have to resort to some client-side code - that means Javascript / JQuery.

A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance when updating

I have a one to many relationship between Project and Requirement entities.
Html:
<div class="container">
<div class="row">
<div class="col-sm-2"></div>
<div class="col-sm-8">
<form action="#" th:action="#{/projects/updateProject/(id=${project.id})}" method="post">
<input hidden="hidden" name="id" th:value="${project.id}" />
<div class="form-group">
<label>Project</label>
<input type="text" name="projectNaam" class="form-control" id="projectName" th:value="${project.projectName}" placeholder="Project" />
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
<div class="col-sm-2"></div>
</div>
</div>
This is my controller code of Project:
#RequestMapping(value = "/updateProject", method = RequestMethod.POST)
public String updateProject #ModelAttribute("project") Project project){
this.projectService.saveProject(project);
return "redirect:/projects";
}
And this is my Project class:
#Entity
#Table(name = "Project")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String projectName;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "project_id")
private Set<Requirement> requirements;
public Project(){
}
public Project(String projectName) {
this.projectName = projectName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public Set<Requirement> getRequirements(){ return requirements; }
public void setRequirements(Set<Requirement> requirements){ this.requirements = requirements; }
I get the error when updating the project (only updating the name).
Already looked on the Internet for a solution, but didn't find one working for me.
I would try one or all of the following:
You are passing the Project object from outside of transactional context. Make sure it is merged before saving.
Make sure that each Requirement in the Set has reference to Project entity.
Make sure that after merge, you do not use public void setRequirements(Set<Requirement> requirements) method.
As you are using Set. Make sure that Requirement has properly implemented hashCode and equals.

<form:input> tag in jsp is not converting to input tag of html for map property of model class

I have Map of CategoryAttribute object in Category model class .Due to Some reason categoryAttributes map is not able bind with <form:input> tag of Spring Mvc in jsp, however category object is available to jsp page. I need to catch input of Category Attributes properties name and value to controller and persist to database how would I do that. I have tried but <form:input> tag is not converting into input field please have a look where i have done mistake thanks for helping.
Category Model class
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "CATEGORY")
public class Category implements Serializable {
#Id
#Column(name = "CATEGORY_ID")
protected Long id;
#Column(name = "NAME", nullable = false)
#Index(name = "CATEGORY_NAME_INDEX", columnNames = { "NAME" })
protected String name;
#OneToMany(mappedBy = "category", targetEntity = CategoryAttribute.class, cascade = { CascadeType.ALL }, orphanRemoval = true)
#MapKey(name = "name")
#BatchSize(size = 50)
protected Map<String, CategoryAttribute> categoryAttributes = new HashMap<String, CategoryAttribute>();
}
CategoryAttribute class
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "CATEGORY_ATTRIBUTE")
public class CategoryAttribute implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "CATEGORY_ATTRIBUTE_ID")
protected Long id;
#Column(name = "NAME", nullable = false)
#Index(name = "CATEGORYATTRIBUTE_NAME_INDEX", columnNames = { "NAME" })
protected String name;
#Column(name = "VALUE")
protected String value;
#ManyToOne(targetEntity = Category.class, optional = false)
#JoinColumn(name = "CATEGORY_ID")
#Index(name = "CATEGORYATTRIBUTE_INDEX", columnNames = { "CATEGORY_ID" })
protected Category category;
}
Controller
#Controller
public class CategoryController {
private static final Logger logger = Logger
.getLogger(CategoryController.class);
#Autowired
private CatalogItemService catalogItemService;
public CatalogItemService getCatalogItemService() {
return catalogItemService;
}
public void setCatalogItemService(CatalogItemService catalogItemService) {
this.catalogItemService = catalogItemService;
}
#RequestMapping(value = "/redirectToForm", method = RequestMethod.GET)
public String retrieveForm(#ModelAttribute Category category) {
return "category";
}
//this function is responsible for sending the category object
#RequestMapping(value = "/gencategory", method = RequestMethod.GET)
public String genCategoryList(Model model, #RequestParam("id") String id) {
Category category = catalogItemService.findCategoryById(Long
.parseLong(id));
List<Category> categories = catalogItemService.findAllCategories();
List<CategoryMapper> childCategories = category
.getAllChildCategoryMappers();
List<CategoryMapper> parentCategories = category.getAllParentCategoryMappers();
model.addAttribute("categoryList", categories);
model.addAttribute("childCategoryList", childCategories);
model.addAttribute("parentCategoryList", parentCategories);
List<String> inventoryList = new ArrayList<String>();
inventoryList.add("ALWAYS_AVAILABLE");
inventoryList.add("UNAVAILABLE");
inventoryList.add("CHECK QUANTITY");
List<String> fulfillmentList = new ArrayList<String>();
fulfillmentList.add("Digital");
fulfillmentList.add("Gift card");
fulfillmentList.add("Pickup");
fulfillmentList.add("Physical Pickup or Ship");
fulfillmentList.add("Physical Ship");
model.addAttribute("category", category);
model.addAttribute("inventorIs", inventoryList);
model.addAttribute("fulfillmentIs", fulfillmentList);
return "generalcategory";
}
#RequestMapping(value = "/saveCategory", method = RequestMethod.POST)
public String saveCategory(#ModelAttribute("category") Category category,
BindingResult bindingResult,
#ModelAttribute("hiddenFormValue") String hiddenFormValue,
Model model) {
Category defaultParentCategory = catalogItemService
.findCategoryById(Long.parseLong(hiddenFormValue));
category.setDefaultParentCategory(defaultParentCategory);
List<Category> categories = catalogItemService.findAllCategories();
model.addAttribute("categoryList", categories);
category.setId(29965L);
catalogItemService.saveCategory(category);
return "generalcategory";
}
#InitBinder
public void customDateBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat(
"yyyy-MM-dd hh:mm:ss");
binder.registerCustomEditor(Date.class, "activeStartDate",
new CustomDateEditor(dateFormat, false));
binder.registerCustomEditor(Date.class, "activeEndDate",
new CustomDateEditor(dateFormat, false));
}
}
generalcategory.jsp
<form:form action="saveCategory" method="post" id="categoryForm" modelAttribute="category">
<c:set var="myRequestModel" value="${category}" scope="request" />
<c:out value="${myRequestModel.categoryAttributes}"></c:out>
<jsp:include page="categoryattributemodal.jsp">
<jsp:param name="category" value="${myRequestModel}" />
</jsp:include>
</form:form>
CategoryAttribute.jsp page where i am trying to map the map object of category class
<div class="modal fade" id="modalCategoryAttribute" tabindex="-1"
role="dialog" aria-labelledby="myModelCattLabel" aria-hidden="true">
<div class="modal-dialog" aria-hidden="true">
<div class="modal-content">
<div class="modal-body">
<div class="form-group">
<label for="key">Key*:</label>
<div class='input-group date' id='name'>
<form:input path="category.categoryAttribute['name']" cssClass="form-control" />
<!-- <input type="text" class="form-control" /> -->
</div>
</div>
<div class="form-group">
<label for="key">Attribute Value*:</label>
<div class='input-group date' id='attributeValue'>
<form:input path="category.categoryAttributes['value']" cssClass="form-control" />
<!-- <input type="text" class="form-control" /> -->
</div>
</div>
</div>
<div class="modal-footer">
<span class="text-muted"><input type="button" id="addCategoryAttrButton"
class="btn btn-primary" value="Save" /></span>
</div>
</div>
</div>
</div>
This is the page where input field is not coming when try to bind map object of category class
First you need to generate getters and Setters to all member variables.
Second : In form:input should bind to member variable only. So please change
<form:input path="category.categoryAttributes['value']" cssClass="form-ontrol" />
to
<form:input path="category.categoryAttributes['Key of HashMap'].memberVariableInCategoryAttribute" cssClass="form-ontrol" />

Categories