how to get the data from nested object entity class to thymeleaf - spring-boot

i have 2 entity class, parkingUser and parkingDetails,
user can have many parking details(one to many).
parkingUserobject contain:
[id, firstName, lastName,parkingDetails[id, entryDate,...,user_id(FK)]]
in parkingUser i had List of parkingDetails(its my FK)
#OneToMany(mappedBy = "parkingUsers", cascade = CascadeType.ALL, orphanRemoval = true)
private List<parkingDetails> parkingDetails = new ArrayList<parkingDetails>();
what i want to achive is to access the data of nested object,
like that:
parkingUser.getParkingDetails.getEntryTime()
this is my controller:
#GetMapping("/exitform")
public String exitForm(#RequestParam ("userId") int theId, Model model) {
parkingUsers parkingUsers = parkingUsersService.findByID(theId);
model.addAttribute("user",parkingUsers);
model.addAttribute("exitDateAndTime", new java.util.Date());
return "exit-form";
}
and my thymeleaf snift code:
<th:block th:object="${user}">
<form action="#" th:action="#{/saveDateAndTimeOfExitParking}" th:object="${user.parkingDetails}" method="POST">
<input type="hidden" th:field="*{id}"/>
<input type="text" th:field="*{firstName}">
<br>
<input type="text" th:field="*{lastName}">
<br>
<input type="text" th:field="*{license}">
<br>
<!-- i want to get the value of entryDate on parkingDetails object -->
<input type="text" th:field="*{user.parkingDetails.entryDate}">
<br>
<!-- i want to get the value of entryTime on parkingDetails object -->
<input type="text" th:field="*{user.parkingDetails.entryTime}">
<br>
<input type="text" th:value="${#dates.format(exitDateAndTime, 'dd-MM-yyyy')}">
<br>
<input type="text" th:value="${#dates.format(exitDateAndTime, 'HH:mm:ss')}">
<br>
<button type="submit" class="btn btn-info mb-11 col-15">Exit Car</button>
</form>
</th:block>

Related

Form is not saved because it has errors, but errors are not displayed

I have a SignUp form for user, also I have a validation for fields, I'll provide all of that. So, when user insert for example on field username > s but that field need to have more then 3 characters, program should show error and not save that data into table. What's happening? Application process the error, I mean form is not submitted when I made a error on purpose, but error is not showing on HTML, not on stack trace. Page is just returned but error message are not showing, good thing is just that form is not saved if there is error so that work, problem is just message not appearing.
I have a SignUp form, and for example I have this validation on username and firstName:
#NotEmpty
#Size(min = 3, max = 20, message = "Username not valid")
private String username;
#NotEmpty(message = "Please, insert a first name")
private String firstName;
This is how I display form, its login and register on same page, that is why I have two model attributes:
#GetMapping("/loginAndRegisterForm")
public String showLoginForm(Model model) {
// create model object to store form data
LoginRequest loginRequest = new LoginRequest();
SignupRequest signupRequest = new SignupRequest();
model.addAttribute("login", loginRequest);
model.addAttribute("user", signupRequest);
return "login_and_registration";
}
This is SignUp controller, with result.hasErrors() inside it:
#PostMapping("/signup")
#Transactional
public String signup(#ModelAttribute("signup") #Valid SignupRequest signupRequest, BindingResult result, Model model) throws Exception {
boolean thereAreErrors = result.hasErrors();
if (thereAreErrors) {
LoginRequest loginRequest = new LoginRequest();
model.addAttribute("user", signupRequest);
model.addAttribute("signup", signupRequest);
model.addAttribute("login", loginRequest);
return "login_and_registration";
}
User user = new User(signupRequest.getUsername(), signupRequest.getFirstName(), signupRequest.getLastName(), signupRequest.getEmail(), encoder.encode(signupRequest.getPassword()));
model.addAttribute("signup", signupRequest);
userRepository.save(user);
return "redirect:/api/auth/loginAndRegisterForm";
}
And this is Thymeleaf:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security Tutorial</title>
<link rel="stylesheet" type="text/css" th:href="#{/css/loginAndRegistration.css}"/>
</head>
<body>
<div class="main">
<input type="checkbox" id="chk" aria-hidden="true">
<div class="signup">
<form
method="post"
role="form"
th:action="#{/api/auth/signup}"
th:object="${user}">
<label for="chk" aria-hidden="true">Sign up</label>
<input
class="form-control"
id="usernameSignUp"
name="username"
placeholder="Enter username"
th:field="*{username}"
type="text"/>
<p th:errors="*{username}" class="text-danger"
th:if="${#fields.hasErrors('username')}"></p>
<input
class="form-control"
id="firstName"
name="firstName"
placeholder="Enter first name"
th:field="*{firstName}"
type="text"/>
<p th:errors="*{firstName}" class="text-danger"
th:if="${#fields.hasErrors('firstName')}"></p>
<input
class="form-control"
id="lastName"
name="lastName"
placeholder="Enter lastName"
th:field="*{lastName}"
type="text"/>
<p th:errors="*{firstName}" class="text-danger"
th:if="${#fields.hasErrors('firstName')}"></p>
<input
class="form-control"
id="email"
name="email"
placeholder="Enter email"
th:field="*{email}"
type="email"/>
<p th:errors="*{email}" class="text-danger"
th:if="${#fields.hasErrors('email')}"></p>
<input
class="form-control"
id="passwordSignUp"
name="password"
placeholder="Enter password"
th:field="*{password}"
type="password"/>
<p th:errors="*{password}" class="text-danger"
th:if="${#fields.hasErrors('password')}"></p>
<button>Sign up</button>
</form>
</div>
<div class="login">
<form
method="post"
role="form"
th:action="#{/api/auth/login}"
th:object="${login}">
<label for="chk" aria-hidden="true">Login</label>
<input
class="form-control"
id="usernameLogin"
name="username"
placeholder="Enter username"
th:field="*{username}"
type="text"/>
<p th:errors="*{username}" class="text-danger"
th:if="${#fields.hasErrors('username')}"></p>
<input
class="form-control"
id="passwordLogin"
name="password"
placeholder="Enter password"
th:field="*{password}"
type="password"/>
<p th:errors="*{password}" class="text-danger"
th:if="${#fields.hasErrors('password')}"></p>
<button>Login</button>
</form>
</div>
</div>
</body>
</html>

Thymeleaf errors are not showing on page but validation is fine

Controller:
#PostMapping("/saveExpense/{walletId}")
public String saveExpense(#PathVariable(value = "walletId") long walletId,
#ModelAttribute("wallets") #Valid Transaction transaction, BindingResult result, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
Wallet wallet = walletService.getWalletById(walletId);
boolean thereAreErrors = result.hasErrors();
if (thereAreErrors) {
model.addAttribute("transaction", transaction);
return "redirect:/api/transaction/showNewTransactionForm/" + walletId;
}
transaction.setWallet(wallet);
transactionService.saveExpense(transaction, walletId, userId);
return "redirect:/api/wallet/userWallet/balance/" + userId;
}
This is form inside Thymeleaf:
<form action="#" id="Expense" class="hidden" th:action="#{/api/transaction/saveExpense/{walletId} (walletId=${id})}"
th:object="${transaction}" method="POST">
<input type="text" th:field="*{amount}" placeholder="Enter amount" class="form-control mb-4 col-4">
<p th:if="${#fields.hasErrors('amount')}" th:class="${#fields.hasErrors('amount')}? error">
Invalid Age</p>
<input type="text" th:field="*{note}" placeholder="Enter note" class="form-control mb-4 col-4">
<input type="date" th:field="*{date}" class="form-control mb-4 col-4">
<select th:field="${transaction.expenseCategories}">
<option value="0">Select expense category</option>
<option
th:each="expenseCategories : ${expenseCategories}"
th:value="${expenseCategories}"
th:text="${expenseCategories.displayName}"
></option>
</select>
<button type="submit" class="btn btn-info col-2"> Save Wallet</button>
</form>
Here is the part of field that I want to display error:
<input type="text" th:field="*{amount}" placeholder="Enter amount" class="form-control mb-4 col-4">
<p th:if="${#fields.hasErrors('amount')}" th:class="${#fields.hasErrors('amount')}? error">
Invalid Age</p>
And inside model:
#Min(value = 0, message = "Please, insert a positive amount")
private double amount;
So for example, if I try to set amount -1 and after I submit form, that data is not saved in database so that is fine, but I cant see errors on page, I just get redirected to same page but no errors are showing.
Just to mention, I already tried to find something useful here on SO but nothing worked so far

The server cannot process the request because of Spring MVC <form:select>

I am creating a formular in order to populate some entities. I run into problems when I am trying to POST a form which contains a Spring MVC <form:select> field.
In Eclipse I do not receive any error or warning message, while in the browser I get a http status 400 - Bad Request.
Type Status Report
Description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
Extra explanations:
I have a StudentDetails entity which contains 2 fields that I am interested in at the moment, Classroom classroom and ParentsDetails parentsDetails.
The Classroom objects are already created and all of them will be stored in a LinkedHashMap<Classroom, String> as a model attribute (i am doing this in the saveAccountDetails method from the Controller).
The ParentsDetails object will be created after the StudentDetails entity will be saved with the selected classroom.
When I submit the form as I mentioned above I run into an error but without any(or relevant) error message.
I spent some time debugging and trying different approaches of handling that map of Classrooms, but none of them worked.
What is actually happening, the controller method saveStudentDetails is not called anymore.
The issue must come from that form:select because if I get rid of this input, the controller method will be called and will let me advance in creating the ParentsDetails entity.
I have no clue what is wrong.
I used previously this form:select but the LinkedHashMap contained just Strings, without any objects and it worked. I think thats my issue.
StudentDetails.java
#Entity
#Table(name="student_details")
public class StudentDetails {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="current_year_of_study")
private Integer currentYearOfStudy;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="parents_details_id")
private ParentsDetails parentsDetails;
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name="class_id")
private Classroom classroom;
#OneToOne(mappedBy="studentDetails", cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private User user;
#ManyToMany
#JoinTable(name="course_studentdetails",
joinColumns=#JoinColumn(name="student_details_id"),
inverseJoinColumns=#JoinColumn(name="course_id")
)
private List<Course> courses;
... (Constructors, getters setters)
Controller.java
#PostMapping("/save-account-details")
public String saveAccountDetails(#ModelAttribute("theAccountDetails") AccountDetails theAccountDetails, #RequestParam("userUsername") String username, Model theModel) {
User theUser = userService.getUser(username);
theUser.setAccountDetails(theAccountDetails);
accountDetailsService.saveAccountDetails(theAccountDetails);
userService.saveUser(theUser);
theModel.addAttribute("theUser", theUser);
theModel.addAttribute("theStudentDetails", new StudentDetails());
theModel.addAttribute("classroomsList", classroomService.getSchoolClassrooms(theUser.getAccountDetails().getCity()));
theModel.addAttribute("entity", "StudentDetails");
return "create-user";
}
#PostMapping("/save-student-details")
public String saveStudentDetails(#ModelAttribute("theStudentDetails") StudentDetails theStudentDetails, #RequestParam("userUsername") String username, Model theModel) {
User theUser = userService.getUser(username);
theUser.setStudentDetails(theStudentDetails);
studentDetailsService.saveStudentDetails(theStudentDetails);
userService.saveUser(theUser);
theModel.addAttribute("theUser", theUser);
theModel.addAttribute("theParentsDetails", new ParentsDetails());
theModel.addAttribute("entity", "ParentsDetails");
return "create-user";
}
#PostMapping("/save-parents-details")
public String saveParentsDetails(#ModelAttribute("theParentsDetails") ParentsDetails theParentsDetails, #RequestParam("userUsername") String username, Model theModel) {
User theUser = userService.getUser(username);
theUser.getStudentDetails().setParentsDetails(theParentsDetails);
parentsDetailsService.saveParentsDetails(theParentsDetails);
userService.saveUser(theUser);
theModel.addAttribute("theUser", theUser);
theModel.addAttribute("theParentsDetails", new ParentsDetails());
theModel.addAttribute("entity", "ParentsDetails");
return "create-user";
}
create-user.jsp
<c:if test="${entity == 'StudentDetails'}">
<c:url var="saveStudentDetails" value="save-student-details">
<c:param name="userUsername" value="${theUser.username}" />
</c:url>
<form:form action="${saveStudentDetails}" modelAttribute="theStudentDetails" method="POST">
<form:hidden path="id" />
<div class="form-area">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Current Year of Study</span>
</div>
<form:input type="text" class="form-control" path="currentYearOfStudy" aria-label="Default" aria-describedby="inputGroup-sizing-default" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text" for="inputGroupSelect01">Classroom</label>
</div>
<form:select path="classroom" class="custom-select" id="inputGroupSelect01">
<form:option value="" label="Select classroom..." />
<form:options items="${classroomsList}" />
</form:select>
</div>
<button type="submit" class="btn btn-outline-secondary btn-block">Submit</button>
</div>
</form:form>
</c:if>
<c:if test="${entity == 'ParentsDetails'}">
<c:url var="saveParentsDetails" value="save-parents-details">
<c:param name="userUsername" value="${theUser.username}" />
</c:url>
<form:form action="${saveParentsDetails}" modelAttribute="theParentsDetails" method="POST">
<form:hidden path="id" />
<div class="form-area">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Father First Name</span>
</div>
<form:input type="text" class="form-control" path="fatherFirstName" aria-label="Default" aria-describedby="inputGroup-sizing-default" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Father Last Name</span>
</div>
<form:input type="text" class="form-control" path="fatherLastName" aria-label="Default" aria-describedby="inputGroup-sizing-default" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Father Telephone</span>
</div>
<form:input type="text" class="form-control" path="fatherTelephone" aria-label="Default" aria-describedby="inputGroup-sizing-default" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Mother First Name</span>
</div>
<form:input type="text" class="form-control" path="motherFirstName" aria-label="Default" aria-describedby="inputGroup-sizing-default" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Mother Last Name</span>
</div>
<form:input type="text" class="form-control" path="motherLastName" aria-label="Default" aria-describedby="inputGroup-sizing-default" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Mother Telephone</span>
</div>
<form:input type="text" class="form-control" path="motherTelephone" aria-label="Default" aria-describedby="inputGroup-sizing-default" />
</div>
<button type="submit" class="btn btn-outline-secondary btn-block">Submit</button>
</div>
</form:form>
</c:if>
If any more code snippets are needed, I will add them as soon as possible. Thank you in advance!
This is the network tab. The object looks like it was submitted with the form...
I will add shortly a video with the application.
Edit: demo link: https://youtu.be/neJOLHL9REo
Try adding multipart form-data to enctype in the form tag, it may works.
FROM:
<form:form action="${saveStudentDetails}"
modelAttribute="theStudentDetails" method="POST">
TO:
<form:form action="${saveStudentDetails}" enctype="multipart/form-data"
modelAttribute="theStudentDetails" method="POST">

Spring & Thymeleaf form

adding in form is trivial when I have only one object like here:
<form th:action="|#{group/save}/${id}|" th:object="${groupForm}" method="post" class="col m8 s8 offset-m2">
<div class="row">
<div class="input-field">
<input th:field="${groupForm.name}" id="name" type="text" required="required"/>
<label for="name">Nazwa:</label>
</div>
</div>
<div class="row">
<button class="btn-success" type="submit" name="save">Wyślij<i class="mdi-content-send right"></i></button>
</div>
</form>
but let's assume that groupForm have list of customer
public class Customer{
private long id;
private String firstName;
private String lastName;
private String nick;
}
How can I add 5 Customers into list in class Group? I want to achive in one request.
Assuming that your Group class looks something like this:
public class Group {
private List<Customer> customers;
}
Try this:
<input th:field="*{customers[0].name}" type="text" required="required"/>
<input th:field="*{customers[1].name}" type="text" required="required"/>
<input th:field="*{customers[2].name}" type="text" required="required"/>
<input th:field="*{customers[3].name}" type="text" required="required"/>
<input th:field="*{customers[4].name}" type="text" required="required"/>

How tell the view an error has occured from controller? (Spring MVC)

How can I say trigger error/validation messages in the view from the controller in a better way? Currently, I do this by sending boolean attributes. For example, in creating a product, I have two possible errors. Invalid format of UPC of a product, or duplicate upc. I also have a validati
#RequestMapping("/createProduct")
public String createProduct(Model model, #RequestParam(value = "name") String name,
#RequestParam(value = "upc") String upc, #RequestParam(value = "category") String categoryName,
#RequestParam(value = "description") String description, #RequestParam(value = "price") BigDecimal price,
#RequestParam(value = "stock") int stock){
model.addAttribute("activeTab", 3);
if(Validator.invalidUpcFormat(upc)){
model.addAttribute("invalidFormat", true); //trigger for invalid format
return "management";
}
Category category = productService.getCategory(categoryName);
Product product = new Product(upc, category, name, description, price);
InventoryProduct inventoryProduct = new InventoryProduct(product, stock);
try {
managerService.add(inventoryProduct);
model.addAttribute("productCreated", true);
} catch (DuplicateProductException e) {
model.addAttribute("upc", upc);
model.addAttribute("duplicateProduct", true); // trigger for duplicate product
}
return "management";
}
And here is my view:
<div id="menu3"
class="tab-pane fade <c:if test="${activeTab == 3}">in active</c:if>">
<div class="container-fluid" style="padding: 2%;">
<div class="row">
<div class="col-md-12"
style="padding-left: 15%; padding-right: 15%;">
<c:if test="${productCreated}">
<div class="alert alert-success fade in">
<a href="#" class="close" data-dismiss="alert"
aria-label="close">×</a> <strong>Success!</strong>
Product has been created!
</div>
</c:if>
<c:if test="${duplicateProduct}">
<div class="alert alert-warning fade in">
<a href="#" class="close" data-dismiss="alert"
aria-label="close">×</a> <strong>Oh no!</strong>
Product with the UPC ${upc} already exists!
</div>
</c:if>
<c:if test="${invalidFormat}">
<div class="alert alert-warning fade in">
<a href="#" class="close" data-dismiss="alert"
aria-label="close">×</a> <strong>Oops!</strong>
Invalid UPC format!
</div>
</c:if>
<form
action="${pageContext.request.contextPath}/manager/createProduct"
method="post">
<div class="form-group">
<label for="Name">Name </label> <input type="text" name="name"
class="form-control" required />
</div>
<div class="form-group">
<label for="UPC">UPC </label> <input type="number" name="upc"
class="form-control" required />
</div>
<div class="form-group">
<div class="form-group">
<label for="category">Category</label> <select
class="form-control" name="category" required>
<option selected disabled value="">SELECT CATEGORY</option>
<c:forEach items="${categories}" var="item">
<option>${item.getName()}</option>
</c:forEach>
</select>
</div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control" rows="5" name="description"></textarea>
</div>
<div class="form-group">
<label for="price">Price </label> <input type="number"
name="price" class="form-control" required />
</div>
<div class="form-group">
<label for="stock">Stock </label> <input type="number"
name="stock" class="form-control" required />
</div>
<button type="submit" class="btn btn-primary">Add
product</button>
</form>
</div>
</div>
</div>
</div>
Is there a better of doing this other than sending boolean triggers?
You could use Spring BindingResult. This is typical filled with the result of Binding and Validation results. But you can also add errors by hand.
But first you need to refactor your code, so that you use an single command/form-backing object instead of all the #Param values
public class CreateProductCommand {
private String name;
private String upc;
private String categoryName;
.... //other fields
public CreateProductCommand (){} //parameter less conturctor
Getter+Setter
}
Controller
#RequestMapping("/createProduct")
public ModelAndView createProduct(CreateProductCommand createProductCommand, BindingResult bindingResult) //Binding result must be the parameter direct next to the object that should been validated!!!
{
if (someustomValidationForUcpFail()) {
bindingResult.rejectValue("upc", //the field name of the invalid field
"error.Message.Key",
"Default Error Message");
}
if (bindingResult.hasErrors()) {
ModelMap model = new ModelMap();
model.add("createProductCommand", createProductCommand);
return new ModelAndView("createForm", model)
} else {
Product product = ...
return new ModelAndView("showProduct", "product", product)
}
}
jsp:
You need to use springs form and input tag:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:springForm="http://www.springframework.org/tags/form"
version="2.0">
....
<springForm:form action="<c:url value="/manager/createProduct">" method="POST" modelAttribute="createProductCommand">
<springForm:input path="name"/> <form:errors path="name" />
<springForm:input path="ucp"/> <form:errors path="ucp" />
....
</springForm:form>
....

Resources