Thyme leaf show selected value from object - spring-boot

I have a Spring Boot 1.3 app using Thymeleaf. I have a screen that shows a list of users, including the institution they are associated with. Thanks to some previous StackOverflow help, I am able to create a new user and select their institution from a drop down list. I am now trying to do something similar for editing a user - when you edit the record, the dropdown defaults to the institution the user has already been assigned.
I have a User:
#Entity
#Table(name = "Users")
public class User implements UserDetails {
#Id
private String username;
...
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "institutionId", nullable = false)
private Institution institution;
}
And an Institution:
#Entity
#Table(name = "Institution")
public class Institution {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long institutionId;
private String name;
#OneToMany(mappedBy = "institution", fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
List<User> users = new ArrayList<User>();
}
And here is my HTML to edit the user record (edited):
I have attached screen shots showing a user with an assigned institution, but when I try to edit, there is no dropdown.
How do I get that drop down there, and ideally pre-selected with the user.institution?
Here you can see the edit user does not have the dropdown:
EDIT to show progress and clean up the question:
I have it so that the the list appears to show the institution on the user when I go to edit.
<div class="col-md-5">
<div th:if="${user.institution != null }"
<select name="user.institution">
<option th:each="choice : ${institutionList}"
th:value="${choice.institutionId}"
th:attr="choiceinstitutionId=${choice.institutionId}, institutioninstitutionId=*{institution.institutionId}, showselected=(${choice.institutionId} == *{institution.institutionId})"
th:selected="(${choice.institutionId} == *{institution.institutionId})"
th:readonly="(${choice.institutionId} == *{institution.institutionId})"
th:text="${choice.name}">
</option>
</select>
</div>
<div th:if="${user.institution == null }">
<div th:if="${institutionList != null and not #lists.isEmpty(institutionList)}">
<select name="user.institution">
<option th:each="dropDownItem : ${institutionList}"
th:value="${dropDownItem.institutionId}"
th:text="${dropDownItem.name}" />
</select>
</div>
<div th:if="${institutionList == null or #lists.isEmpty(institutionList)}">
<div>"No Institutions were found, please create some first"</div>
</div>
</div>
<div th:if="${institutionList == null or #lists.isEmpty(institutionList)}">
<div>"No Institutions were found, please create some first"</div>
</div>
</div>
This is very close, except that it doesn't actually set the selected dropdown value on the user object, so its set to null each time...

Turns out it was a dumb mistake - not actually declaring the field to set. In case anyone else runs into a similar issue, here is the correct code to display any selected value in the list, and to set it:
<div th:if="${user.institution != null }">
<select name="user.institution" th:field="*{institution}">
<option th:each="choice : ${institutionList}"
th:value="${choice.institutionId}"
th:attr="choiceinstitutionId=${choice.institutionId}, institutioninstitutionId=*{institution.institutionId}, showselected=(${choice.institutionId} == *{institution.institutionId})"
th:selected="(${choice.institutionId} == *{institution.institutionId})"
th:readonly="(${choice.institutionId} == *{institution.institutionId})"
th:text="${choice.name}"></option>
</select>
</div>
<div th:if="${user.institution == null }">
<div th:if="${institutionList != null and not #lists.isEmpty(institutionList)}">
<select name="user.institution" th:field="*{institution}">
<option th:each="dropDownItem : ${institutionList}"
th:value="${dropDownItem.institutionId}"
th:text="${dropDownItem.name}" />
</select>
</div>
<div th:if="${institutionList == null or #lists.isEmpty(institutionList)}">
<div>"No Institutions were found, please create some first"</div>
</div>
</div>

Related

Show Springboot validation results in Thymeleaf template

I am getting started with Springboot and am unable to propagate validation results (errors) back to the thyme template. I have a typical setup: #Entity object, #Service, and #Repository. Here are the sections of my Controller and index template (and its form). UserVital, UserBlood, etc. are the data objects mapped to the DB tables using hibernate. Hope this information is enough for the members to point me in the right direction.
Data Object
#Entity
#Table(name = ".....")
public class UserVital {
#NotNull(message = "Height (Feet) cannot be null")
#Range(min = 0, max = 15, message = "Height (Feet) must be greater than 0")
#Column(name = "feet", nullable = false)
private int heightInFeet;
.............
}
Controller
#GetMapping("/")
public String getUsers(Model model) {
UserVital vital = new UserVital();
UserGrithMeasurements grith = new UserGrithMeasurements();
UserBloodChemistry blood = new UserBloodChemistry();
List<Category> categories = categoryService.getAllCategories();
model.addAttribute("categories", categories.get(0));
model.addAttribute("vital", vital);
model.addAttribute("grith", grith);
model.addAttribute("blood", blood);
return "index";
}
#PostMapping("/add")
public String addData(#Valid UserVital vital, BindingResult vitalValidationResult,
#Valid UserGrithMeasurements grith, BindingResult grithValidationResult, #Valid UserBloodChemistry blood,
BindingResult bloodValidationResult, Model model) {
if (vitalValidationResult.hasErrors() || grithValidationResult.hasErrors()
|| bloodValidationResult.hasErrors()) {
return "index";
} else {
model.addAttribute("successMsg", "Details saved successfully!!");
return "index";
}
}
Thyme Form
<form class="tab-content" method="POST" th:action="#{/add}">
<div class="form-group row">
<label for="height" class="col-sm-2 control-label" th:text="#{height}"></label>
<div class="col-sm-2">
<input type="number" class="form-control" id="feet" th:attr="placeholder=#{feet}"
th:field="${vital.heightInFeet}">
</div>
<div class="form-group col-sm-12">
<label for="neck" class="col-sm-2 control-label" th:text="#{neck}"></label>
<div class="col-sm-2">
<input type="number" class="form-control" id="systolic" th:attr="placeholder=#{inches}"
th:field="${grith.neck}">
<div class="col-sm-2">
<input type="number" class="form-control" id="ldl" th:field="${blood.ldl}">
.....
</form>
Question: As you can see I have multiple BindingResult objects. Each BindingResult object holding the validation results of the respective data object (vitalValidationResult holds validation result of UserVital object vital, etc.). Now, how do I write "th:if" statements in the template that will allow me to check if there are any errors in the fields.
Thanks.
I have solved the problem by encapsulating the required form fields in a div and then placing "th:object=${objectName}" in the div.
<form >
<div class="tab-pane active text-center" id="tab_1_1" th:object=${vital}>
<div class="tab-pane active text-center" id="tab_1_2" th:object=${grith}>
</form>

Thymeleaf - Setting Object's Field To Another Object

I've been stuck on this issue for a while now. I am making an E-Commerce application and I have a product class and a CartItems class.
The CartItems class contains a product in it, because in a cart you will have products inside and the CartItems basically takes the product you select and gets details like total price and quantity.
CartItems:
#Table(name = "cartitems")
public class CartItems implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(name = "quantity")
private int quantity;
#Column(name = "price")
private double price;
#OneToOne
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne
#JoinColumn(name = "cart_id")
private Cart cart;
public CartItems() {
}
public CartItems(Product product, int quantity, double price, Cart cart) {
this.quantity = quantity;
this.price = price;
this.product = product;
this.cart = cart;
}
Catalog Thymeleaf Page:
<div th:each="product : ${popularProducts}">
<form action="#" th:action="#{'/cart/'}" th:object="${cartItem}" method="POST">
<div class="card">
<img class="card-img" th:src="${product.pictureUrl}">
<a href="#" class="card-link text-danger like">
<i class="fas fa-heart"></i>
</a>
</div>
<div class="card-body">
<h4 class="card-title" th:text="${product.getName()}"></h4>
<h6 class="card-subtitle mb-2 text-muted" th:text="${product.getCategory()}"></h6>
<p class="card-text" th:text="${product.getDescription()}">
<div class="buy d-flex justify-content-between align-items-center">
<div class="price text-success"><h5 class="mt-4" th:text="'$' + ${product.getPrice()}"></h5></div>
<div class="form-group blu-margin">
<div class="form-label-group">
<input th:field="*{cartItem.quantity}" type="text" id="quantity" name="quantity" class="form-control" placeholder="1" required="required">
</div>
</div>
<i class="fas fa-shopping-cart"></i> Add to Cart
</div>
The problem is with setting the CartItems.product equal to the currently displayed product.
I am looping through each product, and if the user presses Add To Cart, I want the CartItem's product field to be equal to the currently displayed product. How do you manually set this when you pass it back up the controller?
Is there a way of just typing ${cartItem.product = product} without having the user to manually write it as an input?
Alternatively, is there a way I can feed up the product : ${popularProducts}
using model attribute? How do I bring that up once the form is submitted along with CartItem object?
I managed to fix this by putting the product id into the post mapping address as a path variable then using the product repository to find the product by the id passed through then setting it manually in the controller.

Java list items re-added to thymeleaf dropdown on page reload, appearing multiple times

I am building a Spring Boot app that calculates and displays Airbnb payouts on a monthly basis. Payout data is pulled in from the Airbnb Api, and user account information is stored in a database.
I have created a form where the user can specify the month and the listing to display the monthly payout. The user chooses the listing (the rental) from a dropdown menu. To display the names of the listings, a listingListDto attribute is added to the MVC model. A List of Listing entities is obtained from the database, and it is converted to a List of ListingDTO entities. This list gives the listingListDto.
When I reload the form page, the listings are readded to the dropdown, appearing twice, then three, four and more times.
How could I prevent this from happening?
I assume I could create a ListingListDTO entity, which would wrap the List of ListingDTOs, but I was hoping to be able to keep things simple, and use the List of ListingDTOs directly in the MVC model.
Here is the Controller method that displays the html form :
#RequestMapping(value = "payout", method = RequestMethod.GET)
public String payoutSelection(Model model, RedirectAttributes redirectAttributes) {
Long userId = sessionService.getCurrentUserId();
if (null == userId) {
return loginService.handleInvalidLogin("payout", redirectAttributes);
} else {
PayoutSelectionDTO payoutSelectionDto = new PayoutSelectionDTO();
LocalDate lastMonth = LocalDate.now().minusMonths(1);
payoutSelectionDto.setYear(lastMonth.getYear());
payoutSelectionDto.setMonth(lastMonth.getMonthValue());
Optional<User> user = userService.getUserById(userId);
model.addAttribute("payoutSelectionDto", payoutSelectionDto);
model.addAttribute("listingListDto", listingListDtoService.getListingListDTO(listingService.getListingsByUser(user.get())));
return "payout_monthpicker.html";
}
}
Here is the form which contains the dropdown of listings:
<body>
<div class="content-block">
<form action="#" th:action="#{/get_payouts}"
th:object="${payoutSelectionDto}" method="POST">
<h2>Kifizetések lekérése</h2>
<div class="content-group">
<select th:field="*{year}">
<option th:value="${payoutSelectionDto.year} -1"
th:text="${payoutSelectionDto.year} -1"></option>
<option th:value="*{year}" th:text="*{year}"></option>
</select> <select th:field="*{month}">
<option th:value="'1'" th:text="Január"></option>
<option th:value="'2'" th:text="Február"></option>
<option th:value="'3'" th:text="Március"></option>
<option th:value="'4'" th:text="Április"></option>
<option th:value="'5'" th:text="Május"></option>
<option th:value="'6'" th:text="Június"></option>
<option th:value="'7'" th:text="Július"></option>
<option th:value="'8'" th:text="Augusztus"></option>
<option th:value="'9'" th:text="Szeptember"></option>
<option th:value="'10'" th:text="Október"></option>
<option th:value="'11'" th:text="November"></option>
<option th:value="'12'" th:text="December"></option>
</select>
</div>
<div class="content-group">
<select th:field="*{listingId}">
<option th:each="listingDto : ${listingListDto}" th:value="${listingDto.airbnbId}" th:text="${#strings.abbreviate(listingDto.airbnbLabel,30)}"></option>
</select>
</div>
<div class="content-group">
<button type="submit" class="btn btn-primary btn-lg btn-block">Lekérés indítása</button>
</div>
</form>
</div>
</body>
Here is the ListingListDtoService. It has a way of screening for duplicates, so unless this is not doing what I think it is doing, no duplicates should be there from the result of running this service.
#Service
public class ListingListDTOService {
List<ListingDTO> listingDtoList;
public ListingListDTOService() {
this.listingDtoList = new ArrayList<>();
}
public List<ListingDTO> getListingListDTO(List<Listing> listingList) {
for(Listing listing : listingList) {
ListingDTO listingDto = convertListingToListingDTO(listing);
if(!listingDtoList.contains(listingDto)) {
listingDtoList.add(listingDto);
}
else {
System.out.println("identical Dto found in list while adding Dtos.");
}
}
return listingDtoList;
}
public ListingDTO convertListingToListingDTO(Listing listing) {
ListingDTO listingDto = new ListingDTO();
listingDto.setAirbnbId(listing.getAirbnbId());
listingDto.setAirbnbLabel(listing.getAirbnbLabel());
listingDto.setAirbnbPictureUrl(listing.getAirbnbPictureUrl());
return listingDto ;
}
}
Thanks to the coments from #Seth, this problem has been resolved.
Just copying from the comments to answer this.
If you don't have a good equals()/hashcode() method on ListingDTO, then your contains() call won't do what you want.

Pass a object from Select in Thyemleaf

I have the following class:
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "person_id")
private int personId;
#Column(name = "person_name", unique = true)
private String personName;
#Column(name = "gender")
private Gender personGender;
}
Here is Gender as well:
public class Gender{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "gender_id")
private int genderId;
#Column(name = "gender_name", unique = true)
private String genderName;
}
I've assembled a Thymelef dropdown menu when creating a new Person that looks like this:
#RequestMapping(value = ("/newPerson"), method = RequestMethod.GET)
public ModelAndView createPerson() {
ModelAndView model = new ModelAndView();
Person person= new Person ();
model.addObject("person", person);
List<Gender> genders= genderService.getAll();
model.addObject("genders", genders);
model.setViewName("user/newPerson");
return model;
}
(I realize it looks kinda dumb, but it is a simplified version of my code.)
And here is the HTML:
<form class="form-createNew" role="form" method="POST"
th:action="#{/newPerson}" th:object="${person}">
<div class="form-row">
<div class="col">
<div class="form-label-group">
<input type="text" th:field="*{personName}" id="personName" class="form-control" placeholder="Person name">
</div>
</div>
</div>
<div class="form-row">
<div class="col">
<div class="form-label-group">
<select th:field="*{personGender}" class="form-control"
id="personGender" name="personGender">
<option value="" selected disabled hidden>Select gender</option>
<option th:each="gender: ${genders}"
th:value="${gender.genderId}"
th:text="${gender.genderName}"></option>
</select>
</div>
</div>
</div>
<button id="registerBtn" class="btn btn-lg btn-primary btn-block shadow-none text-uppercase" type="submit"> Create </button>
</form>
And finally my question:
What I am receiving at the POST method in the controller for /newPerson is a Person object with the value from the input field, but NULL for the Gender. What is causing that and where am I wrong here? I went through similar questions on SO regarding that issue and also the Thymeleaf Docs/Baeldung and everything looks okay to me.
Any help is appreciated! :)
You are sending for your post controller something like this:
personGender: gernderId
You must do:
personGender.genderId : genderId
For that use in your html:
th:field="*{personGender.genderId}"
instead of
th:field="*{personGender}"
I think it will help you!

Problem with passing changed subobject in SPRING application via Thymeleaf template

I am new in SPING and web developing.
I am developing the test task - quote book (without authorizing). I have two entities: Quote and Author. There is the code:
#Entity
#Table(name="authors")
public class Author {
#Id
#SequenceGenerator(name="author_id_seq", sequenceName="author_id_seq", allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "author_id_seq")
#Column(name = "id")
private long id;
#Column(name = "nick_name")
private String nickName;
public Author() {
}
public Author(long id, String nickName) {
this.id = id;
nickName = nickName;
}
/*
Getters and Setters
*/
}
Quote:
#Entity
#Table(name = "quotes")
public class Quote {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "quotes_id_seq")
#SequenceGenerator(name="quotes_id_seq", sequenceName="quotes_id_seq", allocationSize=1)
#Column(name = "id")
private long id;
#Column(name = "content")
private String content;
// #Temporal(TemporalType.DATE)
#Column(name = "date")
private LocalDate date;
#ManyToOne
#JoinColumn(name = "author")
private Author author;
public Quote() {
}
public Quote(long id, String content, LocalDate date, Author author) {
this.id = id;
this.content = content;
this.date = date;
this.author = author;
}
/*
Getters and Setters
*/
}
My Thymeleaf template contains form with two input fields(for quote.content and for quote.author.nickName) and select with existing authors. I want to see the behavior, when i fill input for author, content and if author is not existing in authors table, my application add the row in this table with specified by value from input nickName and generated id. But the problem is in getting unexpected result for me from Thymeleaf template. Template pass to controller the Quote object with author, which nickname is null instead of value from my input. There is thymeleaf template code of my form:
<form action="#" th:action="#{/newQuote}" th:object="${quote}" method="post">
<div class="form-row">
<div class="form-group col-md-3">
<label class="sr-only" for="inputNick">Nick Name</label>
<!--/*#thymesVar id="author" type="hello.entity.Author"*/-->
<input type="text" class="form-control" id="inputNick" th:field="*{author.nickName}"
placeholder="enter Nick Name">
</div>
<div class="form-group col-md-6">
<select id="nickNames">
<th:block th:each="author : ${allAuthors}">
<option th:text="${author.nickName}">Nick Name</option>
</th:block>
</select>
</div>
<div class="form-group col-md-12">
<label for="postContent">Your Quote:</label>
<input type="text" class="form-control" id="postContent" th:field="*{content}"
placeholder="quote text">
</div>
</div>
<div class="form-group col-md-2">
<input type="submit" value="Add Quote"/>
</div>
<div class="form-group col-md-2">
<input type="reset" value="Reset"/>
</div>
</form>
Controller methods:
#GetMapping("/newQuote")
public String showAuthors(Model model){
model.addAttribute("allAuthors",authorService.findAll());
model.addAttribute("quote", new Quote());
return "newQuote";
}
#PostMapping("/newQuote")
public String addQuote (#ModelAttribute Quote quote) {
quote.setDate(LocalDate.now());
quoteRepository.save(quote);
return "redirect:/";
I've tried:
to add Author object in GetMapping, pass it in newQuote template,
pass from it to PostMapping - no effect. Null in nickname.
to create and insert author object in quote object, pass to
template, pass to postmapping. no effect
I know that i can create DTO class with field nickname insteadof author field and convert it into my entity class in postmapping method of controller. But i think that it is "bad practice" way. I think, that i made wrong steps,may be, when tried to change author object and pass it from thymeleaf to controller. And also i suppose, that there is no way to realize this logic in this situation. i dont know where is the truth. Please, help me in finding it.
P.S: sorry for my bad english
So, the answer is asking how to add a quote and it's author at the same time. With a select approach is impossible, since our author doesn't exists. And simple way to achieve this, is changing your quote entity and adding #Cascade(CascadeType.PERSIST) to the author field in quote. This will automatically persist the author with your quote.
These would be the changes to your code that you would required to accomplish this.
Update
Since, the last solution didn't work, we will try sending an additional parameter to our controller, to avoid receiving a null, instead of the author's nickname.
Controller
#PostMapping("/newQuote")
public String addQuote (#ModelAttribute Quote quote,
#RequestParam("nickName") String nickName) {
// This will automatically persist your quote and it's author.
quote.setDate(LocalDate.now());
Author author = new Author();
author.setNickName(nickName);
quoteRepository.save(quote);
}
Quote Entity
#Entity
#Table(name = "quotes")
public class Quote {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "quotes_id_seq")
#SequenceGenerator(name="quotes_id_seq", sequenceName="quotes_id_seq", allocationSize=1)
#Column(name = "id")
private long id;
#Column(name = "content")
private String content;
#Column(name = "date")
private LocalDate date;
#ManyToOne
#Cascade(CascadeType.PERSIST)
#JoinColumn(name = "author")
private Author author;
// Getters and Setters
}
Important, make sure to notice that we are adding a new annotation #Cascade(CascadeType.PERSIST) to our author field.
HTML
Here we will simply remove the unnecessary select.
<form action="#" th:action="#{/newQuote}" th:object="${quote}" method="post">
<div class="form-row">
<div class="form-group col-md-3">
<label class="sr-only" for="inputNick">Nick Name</label>
<!--/*#thymesVar id="author" type="hello.entity.Author"*/-->
<input type="text" name="nickName" class="form-control" id="inputNick" placeholder="enter Nick Name"/>
</div>
<div class="form-group col-md-12">
<label for="postContent">Your Quote:</label>
<input type="text" class="form-control" id="postContent" th:field="*{content}" placeholder="quote text"/>
</div>
</div>
<div class="form-group col-md-2">
<input type="submit" value="Add Quote"/>
</div>
<div class="form-group col-md-2">
<input type="reset" value="Reset"/>
</div>
</form>

Resources