I'm curious to find a solution for this but couldn't find anything relatable and useful so far.
Lets say user have two transactions for 18/01/2023
I want to have a heading for example like separate <div> and to pass today date and each transaction from today, and below down for example 15/01/2023 because user also have a transactions on that date.
This is an example, as you can see I have today section because user made a transaction on today date, also for yesterday and then back on January 14, so I want to separate transactions for each that like that. Example
This is Transaction class:
#Entity
#Table(name = "transaction")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "transaction_id")
private Long id;
#Column(name = "user_id", nullable = false)
private Long userId;
#Column(name = "wallet_name", nullable = false)
private String walletName;
#NotNull(message = "Please, insert a amount")
#Min(value = 0, message = "Please, insert a positive amount")
private Double amount;
private String note;
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Column(name = "date")
private LocalDate date;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "wallet_id", nullable = false)
private Wallet wallet;
#Enumerated(EnumType.STRING)
#Column(name = "transaction_type", columnDefinition = "ENUM('EXPENSE', 'INCOME')")
private TransactionType transactionType;
#Nullable
#Enumerated(EnumType.STRING)
#Column(name = "expense_categories", columnDefinition = "ENUM('FOOD_AND_DRINK', 'SHOPPING', 'TRANSPORT', 'HOME'," +
" 'BILLS_AND_FEES', 'ENTERTAINMENT', 'CAR', 'TRAVEL', 'FAMILY_AND_PERSONAL', 'HEALTHCARE'," +
" 'EDUCATION', 'GROCERIES', 'GIFTS', 'BEAUTY', 'WORK', 'SPORTS_AND_HOBBIES', 'OTHER')")
private ExpenseCategories expenseCategories;
#Nullable
#Enumerated(EnumType.STRING)
#Column(name = "income_categories", columnDefinition = "ENUM('SALARY', 'BUSINESS', 'GIFTS', 'EXTRA_INCOME', 'LOAN', 'PARENTAL_LEAVE', 'INSURANCE_PAYOUT', 'OTHER')")
private IncomeCategories incomeCategories;
I created a new class that will act as group and that is like this:
class TransactionGroup {
private LocalDate date;
private List<Transaction> transactions;
/* Getters and setters */
}
And Thymeleaf:
<div th:each="singleGroup : ${transactionGroup}">
<h1 th:text="${singleGroup .date}"></h1>
<div th:each="singleTrans : ${singleGroup.transactions}">
<h2>Amount: <span th:text="${singleTrans .amount}"></span></h2><br>
<h2>Note: <span th:text="${singleTrans .note}"></span></h2><br>
<h2>Wallet name: <span th:text="${singleTrans .walletName}"></span></h2><br>
<h2>Expense Category: <span th:text="${singleTrans .expenseCategories}"></span></h2><br>
<h2>IncomeCategory: <span th:text="${singleTrans .incomeCategories}"></span></h2>
<div>
</div>
</div>
</div>
And this is controller:
#GetMapping("/userTransactions/{user_id}")
public String getUserTransactions(#PathVariable("user_id") long user_id, TransactionGroup transactionGroup, Model model) {
List<Transaction> transactions = transactionRepository.getTransactionsByUserId(user_id);
//create a TransactionGroup list
List<TransactionGroup> transactionByDate = new ArrayList<>();
//create a list that will hold all transactions for a day
List<Transaction> transOnSingleDate = new ArrayList<>();
//initialize currDate with the first transaction date
LocalDate currDate = transactions.get(0).getDate();
//create a TransactionGroup
TransactionGroup transGroup = new TransactionGroup();
//loop through your transactions and populate the wrapper list
for(Transaction t : transactions){
//create a new transaction group if the date has changed
if(!currDate.isEqual(t.getDate())){
//fill the wrapper list before creating a new list
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
//create new TransactionGroup and List<Transaction> for a new date
transGroup = new TransactionGroup();
transOnSingleDate = new ArrayList<>();
}
transOnSingleDate.add( t );
currDate = t.getDate();
}
//add the final list
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
model.addAttribute("transactionGroup", transactionByDate);
return "transactions";
}
You can see that I populate here transaction list in TransactionGroup by transactions from entity Transaction:
transactionGroup.setTransactions(transactionService.findDistinctIdByUserId(userId));
And on page I can see transactions, but I cant see a date how I want, I cant even see date, date is not displayed, because I didn't populate a date field on class TransactionGroup with date from Transaction class. How I can get a transaction date from each transaction that is created?
I guess I need to populate it somehow like transactionGroup.setTransactions... but now like transactionGroup.setDate... but Date is not a list type, so there is a problem.
Its obivous that if I try with transactionGroup.setDate(transaction.getDate); returning null
SOUT LOGS:
transGroup TransactionGroup{date=2023-01-01, transactions=null}
transactionByDate [TransactionGroup{date=2023-03-01, transactions=[Transaction{id=18, userId=1, walletName='Dailyk', amount=4.0, note='Cetvrta transakcija', date=2023-03-01, wallet=com.budgettracker.demo.userProfile.models.Wallet#68e4f813, transactionType=INCOME, expenseCategories=null, incomeCategories=BUSINESS}]}, TransactionGroup{date=2023-02-01, transactions=[Transaction{id=17, userId=1, walletName='Dailyk', amount=3.0, note='Treca transakcija', date=2023-02-01, wallet=com.budgettracker.demo.userProfile.models.Wallet#68e4f813, transactionType=INCOME, expenseCategories=null, incomeCategories=EXTRA_INCOME}]}, TransactionGroup{date=2023-01-01, transactions=[Transaction{id=15, userId=1, walletName='Dailyk', amount=1.0, note='Prva transkacija', date=2023-01-01, wallet=com.budgettracker.demo.userProfile.models.Wallet#68e4f813, transactionType=INCOME, expenseCategories=null, incomeCategories=SALARY}, Transaction{id=16, userId=1, walletName='Dailyk', amount=2.0, note='Druga transkacija', date=2023-01-01, wallet=com.budgettracker.demo.userProfile.models.Wallet#68e4f813, transactionType=INCOME, expenseCategories=null, incomeCategories=GIFTS}]}]
This can be done in many ways. For instance, you can define a wrapper list ( List<List<Transaction>> or List<TransactionGroup>) where the contained list(s) will contain all the transactions on a given day. It would also make sense to create a native query to retrieve the transactions for a particular user sorted by date.
//your Transaction repository
#Query( value="select * from transaction where user_id = ?1 order by date desc", nativeQuery=true)
List<Transaction> getTransactionsByUserId(Integer userId);
The logic in your controller could then look something like
....
//this list holds all the transactions for a particular user
List<Transaction> transactions = transRepository.getTransactionsByUserId(userId);
//create the wrapper list
List<List<Transaction>> transactionByDate = new ArrayList<>();
//initialize currDate with the first transaction date
LocalDate currDate = transactions.get(0).getDate();
//create a list that will hold transactions for a single date
List<Transaction> transOnSingleDate = new ArrayList<>();
//loop through your transactions and populate the wrapper list
for(Transaction t : transactions){
//create a new list of transactions if the date has changed
if(!currDate.isEqual(t.getDate()){
//fill the wrapper list before creating a new list
transactionByDate.add(transOnSingleDate);
transOnSingleDate = new ArrayList<>();
}
transOnSingleDate.add( t );
currDate = t.getDate();
}
//add the final list
transactionByDate.add(transOnSingleDate);
model.addAtrribute("transByDate", transactionByDate);
or using List<TransactionGroup>
....
//this list holds all the transactions for a particular user
List<Transaction> transactions = transRepository.getTransactionsByUserId(userId);
//create a TransactionGroup list
List<TransactionGroup> transactionByDate = new ArrayList<>();
//create a list that will hold all transactions for a day
List<Transaction> transOnSingleDate = new ArrayList<>();
//initialize currDate with the first transaction date
LocalDate currDate = transactions.get(0).getDate();
//create a TransactionGroup
TransactionGroup transGroup = new TransactionGroup();
//loop through your transactions and populate the wrapper list
for(Transaction t : transactions){
//create a new transaction group if the date has changed
if(!currDate.isEqual(t.getDate()){
//fill the wrapper list before creating a new list
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
//create new TransactionGroup and List<Transaction> for a new date
transGroup = new TransactionGroup();
transOnSingleDate = new ArrayList<>();
}
transOnSingleDay.add( t );
currDate = t.getDate();
}
//add the final list
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
model.addAtrribute("transactionGroup", transactionByDate);
Hope this helps.
UPDATE:
The controller part is now ok. The List<TransactionGroup> object contains 3 transaction groups, one for each date. The thymeleaf template is wrong. It should be something like
<div th:each="singleGroup : ${transactionGroup}">
<div th:each="singleTrans : ${singleGroup.transactions}">
<h2>Amount: <span th:text="${singleTrans.amount}"></span></h2><br>
<h2>Note: <span th:text="${singleTrans.note}"></span></h2><br>
<h2>Wallet name: <span th:text="${singleTrans.walletName}"></span></h2><br>
<h2>Expense Category: <span th:text="${singleTrans.expenseCategories}"></span></h2><br>
<h2>IncomeCategory: <span th:text="${singleTrans.incomeCategories}"></span></h2>
<h2>IncomeCategory: <span th:text="${singleTrans.date}"></span></h2>
<div>
</div>
I think you don't need TransactionGroup as a intermediate class, just store transaction by Transaction entity, and you can get query by userId and return list of transactions, and from this list you can get all distinct dates,
If the specific user want to see all transactions in specific date you can define a query by two parameters with userId and Date from transaction repository.
So it can be better define a method with userId and Date(can be nullable) and return list of transactions.
hey so I wanted to have a form:select that looks like this so far:
<form:select id="constants" path="constant" class="form-control chosen-select" size="2"
items="${constants}" itemValue="id" itemLabel="description" multiple="true" />
I retrieve constants from the db, they are 4/5 rows of constant values. I wanted to do such that if I select "Other" a form:input/ below pops up/becomes active and usable
so at the end onSubmit I just send the optional form:input value that the user writes manually. How do I achieve that? I've been trying to use jquery but I end up not sending the right data as I really cannot "turn off" the <form:select /> and "turn on" the <form:input />. Hope you can help me out!
This is the Constant Entity:
#Entity
#Table(name = "constant", schema = BaseEntity.SCHEMA_NAME)
public class Constant {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_constant")
#SequenceGenerator(name = "seq_constant", sequenceName = "seq_constant", allocationSize = 1)
#Column(name = "ID_CONSTANT")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
}
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
I have 2 Entity classes the "Menu" which only has one field called "name" and second Entity - "Ingredients" which has 2 fields - "ingredientName" and "ingredientDescription". Database Structure
I'm creating a simple CRUD web-app , but the update method instead of updating the Entity , it inserts new values in the DB. I checked and when user clicks on the update on specified menu, the first entity's id and its ingredients id's as well are predifined. Im new to spring boot and thymeleaf and Don't really know how to work with JPA when you have more than 1 entity.
Menu entity :
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#Column(name = "name")
private String name;
// Mapping To second table
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "menu_ingredient",
joinColumns = #JoinColumn(name = "menu_id"),
inverseJoinColumns = #JoinColumn(name = "ingredient_id"))
private List<Ingredients> ingredient = new ArrayList<>();
//Getters/Setters/Constructors/ToString
Ingredients entity :
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "ingredient")
private String ingredientName;
#Column(name = "description")
private String ingredientDescription;
//Getters/Setters/Constructors/ToString
Controller(Only the update methods) :
#GetMapping("/edit/{id}")
public String edit(#PathVariable(name = "id")String id, Model model){
Optional<Menu> menu = menuRepository.findById(id);
List<Ingredients> ingredients = menu.get().getIngredient();
for (Ingredients ing : ingredients){
System.out.println(ing);
}
model.addAttribute("ingredients", ingredients);
model.addAttribute("newMenu",menu);
return "edit-page";
}
#PostMapping("/postEditMenu")
public String postEdit(#ModelAttribute("newMenu")Menu menu){
menuRepository.save(menu);
return "redirect:/recipeList";
}
edit-page.html :
<form action = "#" th:action="#{/postEditMenu}" th:object="${newMenu}" method="post">
<p>Menu Name: <br><input type="text" th:field="*{name}"></p>
<div id="wrapper" th:each="ing: ${ingredients}">
<label for="ingredientName"></label>
<p>Ingredient Name: <br><input th:value="${ing.ingredientName}" id="ingredientName" type="text" name="ingName"></p>
<label for="ingredientDescription"></label>
<p>Ingredient Description:</p> <textarea id="ingredientDescription" type="text" th:text="${ing.ingredientDescription}" name="ingDesc"></textarea>
</div>
<br>
<input type="button" id="more_fields" onclick="add_fields();" value="Add More" />
<br>
<input type="submit" th:value="Submit">
</form>
FIX I actually figured it out with the help of below answers. Here's the code :
#PostMapping("/postEditMenu")
public String postEdit(#ModelAttribute("newMenu")Menu menu,
#RequestParam String ingName,
#RequestParam String ingDesc){
String[] ingNameSplit = ingName.split(",");
String[] ingDescSplit = ingDesc.split(",");
Menu menuToUpdate = menuRepository.getOne(menu.getId());
List<Ingredients> newIngredientList = menuToUpdate.getIngredient();
newIngredientList.clear();
for(int a = 0, b = 0; a<ingNameSplit.length; b++, a++){
newIngredientList.add(new Ingredients(ingNameSplit[a], ingDescSplit[b]));
}
menuToUpdate.setIngredient(newIngredientList);
menuRepository.save(menuToUpdate);
return "redirect:/recipeList";
}
So, First I added hidden "id" fields to each of the items required , like this :
<input type="text" th:field = "*{id}" hidden>
and
<input type="text" th:value = "${ing.id}" hidden>
Then, in the postEditMenu method, I added #RequestParam String ingName, and #RequestParam String ingDesc to get the input of new items from thymeleaf, then I split that String and add it to String[] array with String[] ingNameSplit = ingName.split(",") Because the input would be one big comma separated String and not array[] . Then I get the menu which user wants to update - Menu menuToUpdate = menuRepository.getOne(menu.getId()); The menu.getId() isn't null because I set hidden "id" fields in thymeleaf. Then I get the Ingredients of this menu - List<Ingredients> newIngredientList = menuToUpdate.getIngredient(); because the list would already be filled with existed ingredients I clear that list and add new ingredients which user will fill the form with -
for(int a = 0, b = 0; a<ingNameSplit.length; b++, a++){
newIngredientList.add(new Ingredients(ingNameSplit[a], ingDescSplit[b]));
}
after that I set this newIngredientsList and save the menu itself to the db -
menuToUpdate.setIngredient(newIngredientList);
menuRepository.save(menuToUpdate);
Thanks for all the help guys :)
At this point:
#PostMapping("/postEditMenu")
public String postEdit(#ModelAttribute("newMenu")Menu menu){
menuRepository.save(menu);
return "redirect:/recipeList";
}
You receive menu from edit-page.html and it has no id, that is why it always creates new records in database.
To edit the desired menu, you would need to have it's id before.
You can create endpoint for obtaining list of menus and display them with edit button next to each menu in html site. Then if user clicks edit button redirect him to your edit form, but this time you can pass menu's id.
First you need to fetch Menu entity from database by id using getOne and then you can update it.
Edit your code in postEdit method as follows:
Fetch Menu entity:
Menu menuToUpdate = menuRepository.getOne(menu.getId());
Update attributes:
menuToUpdate.setName(menu.getName());
Save entity:
menuRepository.save(menuToUpdate);
Add a hidden id field to hold the menu's id, and add hidden id fields for each ingredient.
I have two models, Patient and Study. In the Study model, I want to use Patient's Id as a foreign key. My Study Model (without getter/setter) is as below
#Entity
#Table(name = "Study")
public class Study {
#Id
#Column(name = "study_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#ManyToOne
#JoinColumn(name = "patient_id")
private Patient patient;
#NotNull
#Column(name = "description")
private String description;
#NotNull
#Column(name = "status")
private String status;
}
EDIT : Adding Patient class (without getter/setter) as well
#Entity
#Table(name="Patient")
public class Patient {
#Id
#Column(name="patient_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#NotNull
#Column(name="name")
private String name;
#Column(name="sex")
private String sex;
#Column(name="date_of_birth")
private Date dateOfBirth;
}
I am using Thymeleaf and selection of patient in my scheduleStudy.html is shown below
<form method="POST" th:action="#{/scheduleStudy}" th:object="${study}">
<p>
Select Patient<select th:field="*{patient}"
required="required" class="form-control">
<option th:each="patient: ${patientList}" th:value="${patient.id}"
th:text="${patient.name}"></option>
</select>
</p>
The form is loading successfully with list of Patients in dropdown. However, when I am submitting the form after filling out all the fields, I am receiving:
Validation failed for object='study'. Error count: 1 error on browser.
Also the controller entries for Study form
#GetMapping("/scheduleStudy")
public String addSchedule(Model model) {
List<Patient> patientList = patientService.getPatients();
model.addAttribute("patientList", patientList);
model.addAttribute("study", new Study());
return "scheduleStudy";
}
#PostMapping("/scheduleStudy")
public void processAddSchedule(#ModelAttribute Study study) {
studyService.addStudy(study);
}
I have just started using Thymeleaf and I think I have dome some mistake in the patient field. Could anyone please help?
EDIT 2: I have updated the method for POST request, but patient is still null in controller. The previous browser error is gone of course.
#RequestMapping(value = "/scheduleStudy", method = RequestMethod.POST)
public String processAddSchedule(Model model, #Valid #ModelAttribute("study") Study study,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
} else {
studyService.addStudy(study);
}
}
I was working on a similar task. As far as I learned when you post the study form Thymeleaf sends all fields as parameters to a POST request. It converts the Patient object to a String with toString(). Then when assembling the Study object it must convert the Patient back to the Object form. I solved this by registering 2 converters (toString and fromString) with the standard conversionService for Patient.