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

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.

Related

Spring Boot Thymeleaf Method POST SELECT null

Guys.
Tell me please how to get values from select in the Controller?
This returns null.
request.getParameter("firstUserYears");
request.getParameter("secondUserYears");
I want to get value of "birthDay" field.
my thymeleaf html form:
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form method="post" action="/calculate_years">
<select name="firstUserYears" th:field="*{users}"/>
<option th:each="user : ${users}" th:value="firstUserYears" th:text="${user.firstName}">
</option>
</select>
<select name="secondUserYears" th:field="*{users}"/>
<option th:each="user : ${users}" th:value="${user.firstName}" th:text="${user.firstName}">
</option>
</select>
<button type="submit">Submit</button>
</form>
</body>
</html>
Controller:
#GetMapping("/user-difference")
public String calculateDifferenceForm(Model model) {
model.addAttribute("users", service.findAll());
return "user-difference";
}
#PostMapping("/calculate_years")
public String calculateDifferenceForm(HttpServletRequest request, #ModelAttribute User user,
BindingResult bindingResult){
String firstUsersYearsOld = request.getParameter("firstUserYears");
String secondUsersYearsOld = request.getParameter("secondUserYears");
String name = request.getParameter("name");
BindingResult results = bindingResult;
System.out.println(results);
System.out.println(user);
System.out.println(name);
System.out.println(firstUsersYearsOld);
System.out.println(secondUsersYearsOld);
return "redirect:/user-difference";
}
You should start with defining a form data object. For example:
public class AgeDifferenceFormData {
private long user1Id;
private long user2Id;
// getters and setters here
}
Create an empty such object in your #GetMapping:
#GetMapping("/user-difference")
public String calculateDifferenceForm(Model model) {
model.addAttribute("formData", new AgeDifferenceFormData());
model.addAttribute("users", service.findAll());
return "user-difference";
}
Now update your HTML form to use the form data object:
<form method="post" action="/calculate_years" th:object="${formData}">
<select th:field="*{user1Id}"/>
<option th:each="user : ${users}" th:value="${user.id}" th:text="${user.firstName}">
</option>
</select>
<select th:field="*{user2Id}"/>
<option th:each="user : ${users}" th:value="${user.id}" th:text="${user.firstName}">
</option>
</select>
<button type="submit">Submit</button>
</form>
Note how you need to:
Set a selected object for Thymeleaf via th:object="${formData}"
Set the dedicated field for each select via th:field="*{user1Id}" and th:field="*{user2Id}"
Use the unique id of the user for the th:value.
Now in your #PostMapping method do this:
#PostMapping("/calculate_years")
public String calculateDifferenceForm(#ModelAttribute("formData") AgeDifferenceFormData formData,
BindingResult bindingResult){
User user1 = service.getUser(formData.getUser1Id());
User user2 = service.getUser(formData.getUser2Id());
// calculate age difference here
return "redirect:/user-difference";
}
See also Using HTML select options with Thymeleaf for more info.

Get DropDownList value into POST method

I am working on this ASP.NET Core MVC where I have this DropDownLisit which gets its values from Controller using ViewBag.DishTypes. However, upon submitting the form, the POST method is not getting the value of the option selected in the DropDownList. The code snippets are as follows:
Controller: GET Method
var allDishTypes = _context.DishType
.ToList()
.Select(dt => new SelectListItem { Value = dt.DishTypeId.ToString(), Text = dt.DishTypeName.ToString() }).ToList();
ViewBag.DishTypes = allDishTypes;
return View();
View
<form asp-controller="Home" asp-action="AddMenuItems">
<div class="row">
<label class="my-1 mr-2" for="inlineFormCustomSelectPref">Dish Type</label>
<div class="input-group">
<div class="fg-line form-chose">
<label asp-for="DishTypeId" class="fg-labels" for="DishTypeId">Dish Type</label>
<select asp-for="DishTypeId" asp-items="ViewBag.DishTypes" class="form-control chosen" data-placeholder="Choose Dish Type" required name="dishtype" id="dishtype">
<option value=""></option>
</select>
</div>
</div>
....
Controller: POST Method
[HttpPost]
public IActionResult AddMenuItems([Bind("DishTypeId, DishName, Cost")] Dishes dishesObj)
{
....
}
the POST method is not getting the value of the option selected in the DropDownList
Note that you specified a name=dishtype within your code. By this way, the field name is
always the same as this name attribute, i.e, dishtype instead of DishTypeId, which will not be recognized by ASP.NET Core by default.
To fix that issue, simply remove that attribute such that it uses asp-for to generate the name attribute automatically:
<select asp-for="DishTypeId" asp-items="ViewBag.DishTypes"
class="form-control chosen" data-placeholder="Choose Dish Type" required
name="dishtype" id="dishtype"
>
<option value=""></option>
</select>

Property [kodeSparepart] does not exist on this collection instance

I want to get all of the data from sparepart database using all() function, then use foreach in view to access the data, but I keep get that error. It works fine when I use the same method for other view blade.
Controller
public function LaporanSisaStok(Request $request) {
if($request->kode == "")
{
$spareparts = Sparepart::all();
return view('laporan/sisaStok')->with(['spareparts' => $spareparts]);
}
else {
$query = DB::table("historisparepart")->select(DB::raw('EXTRACT(MONTH FROM tanggal) AS Bulan, SUM(jumlah) as Sisa'))
->where('kodeSparepart', $request->kode)
->groupBy(DB::raw('EXTRACT(MONTH FROM tanggal)'))
->get();
return view('printPreview/sisaStok', ['data'=>$query]);
}
}
View
<form method="POST" action="{{ route('laporan.sisaStok') }}" enctype="multipart/form-data">
#csrf
<div class="form-group-row">
<label for="sparepart" class="col-sm-2 col-form-label">Sparepart</label>
<select class="custom-select" id="kode" name="kode">
<option value="">-Pilih Sparepart-</option>
foreach($spareparts as $sparepart)
{
<option value="{{$sparepart->kodeSparepart}}"> {{$sparepart->namaSparepart}} </option>
}
</select>
</div>
<br>
<button type="submit" class="btn btn-info"><i class="oi oi-task"></i> Cari </button>
You aren't looping through anything. You need to give the foreach() method a variable from your spareparts collection. I think it might help you avoid confusion, to name the collection variables plural:
In your controller:
$spareparts = Sparepart::all();
return view('laporan/sisaStok', compact('spareparts'));
Then, most importantly, you need to tell foreach what it should produce. In your view, change:
foreach($sparepart)
to
#foreach($spareparts as $sparepart)
Don't forget you are in blade, so use the # before the foreach. Then, assuming you actually have a property on the spareparts model called kodeSparepart, this should work fine.
This is not a looping syntax in the laravel view
foreach($spareparts as $sparepart)
{
<option value="{{$sparepart->kodeSparepart}}"> {{$sparepart->namaSparepart}} </option>
}
It should be like this
#foreach($spareparts as $sparepart)
<option value="{{$sparepart->kodeSparepart}}"> {{$sparepart->namaSparepart}} </option>
#endforeach
And another problem is you are passing the data in wrong way. It should be like this
return view('printPreview/sisaStok', ['spareparts'=>$query]);
You shoud use the plural of your dtb name to loop over the values so your 'sparepart' variable should change to 'spareparts'
$sparepart = Sparepart::all();
return view('laporan/sisaStok')->with(['spareparts' => $sparepart]);
In your view change your loop to the new variable and loop using your current variable, so your view shoul look like this:
View
<div class="form-group-row">
<label for="sparepart" class="col-sm-2 col-form-label">Sparepart</label>
<select class="custom-select" id="kode" name="kode">
<option value="">-Pilih Sparepart-</option>
#foreach($spareparts as $sparepart)
{
<option value="{{$sparepart->kodeSparepart}}"> {{$sparepart->namaSparepart}} </option>
}
#endforeach
</select>
</div>

thymeleaf how to pass many objects?

how to pass many objects in one form? becouse like that not working.... help me please
<form class="form-control" action="#" th:action="#{'/'}" method="post">
<select>
<option th:each="city :${cities}" th:value="${city.getId()}" th:text="${city.getName()}"/>
</select>
<select>
<option th:each="category :${categories}" th:value="${category.getId()}" th:text="${category.getDescirption()}">
</option>
</select>
//this line isnt correct:
<a class="btn btn btn-primary btn-sm" href="#"
th:href="#{'/todo/done?id=' + ${city.id} + '&name=' + ${category.name}}">done</a>
</form>
You add each object as named model attribute. Example
#RequestMapping(value = "message", method = RequestMethod.GET)
public String messages(Model model) {
model.addAttribute("cities", citiesRepository.getAll());
model.addAttribute("categories", citiesRepository.getAll());
return "message/list";
}
Personally I find it more readable to have single object as model attribute. That object has fields for each actual attribute.
See official documentation for more details https://www.thymeleaf.org/doc/articles/springmvcaccessdata.html
In your example you are not using th:each correctly.
You should have th:each as select level (not at option level) then refer to individual items in nested select elements.
like
<select th:each="city :${cities}">
<option th:value="${city.getId()}" th:text="${city.getName()}"/>
</select>
In your last example you are refering to ${city.id} and ${category.name} but city and category are not defined (probably thymleaf error message tells you that).
What exactly you want to achieve? Do you want to link with selected city and category? If so, you need separate model attributes like
selectedCity and selectedCatetory.

Pass dropdown text to controller

I have below dropdown, how do I retrieve the dropdown selected dropdown text (not value) on MVC controller upon clicking the submit button (httppost)?
<select id="detailThing" name="MyList">
<option value="BMI">ListDetail1</option>
<option value="BMI">ListDetail2</option>
<option value="BMI">ListDetail3</option>
</select>
Put the <select> in a form and submit it to the controller. You will need a model with a string variable to pass the value into/through.
public class MyModel
{
public String myValue { get; set; }
}
in the view put this line at the top;
#model MyProject.Models.MyModel
then create an html form and put your select inside and create a submit button;
#using (Html.BeginForm("MyControllerMethod", "MyController", FormMethod.Post, new { id = "myform" }))
{
<select id="detailThing" name="myValue">
<option value="BMI">Putrajaya</option>
<option value="BMI">Sepang</option>
<option value="BMI">Hulu Langat</option>
</select>
<button type="submit">Submit</button>
}
setting the 'name' of the select to 'myValue' will link its 'selected' value to the variable on the model and pass it to the controller when the form is submitted. Hope this helps!
Update:
change the values to be the same as the display text,
<select id="detailThing" name="myValue">
<option value="Putrajaya">Putrajaya</option>
<option value="Sepang">Sepang</option>
<option value="Hulu Langat">Hulu Langat</option>
</select>

Resources