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

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>

Related

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.

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

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.

Java spring boot - retrieve dynamic inputs

I'm trying to build a small QCM Application:
You have an quiz that have multiple questions and therefore multiple potential answer. The user can select only one answer between all (using radio buttons).
The user submit the form, but now i'm trying to retrieve the answers that the user selected on the server but I have not much clue on what to do because those fields are dynamic.
<form method="post" th:action="#{/score}" class="qcm-questions">
<input type="hidden" name="id_quiz" th:value="${id_quiz}" />
<input type="hidden" name="pseudo" th:value="${pseudo}" />
<fieldset class="qcm-questions-item" th:each="question: ${questions}">
<h2 th:text="${question.getLabel()}"></h2>
<small th:text="'Question ' + ${questionStat.index}"></small>
<div th:each="answer: ${question.getAnswers()}">
<label th:text="${answer.getLabel()}" th:for="${answer.getId_answer()}"></label>
<input type="radio" th:id="${answer.getId_answer()}" th:name="${question.getId_question()}" th:value="${answer.getId_answer()}" />
</div>
</fieldset>
<button>Soumettre QCM</button>
</form>
The only method I found is this :
#PostMapping
public String registerScore(#RequestParam("id_quiz") final long id_quiz, #RequestParam("pseudo") final String pseudo, ServletRequest request) {
Map<String, String[]> answers = request.getParameterMap();
answers.remove("id_quiz");
answers.remove("pseudo");
return "page";
}
Maybe you have a better idea than this ?
You can retrieve inputs from a form through parameters or modelattributes.
I found personnally the model attribute solution easier and below is a code to do it like so.
I have created 2 modelattributes that I pass to the controller.
The first one will contain all the quiz details and the second one will be the answers collected in you form.
Modelattributes are objects and of course you can adapt the entities.
#Controller
public class FormQuizController {
#GetMapping ("/quiz")
public String main(Model model) {
Question quest1 = new Question();
quest1.setId(1);
quest1.setLabel("What is the answer?");
Answer answer1 = new Answer (1, "answer 1");
Answer answer2 = new Answer (2, "answer 2");
Answer answer3 = new Answer (3, "answer 3");
Answer[] answers = {answer1, answer2, answer3};
quest1.setAnswers(answers);
Question quest2 = new Question();
quest2.setId(2);
quest2.setLabel("What is the answer again?");
quest2.setAnswers(answers);
Question[] questions = {quest1, quest2};
Quiz quiz = new Quiz();
quiz.setId_quiz(1);
quiz.setPseudo("Quiz 1");
quiz.setQuestions(questions);
ResultQuiz resultQuiz = new ResultQuiz(0,"init", new int[questions.length],new int[questions.length]);
model.addAttribute("quiz", quiz);
model.addAttribute("resultQuiz", resultQuiz);
return "quiz";
}
#PostMapping ("/score")
public String score(#ModelAttribute ResultQuiz resultQuiz) {
System.out.println(resultQuiz);
return "score";
}
}
The method main is creating a quiz with 2 questions and 3 answers for each questions. It is also creating a resultQuiz initialized with fake data.
the method score is retrieving the result of the quiz through the object ResultQuiz.
Below the classes for domain (getters/setters/constructors omitted for brevity):
public class Quiz {
private int id_quiz;
private String pseudo;
private Question[] questions;
}
public class Question {
private int id;
private String label;
private Answer[] answers;
}
public class Answer {
private int id;
private String text;
}
public class ResultQuiz {
private int id_quiz;
private String pseudo;
private int[] id_question = {0,0};
private int[] id_answer = {0,0};
}
Then the code for the html pages using thymeleaf.
th:object indicates the object linked to the form. Then you can bind data directly.
To access values of an attribute with thymeleaf you just need to indicate the name of it.
iter variable gives you the index of each iteration.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>test form</title>
</head>
<body>
<form method="post" th:action="#{/score}" th:object="${resultQuiz}" class="qcm-questions">
<input type="hidden" th:name="'id_quiz'" th:value="${quiz.id_quiz}" >
<input type="hidden" th:name="'pseudo'" th:value="${quiz.pseudo}" >
<p th:text="'Valeur de resultQuiz:'+${resultQuiz}"></p>
<fieldset class="qcm-questions-item" th:each="question, iter: ${quiz.questions}">
<h2 th:text="${question.label}"></h2>
<small th:text="'Question ' + ${iter.index}"></small>
<input type="hidden" th:name="'id_question['+${iter.index}+']'" th:value="${question.id}" >
<div th:each="answer: ${question.answers}">
<label>
<span th:text="${answer.text}" th:for="${answer.text}"></span>
<input type="radio" th:name="'id_answer['+${iter.index}+']'" th:value="${answer.id}" />
</label>
</div>
</fieldset>
<button type="submit">Soumettre QCM</button>
</form>
</body>
</html>
And finally the code for the score html page:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>test form</title>
</head>
<body>
<span th:text="'For Quiz '+${resultQuiz.id_quiz}+' with name '+${resultQuiz.pseudo}"></span>
<h2>Results are:</h2>
<p th:each="question, iter: ${resultQuiz.id_question}">
<span th:text="'For question '+${question} +' answer is '+${resultQuiz.id_answer[iter.index]}"></span>
</p>
</body>
</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!

How to fix "Exception evaluating SpringEL expression" error after submitting a variable Spring/Thymeleaf

I am using Spring Boot/Thymeleaf to create a form that accepts an email address, redirects to a results page that displays the accepted email and sends it to a third party API (authenticated with Oauth2). I am having trouble with the form portion, I am attempting to use Thymeleaf to accept the input to display it on the result.html page. I am receiving an error when trying to display it on the results page, full error is:
[THYMELEAF][http-nio-8080-exec-4] Exception processing template "result.html": Exception evaluating SpringEL expression: "signup.email" (template: "result.html" - line 10, col 4)
I was attempting to follow the examples provided here:
https://spring.io/guides/gs/handling-form-submission/
I have attempted to modify the controller from #PostMapping and #GetMapping to #RequestMapping and add commenting described in a workaround such as:
<!--/*#thymesVar id="signup" type="com.mainconfig.controller1"*/-->
Here is the signup.html code containing the form:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html>
<head>
<title>My Jmml</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body style="background-color: #2B2B2B">
<br /><br />
<h2 style="text-align:center">Contact Information</h2>
<!-- Input Form -->
<!--/*#thymesVar id="signup" type="com.mainconfig.controller1"*/-->
<form action="#" th:action="#{/signup}" th:object="${signup}" method="post">
<div align="center">
<label>Email Address</label><br /><br />
<!--/*#thymesVar id="email" type="String"*/-->
<input type="text" th:field="*{email}" placeholder="Email" required />
<br />
<br />
<input class="submitbutton" type='submit' value='Submit'/>
<br />
</div>
</form>
</body>
</html>
Results page that should display the email (result.html):
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thank you for your submission!</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Thank you for your submission!</h1>
<p th:text="'Email: ' + ${signup.email}" />
Submit another message
</body>
</html>
Controller:
#Controller
public class controller1 {
#RequestMapping (value = "/home")
public String home(Model model) {
return "index.html";
}
#RequestMapping(value = "/signup", method= RequestMethod.GET)
public String signupForm(Model model) {
model.addAttribute("signup", new emailInput());
return "signup.html";
}
#RequestMapping(value = "/signup", method= RequestMethod.POST)
public String signupSubmit(#ModelAttribute("email") emailInput email) {
return "result.html";
}
}
Expected output should be the email variable displayed on the results page after it being gathered in the signup form.
If you have a recommendation on how to better do what I am attempting, I am open to suggestions! I am very new to Spring/Thymeleaf but have had experience with Java/Jsp. Thank you for any help, please let me know if you need anything else to help!
Hopefully this will be a starting point for you.
Make sure you place the html files under /resources/templates.
I changed a bit your signup html and result.html as follows, they are still not perfect(avoid using inline styles and use an external stylesheet!):
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<title>My Jmml</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body style="background-color: #2B2B2B">
<br /><br />
<h2 style="text-align:center">Contact Information</h2>
<!-- Input Form -->
<!--/*#thymesVar id="signup" type="com.mainconfig.controller1"*/-->
<form th:action="#{/signup}" th:object="${signup}" method="post">
<div align="center">
<label>Email Address</label><br /><br />
<!--/*#thymesVar id="email" type="String"*/-->
<input type="text" th:field="*{email}" placeholder="Email" />
<br />
<br />
<input class="submitbutton" type="submit" value="Submit"/>
<br />
</div>
</form>
</body>
and the result.html looks like this
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<title>Thank you for your submission!</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Thank you for your submission!</h1>
<p th:text="'Email: ' + ${email}" />
Submit another message
</body>
</html>
I also created a form object, add additional fields here if you want
public class SignUpForm {
//you can put some annotations here if you want for validating the email
//for e.g #NotEmpty or a #Pattern(regexp to validate the email)
private String email;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
and in the end your Controller. I pass the email from the signup post request to the result.html via a flash attribute:
#Controller
public class Controller1 {
#RequestMapping(value = "/signup", method= RequestMethod.GET)
public String signupForm(#ModelAttribute("signup") SignUpForm form) {
return "/signup";
}
#RequestMapping(value = "/signup", method= RequestMethod.POST)
public String signupSubmit(#ModelAttribute("signup") SignUpForm form, RedirectAttributes redirectAttributes) {
//validate form first -> check bindingResult documentation
//do what you need with your form object
redirectAttributes.addFlashAttribute("email", form.getEmail());
return "redirect:/result";
}
#RequestMapping(value = "/result", method= RequestMethod.GET)
public String result() {
return "/result";
}
}

Categories

Resources