How do i pass two th:object in a single form - java

I'm new to thymeleaf and couldn't found a way to pass two th:objects in a single HTML form. how can I pass two different entity objects.in this case, my entities are Patient and Doctor. My form is given below.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<title>LOG IN PAGE</title>
</head>
<body>
<div class="container">
<form th:action="#{/loggedProfile}" th:object="${patient,doctor}"
method="get">
<div class="form-group">
<label for="patientEmail">Email address</label> <input type="email"
class="form-control" placeholder="Enter Your email"
th:field="*{eMail}"> <small id="emailHelp"
class="form-text text-muted">We'll never share your email
with anyone else.</small>
</div>
<div class="form-group">
<label for="patientPassword">Password</label> <input type="password"
class="form-control" placeholder="Enter Your Password"
th:field="*{password}">
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</body>
<div th:insert="Header-Footer/common_header_footer"></div>
</html>
Here is my controller, its raw.
#GetMapping("/logIn")
public String logIn(Model model) {
Patient patient = new Patient();
model.addAttribute("patient", patient);
Doctor doctor = new Doctor();
model.addAttribute("doctor", doctor);
return "UI-Pages/LogIn_Page";
}
#GetMapping("/loggedProfile")
public String loggedProfile(#ModelAttribute Doctor doctor,#ModelAttribute Patient patient,Model model) {
doctor = docRep.findByeMailAndPassword(doctor.geteMail(), doctor.getPassword());
patient = patRep.findByeMailAndPassword(patient.geteMail(), patient.getPassword());
model.addAttribute("doctor", doctor);
model.addAttribute("patient", patient);
return "Profile-Pages/Patient_Profile";
}
Thanks.

You create a new object that contains a Patient and a Doctor.
public class LoginForm {
private Patient patient;
private Doctor doctor;
// getters and setters
}
Then access the fields like this:
<form th:action="#{/loggedProfile}" th:object="${form}"
th:field="*{patient.eMail}"
....
th:field="*{doctor.password}"
</form>
Etc...

It is probably better to use a dedicated Data Transfer Object that maps to the fields in the form data:
public class LoginFormData {
private String email;
private String password;
}
Then transform from the Patient or Doctor entity into LoginFormData and back in your controller.
Side note:
Use th:method="post" in the form with a #PostMapping in the controller if it is the intention to use the form to change data
Having a method findByeMailAndPassword is not something that you should have. Passwords should be in the database in plain text, and it would be strange to try to find a certain user with its plain text password.

Related

Edit data in the database

I want to edit exist data from database via html-form with field. But I can not create a right controller for it, because this code just created a new book. Old data was not changed.
Controller
#GetMapping("/bookUpdate/{id}")
public String bookListUpdate(#PathVariable (value = "id") Integer id, Model model, #Valid
BookDto book) {
model.addAttribute("book", service.findById(id));
return "views/bookUpdate";
}
#PostMapping("/edit")
public String editBook(#Valid Book book, #PathVariable (value = "id") Integer id) {
Book newBook = service.findById(id);
newBook.setDescription(book.getDescription());
newBook.setTopic(book.getTopic());
newBook.setLink(book.getLink());
service.saveBook(newBook);
return "views/index";
}
BookUpdate
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
layout:decorate="~{fragments/main_layout}">
<head>
<title>Book Form</title>
</head>
<body>
<div layout:fragment="content" class="container mySpace">
<form th:action="#{/edit}" th:object="${book}" method="post">
<div class="form-group">
<label for="topic" class="form-control-label">Topic</label> <input
type="text" class="form-control" th:field="*{topic}" id="topic" />
</div>
<div class="form-group">
<label for="description" class="form-control-label">Description</label>
<textarea class="form-control" th:field="*{description}"
id="description" style="height: 95px"></textarea>
</div>
<div class="form-group">
<label for="link" class="form-control-label">Link</label><a href=""> <input
type="text" class="form-control" th:field="*{link}" id="link" />
</div>
<input type="submit" value="Submit" class="btn btn-primary" />
</form>
</div>
</body>
</html>
Your #PostMapping is missing the path variable:
#PostMapping("/edit")
Do something like:
#PostMapping("/edit/{id}")
On a side note, you can make your URLs a bit nicer, by using something like #GetMapping("/books/{id}") and #PostMapping("/books/{id}").

Request method 'POST' not supported. Spring+Thymeleaf

**Hi everyone. I ran into a problem while developing a web application. The saveEmployee method does not work properly and throws the corresponding error. In the project I use Spring-boot + Thymeleaf. I think there is an error in these two files or a problem in the configuration. But so far I haven't found anything.
**
myRestController.java
#Controller
#RequestMapping("/shop")
public class myRestController {
#Autowired
private EmployeeService employeeService;
#GetMapping("/allEmployees")
public String allEmployees(Model model) {
List<Employee> employees = employeeService.getAllEmployees();
model.addAttribute("employeesList", employees);
return "allEmployees";
}
#GetMapping("/allEmployees/{name}")
public String getEmployeeByName(#PathVariable String name, Model model) {
List<Employee> employees = employeeService.findAllByName(name);
model.addAttribute("employeesList", employees);
return "allEmployees";
}
#GetMapping("/newEmployee")
public String addEmployee(Model model) {
Employee employee = new Employee();
model.addAttribute("employee", employee);
return "addNewEmployee";
}
#RequestMapping()
public String saveEmployee(#ModelAttribute("employee") Employee employee){
employeeService.saveEmployee(employee);
return "index";
}
addNewEmployee.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Add Employee</h1>
<form th:method="POST" th:action="#{shop/allEmployees}" th:object="${employee}">
<label for="name">Enter name:</label>
<input type="text" th:field="*{name}" id="name"/>
<br/>
<label for="surname">Enter surname:</label>
<input type="text" th:field="*{surname}" id="surname"/>
<br/>
<label for="department">Enter department:</label>
<input type="text" th:field="*{department}" id="department"/>
<br/>
<label for="salary">Enter salary:</label>
<input type="text" th:field="*{salary}" id="salary"/>
<br/>
<input type="submit" value="Create!">
</form>
<br><br>
</body>
</html>
In your myRestController.java, I am not seeing any #PostMapping defined. In addNewEmployee.html, it appears you are attempting to call shop/allEmployees with a POST rather than the GET method. If your intention is to pass a body or form to the shop/allEmployees endpoint, you may want to consider either changing your #GetMapping to a #PostMapping that accepts a #RequestBody or creating an entirely new #PostMapping that accepts a #RequestBody.

Creating ArrayList from thymelaf form input values

I have a form for adding products written in HTML and thymeleaf.
<form th:action="#{/products/get}" th:object="${form}" method="post">
<div id="fields">
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
Below the form, there is a button that adds two input fields to the form every time it's pressed. New input fields are the same as those two input fields above. The idea is that the user can enter data for as many products as he wants using the same form. For example, after pressing the button once the form will look like that:
<form th:action="#{/products/get}" th:object="${form}" method="post">
<div id="fields">
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
The thing is I'd like to create ArrayList of class ProductForm using the values from input fields and then pass it to my controller using #ModelAttribute.
public class ProductForm{
private String name;
private String label;
//getters and setters
}
Then created a class that wraps ProductForm into ArrayList
public class ProductFormArray {
ArrayList<ProductForm> forms;
//getters and setters
}
And a Controller
#Controller
#RequestMapping(value = "/products")
public class CreateAccountControllerTemporary {
#RequestMapping(value = "/get", method = RequestMethod.POST)
public String createAccount(#ModelAttribute(name = "form")ProductFormArray form){
//some code
}}
My problem is that I can't figure out how to add objects to form ArrayList using values from input fields? Is that even possible? How should I change my HTML file?
It is certainly possible, I explain this on pages 361 to 389 in my book Taming Thymeleaf.
You can check out the sources of the book for free at https://github.com/wimdeblauwe/taming-thymeleaf-sources/tree/main/chapter16
It is hard to summarize 30 pages into a stackoverflow answer, but briefly, check out:
CreateTeamFormData.java: This is similar to your ProductFormArray class. I do use an array instead of an ArrayList.
public class CreateTeamFormData {
#NotBlank
#Size(max = 100)
private String name;
#NotNull
private UserId coachId;
#NotNull
#Size(min = 1)
#Valid
private TeamPlayerFormData[] players;
TeamPlayerFormData.java: This is similar to your ProductForm class.
public class TeamPlayerFormData {
#NotNull
private UserId playerId;
#NotNull
private PlayerPosition position;
TeamController.java: This the controller that uses the CreateTeamFormData.
#GetMapping("/create")
#Secured("ROLE_ADMIN")
public String createTeamForm(Model model) {
model.addAttribute("team", new CreateTeamFormData());
model.addAttribute("users", userService.getAllUsersNameAndId());
model.addAttribute("positions", PlayerPosition.values()); //<.>
return "teams/edit";
}
#PostMapping("/create")
#Secured("ROLE_ADMIN")
public String doCreateTeam(#Valid #ModelAttribute("team") CreateTeamFormData formData,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("editMode", EditMode.CREATE);
model.addAttribute("users", userService.getAllUsersNameAndId());
model.addAttribute("positions", PlayerPosition.values());
return "teams/edit";
}
service.createTeam(formData.toParameters());
return "redirect:/teams";
}
edit.html -> This is the Thymeleaf template. Note that I am using a Thymeleaf fragment edit-teamplayer-fragment for the part of the form that repeats itself (So the name and label fields in your case)
<h3>Players</h3>
<div class="col-span-6 ml-4">
<div id="teamplayer-forms"
th:data-teamplayers-count="${team.players.length}"> <!--.-->
<th:block th:each="player, iter : ${team.players}">
<div th:replace="teams/edit-teamplayer-fragment :: teamplayer-form(index=${iter.index}, teamObjectName='team')"></div>
<!--.-->
</th:block>
</div>
<div class="mt-4">
<a href="#"
class="py-2 px-4 border border-gray-300 rounded-md text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800"
id="add-extra-teamplayer-form-button"
th:text="#{team.player.add.extra}"
#click="addExtraTeamPlayerForm()"
></a> <!--.-->
</div>
</div>
edit-teamplayer-fragment.html: Here is the important part where you need to keep track of the index for each fragment:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
lang="en">
<!-- tag::main[] -->
<div th:fragment="teamplayer-form"
class="col-span-6 flex items-stretch"
th:id="${'teamplayer-form-section-' + __${index}__}"
th:object="${__${teamObjectName}__}"> <!--.-->
<!-- end::main[] -->
<div class="grid grid-cols-1 row-gap-6 col-gap-4 sm:grid-cols-6">
<div class="sm:col-span-2">
<div class="mt-1 rounded-md shadow-sm">
<select class="form-select block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"
th:field="*{players[__${index}__].playerId}">
<option th:each="user : ${users}"
th:text="${user.userName.fullName}"
th:value="${user.id.asString()}">
</select>
</div>
</div>
<div class="sm:col-span-2">
<div class="mt-1 rounded-md shadow-sm">
<select class="form-select block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"
th:field="*{players[__${index}__].position}">
<option th:each="position : ${positions}"
th:text="#{'PlayerPosition.' + ${position}}"
th:value="${position}">
</select>
</div>
</div>
<!-- tag::delete[] -->
<div class="ml-1 sm:col-span-2 flex items-center text-green-600 hover:text-green-900">
<div class="h-6 w-6">
<svg th:replace="trash"></svg>
</div>
<a href="#"
class="ml-1"
th:text="#{team.player.remove}"
x-data
th:attr="data-formindex=__${index}__"
#click="removeTeamPlayerForm($el.dataset.formindex)"> <!--.-->
</a>
</div>
<!-- end::delete[] -->
</div>
</div>
</html>

Using ftl display list from POST method

Sorry for my english!
I am new to Spring and FTL.
I want to display firstName and lastName using <#list template, but I could not recognize any sequences in my POST method, could some one please explain to me. Again I am a newbie and please don't judge me if I don't understand what I should. I am using CUBA STUDIO 6.8 and IDEA. Also I'm working on this task in portal module
This is how I add firstName and lastName to database using my ftl form and Portal Controller:
#GetMapping("/add")
public String add(Model model){
PersonPojo personPojo = new PersonPojo();
model.addAttribute("personPojo", personPojo);
return "add";
}
#PostMapping("/add")
public String save(Model model, #ModelAttribute("personPojo") PersonPojo personPojo){
String firstName = personPojo.getFirstName();
String lastName = personPojo.getLastName();
PersonPojo newPerson = new PersonPojo(firstName, lastName);
Person standardEntity = metadata.create(Person.class);
standardEntity.setFirtName(newPerson.getFirstName());
standardEntity.setLastName(newPerson.getLastName());
dataManager.commit(standardEntity);
return "redirect:/allPersons";
}
My ftl form:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" name="person">
First Name: <input type="text" name="firstName"> <br>
Last Name: <input type="text" name="lastName"> <br>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
<input type="submit" value="Create">
</form></body>
</html>
Thank you!
So, If someone is interested i will post my solution here:
#RequestMapping(value = "/allPersons", method = RequestMethod.GET)
public String getPersons(Model model) {
LoadContext loadJohn = new LoadContext(John.class);
loadJohn.setQueryString("select u from test6$John u");
model.addAttribute("users", dataService.loadList(loadJohn));
return "list";
}
And the ftl should look like this:
The problem i faced next was I did not know I have to check list for null. !"" does that
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>Person List</h3>
Add Person
<br><br>
<div>
<table border="1">
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
<#list users as show>
<tr>
<td>${show.firstName!""}</td>
<td>${show.lastName!""}</td>
</tr>
</#list>
</table>
</div>
</body>
</html>
I hope this will help to people like me.
Also, If someone knows how to delete and update data please share.
Thanks!

Why am I getting null object after submitting form in Spring?

I am new to Spring and yesterday I created a simple app. I type book's title, author and genre and it saves it to List<Book>. Book.java contains private fields (title, author, genre).
So creating books and saving them to list works fine. Also I can view all books I have added. So it works fine. But now I want to create and delete them. So I have BookService.java that can add and delete books.
BookService.java
private List<Book> allBooks = new ArrayList<>();
public List<Book> getAllBooks() {
return allBooks;
}
public void addBook(String title, String author, String genre) {
allBooks.add(new Book(title, author, genre));
}
public void deleteBook(Book book) {
allBooks.remove(book);
}
This is stuff in my controller to delete books
#GetMapping("/books/delete")
public String deleteBook(Model model) {
model.addAttribute("BookList", bookService.getAllBooks()); // Works fine
return "delete";
}
#PostMapping("/books/delete")
public String deletedBook(#ModelAttribute Book book, Model model) {
System.out.println(book.getTitle()); // null
bookService.deleteBook(book); // can't delete null so nothing happens to the list
model.addAttribute("deletedBook", book);
return "deletedBookResult";
}
delete.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<h2>Delete page</h2>
<div th:each="bookObj : ${BookList}"> <!-- BookList - all books I add using submit form -->
<form action="#" th:action="#{/books/delete}" th:object="${bookObj}" method="post"> <!-- I SEND bookObj -->
<input type="submit" th:value="${bookObj.title}"/> <!-- WORKS. I GET BOOK'S NAME ON THIS BUTTON-->
</form>
</div>
</body>
</html>
I use th:each="bookObj : ${BookList}". So bookObj is every book I add. That's why I use th:object=${bookObj}. There is a form for each book I added later. And it display's it's title on the button. But when I press it, I get null to IDEA's console and on webpage. Why?
Thank you in advance.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<h2 th:text=" 'You have just deleted' + ${deletedBook.title}"></h2>
<!-- You have just deleted null -->
</body>
</html>
You are not sending anything in your form. Add some hidden fields:
<div th:each="bookObj : ${BookList}">
<form action="#" th:action="#{/books/delete}" method="post">
<input type="hidden" th:name="genre" th:value="${bookObj.genre}"/>
<input type="hidden" th:name="author" th:value="${bookObj.author}"/>
<input type="hidden" th:name="title" th:value="${bookObj.title}"/>
<input type="submit" th:value="${bookObj.title}"/>
</form>
</div>
Or if you don't want to expose your data in html, you can try to use some sort of session objects instead (Probably an overkill, but sometimes can be useful):
#GetMapping("/books/delete")
public String deleteBook(Model model, HttpSession session) {
session.setAttribute("BookList", new Book[]{
new Book("Title", "Tom","genre"),
new Book("Title 2", "Jerry","genre2")}
);
return "delete";
}
#PostMapping("/books/delete")
public String deletedBook(HttpSession session, Integer id, Model model) {
Book[] books = (Book[]) session.getAttribute("BookList");
Book book = books[id];
System.out.println(book);
model.addAttribute("deletedBook", book);
return "deletedBookResult";
}
And use it like:
<div th:each="bookObj, iter : ${session.BookList}">
<form action="#" th:action="#{/books/delete}" method="post">
<input type="hidden" th:name="id" th:value="${iter.index}"/>
<input type="submit" th:value="${bookObj.title}"/>
</form>
</div>

Categories

Resources