I am working on a spring mvc project. A Name class has a many to one relationship with a gender class. I have a Genderformatter to handle the select list needed for create and update name:
public class GenderFormatter implements Formatter<Gender> {
#Autowired
private GenderRepository genderRepository;
public String print(Gender gender, Locale locale) {
return gender.getId().toString();
}
public Gender parse(String id, Locale locale) throws ParseException {
Gender gender = this.genderRepository.findOne(Integer.valueOf(id));
return gender;
}
}
Here is relavent portion of the addupdatename.jsp
<form:form modelAttribute="name" method="${method}"
class="form-horizontal" id="add-name-form">
...
<!-- Gender Select List -->
<spring:bind path="gender">
<c:set var="cssGroup"
value="control-group ${status.error ? 'error' : '' }" />
<div class="${cssGroup}">
<label class="control-label">Gender</label>
<div class="controls">
<form:select path="gender">
<form:option value="${name.gender.id}" label="${name.gender.gender}" />
<form:options items="${genders}" itemValue="id" itemLabel="gender" />
</form:select>
<span class="help-inline">${status.errorMessage}</span>
</div>
</div>
</spring:bind>
The NameController relevant GET and POST:
#RequestMapping(value = "/names/new", method = RequestMethod.GET)
public String initCreationForm(ModelMap model) {
Name name = new Name();
model.addAttribute(name);
model.put("genders", this.nameAdminService.findGenders());
return "name/addupdatename";
}
#RequestMapping(value = "/names/new", method = RequestMethod.POST)
public String processCreationForm(#Valid Name name, BindingResult result,
SessionStatus status) {
if (result.hasErrors()) {
return "name/addupdatename";
} else {
this.nameRepository.save(name);
status.setComplete();
return "redirect:/names/" + name.getId();
}
}
This all works fine. Now I'm adding CRUD functions for the Gender entity and running into problems. The GenderController and jsp objects follow the same pattern as the Name code. As long as the GenderFormatter is registered as a conversion service, the gender forms wont process add or updates to the data. I assume because on the gender add or update form post, the formatter is trying to convert the gender field to a gender object.
Is there a way to specify when or which fields a formatter should format?
Related
I have an Entity "User". I have an Enum "Usertyp". Users have a field "usertyp". I want to change the "Usertyp" by passing a DTO to my controller. The problem is that the usertype gets changed to Null in the DB
Another minor problem is that I want the default value of the selectfield to have the users current usertype as default value. I tried with "
th:selected="${u.usertyp}">
and also with an if statement. Neither worked.
I am very grateful for any help. I really struggle with Thymeleaf.
Controller:
#PostMapping("editUser/{userId}")
public String editUser(#ModelAttribute("sessionUser") User sessionUser, #Valid #ModelAttribute("userDTO") UserDTO userDTO, #PathVariable Long userId, Model model){
if(sessionUser==null || sessionUser.getUsertyp() == Usertyp.USER)
{
return "redirect:/";
}
else {
User changedUser = userService.findById(userId);
changedUser.setUsertyp(Usertyp.fromString(userDTO.getUsertyp()));
userService.save(changedUser);
return "redirect:/administration";
}
}
DTO
public class UserDTO {
//TODO usertyp anpassem
private String usertyp;
public UserDTO(String usertyp) {
this.usertyp = usertyp;
}
public String getUsertyp() {
return usertyp;
}
public void setUsertyp(String usertyp) {
this.usertyp = usertyp;
}
}
Template
<ul class="list-group list-group-flush">
<span th:each="u : ${userList}">
<li class="list-group-item">
<span th:text="${u.username}"></span>
<form th:object="${userDTO}" th:action="#{/editUser/{userId}(userId=${u.id})}" method="Post" id="Rolle">
<select th:field="*{usertyp}" form="Rolle">
<option
th:each="usertyp : ${T(com.example.myproject.entities.Usertyp).values()}"
th:text="${usertyp.displayText}"
th:value="${usertyp.displayText}">
</option>
</select>
<input class="btn btn-primary" type="submit" value="Speichern">
</form>
</li>
</span>
</ul>
I already changed my DTO field type from "Usertyp" to String
th:selected="${u.usertyp}">
The th:selected property should convert to selected="selected" or just selected in raw html, I think instead you want something like th:selected="${u.usertyp == usertyp}"
You didn't show your #GetMapping setup for the thyme leaf page, so I didn't include a user list, but here's a basic example of what I think you're attempting to do:
User Controller:
#Controller
public class UserController {
#GetMapping("user")
public String getUserTypePage(#ModelAttribute("userDTO") UserDTO userDTO) {
// just setting it to admin so that it will show the default selection of not the first item
userDTO.setUserType(UserType.ADMIN);
return "user";
}
#PostMapping("user")
public void postUserTypePage(#ModelAttribute("userDTO") UserDTO userDTO) {
System.out.println("USER DTO TYPE - " + userDTO.getUserType().getDisplayText());
}
}
UserType enum:
public enum UserType {
USER("Standard User"),
ADMIN("Administrator");
private final String displayText;
UserType(String displayText) {
this.displayText = displayText;
}
public String getDisplayText() {
return displayText;
}
}
UserDTO:
public class UserDTO {
private UserType userType;
public UserDTO(UserType userType) {
this.userType = userType;
}
public UserType getUserType() {
return userType;
}
public void setUserType(UserType userType) {
this.userType = userType;
}
}
Thymeleaf view:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<title>User Type</title>
</head>
<body>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<form th:object="${userDTO}" th:action="#{/user}" method="POST" id="Rolle">
<select th:field="*{userType}" form="Rolle">
<option
th:each="uType : ${T(com.example.demo.models.UserType).values()}"
th:text="${uType.displayText}"
th:value="${uType}"
th:selected="${userType == uType}">
</option>
</select>
<input class="btn btn-primary" type="submit" value="Speichern">
</form>
</li>
</ul>
</body>
</html>
Specifically you want the th:value to be bound to the enum value (if you'd prefer to use an enum rather than a string):
th:value="${uType}"
And then the th:selected set based off of equality of the enum to the UserType option in being looped through:
th:selected="${userType == uType}">
A few additional things to check:
Are you establishing the #ModelAttribute("userDTO") in the GET variant of your controller?
Log out the value of the userType in your post method to confirm it's value before any database operation (to confirm that it is actually coming in as null from the post and not being set to null somewhere in the db operation).
Try just passing along the value for the userType in the form (instead of using a select input item) to confirm that the currentValue for your model is getting fed through i.e.: <input th:field="*{userType}" th:value="${userType}">
I have ReservationController that have 2 methods:
#GetMapping
public String getReservationPage(final Model model,
final #PathVariable String hotelId,
final #PathVariable String roomId) {
final RoomApi roomApi = roomClient.findById(roomId).getBody();
model.addAttribute("roomApi", roomApi);
model.addAttribute("hotelId", hotelId);
model.addAttribute("roomId", roomId);
model.addAttribute("reservationForm", new ReservationApi().toBuilder()
.room(roomApi)
.build());
return "reservation";
}
#PostMapping
public String createReservation(final #PathVariable String roomId,
final #PathVariable String hotelId,
final #ModelAttribute("reservationForm") ReservationApi reservationApi) {
final Reservation reservation = reservationMapper.toDomain(reservationApi);
reservationService.save(reservation);
return "redirect:/";
}
And I have one problem, that relate to the roomApi. So how can I transfer roomApi from the getReservationPage to the crateReservation in reservationForm. I also have thymeleaf page with some form, but when I click submit button, roomApi is null in reservation. I think thats because of thymeleaf form does not contain any information about roomApi in reservation, but Im addinig it with builder.
<form th:object="${reservationForm}"
method="post"
th:action="#{/hotels/{hotelId}/rooms/{roomId}/reservations(hotelId=${hotelId}, roomId=${roomId})}">
<div class="form-group">
<label for="additionalInformation">Additional Information</label>
<input type="text" th:field="*{additionalInformation}" th:name="additionalInformation" class="form-control form-control-sm" id="additionalInformation">
</div>>
<button type="submit">Reserve</button>
</form>
In ReservationController.java I have the following method, which gets a reservation object from new-reservation.jsp
#PostMapping("/addBookToReservation")
public String addBookToReservation(Model model,
#Valid #ModelAttribute("reservation") Reservation reservation,
BindingResult result,
RedirectAttributes redirectAttributes) {
if(result.hasErrors()) {
return "reservation/new-reservation";
}
redirectAttributes.addFlashAttribute("reservation", reservation);
return "redirect:/book/add-book";
}
and sends it to BookController.java, where another method adds another attribute to the model
#GetMapping("/book/add-book")
public String showAddBookForm(Model model) {
model.addAttribute("book", new Book());
Reservation reservation = (Reservation) model.getAttribute("reservation");
System.out.println(reservation); //prints the object I passed it!
return "/book/add-book";
}
and returns the following add-book.jsp
<form:form action="/addBook" modelAttribute="book" method="post">
<div>
<div>
<form:label path="title">Title</form:label>
<form:input type="text" id="title" path="title" />
<form:errors path="title" />
</div>
...
</div>
<div>
<input type="submit" value="Add book">
</div>
</form:form>
Now, when I handle the form's action addBook
#PostMapping("/addBook")
public String addBook(#Valid #ModelAttribute Book book,
BindingResult result,
Model model) {
if (result.hasErrors()) {
return "book/add-book";
}
Reservation reservation = (Reservation) model.getAttribute("reservation");
System.out.println(reservation); // reservation is null!!
return "somewhere/else";
}
and I try to retrieve the reservation object from the model I get a null.
How can I pass my reservation object through the JSPs I've showed you before?
try adding #SessionAttributes for the model attribute on top of the controller and remove the session attribute when done
#SessionAttributes("reservation")
In my registration form, I have a field that give to users the possiblity to choose roles. But, I want to, only, show two roles : USER and MODERATOR.
But, I don't know how to proceed.
this registration form jsp :
<div class="row">
<div class="form-group col-md-12">
<label style="color:#ffffff;" class="col-md-3 control-lable" for="userProfiles">Roles</label>
<div class="col-md-7">
<form:select path="userProfiles" items="${roles}" multiple="true" itemValue="id" itemLabel="type" class="form-control input-sm" />
<div class="has-error">
<form:errors path="userProfiles" class="help-inline"/>
</div>
</div>
</div>
</div>
I have an enum : UserProfileType
public enum UserProfileType implements Serializable{
USER("USER"),
DBA("DBA"),
ADMIN("ADMIN"),
MODERATOR("MODERATOR");
String userProfileType;
private UserProfileType(String userProfileType){
this.userProfileType = userProfileType;
}
public String getUserProfileType(){
return userProfileType;
}
}
And this is how it look :
This my controller, the part responsible of registration
//new user
#RequestMapping(value = "/registrationForm", method = RequestMethod.GET)
public String newUser(User user, ModelMap model) {
//User user = new User();
model.addAttribute("user", user);
model.addAttribute("loggedinuser", getPrincipal());
return "registrationForm";
}
//save user
#RequestMapping(value = { "/registrationForm" }, method = RequestMethod.POST)
public String saveUser1(#Valid User user, BindingResult result,
ModelMap model) {
if (result.hasErrors()) {
return "registrationForm";
}
if(!userService.isUserSSOUnique(user.getId(), user.getSsoId())){
FieldError ssoError =new FieldError("user","ssoId",messageSource.getMessage("non.unique.ssoId", new String[]{user.getSsoId()}, Locale.getDefault()));
result.addError(ssoError);
return "registrationForm";
}
userService.saveUser(user);
model.addAttribute("success", "User " + user.getFirstName() + " "+ user.getLastName() + " registered successfully");
model.addAttribute("loggedinuser", getPrincipal());
//return "success";
return "registrationsuccess";
}
I am using spring mvc/security and hibernate.
Thanks in advance
The data to be displayed by the page should be provided and manipulated by the controller. Filter the roles in your controller before handing over to the view.
Depending on how you are adding attributes to the model you can do it as foLlows (or any equivalent way as Spring MVC is flexible)
You could create methods in your Enum class to return the filtered values according to yours needs:
public enum UserProfileType implements Serializable{
USER("USER"),
DBA("DBA"),
ADMIN("ADMIN"),
MODERATOR("MODERATOR");
String userProfileType;
private UserProfileType(String userProfileType){
this.userProfileType = userProfileType;
}
public String getUserProfileType(){
return userProfileType;
}
public static List<UserProfileType> getAdminRoles(){
return Arrays.asList(UserProfileType.USER , UserProfileType.MODERATOR);
}
}
#ModelAttribute("roles")
public List<UserProfileType> roles(){
return UserProfileType.getAdminRoles();
}
You can also filter the roles in the view but its not recommended to have control logic in your view
<form:select path="userProfiles"multiple="true" class="form-control input-sm">
<c:forEach items="${roles}" var = "role">
<c:if test="${role.type eq 'USER' or role.type eq 'MODERATOR'}">
<form:option value="${role.id}" label="${role.type}"/>
</c:if>
</c:forEach>
</form:select>
Login form:
<f:form class="form-horizontal" method="post" action="/login"
commandName="logindata">
<fieldset>
<legend class="text-info">Login</legend>
<div class="control-group">
<f:label path="uname" class="control-label" for="uname">Username</f:label>
<div class="controls">
<f:input type="text" path="uname" name="uname" id="uname"
placeholder="Username" />
</div>
</div>
<div class="control-group">
<f:label path="pwd" class="control-label" for="pwd">Password</f:label>
<div class="controls">
<f:input type="password" path="pwd" name="pwd" id="pwd"
placeholder="Password" />
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" id="login">
Login <i class="icon-chevron-right"></i>
</button>
</div>
</div>
<div id="errormsg" class="alert alert-error">${message}</div>
</fieldset>
</f:form>
the loginData class:
package com.demo.forms;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
public class loginData {
#Length(min=4)
private String uname;
#NotEmpty
private String pwd;
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
Controller methods for showing and submitting the form: (Shows homepage which contains signup form and login form)
#RequestMapping(value = "/", method=RequestMethod.GET)
public String showHome(Model model)
{
loginservice.logout();
model.addAttribute("logindata", new loginData());
model.addAttribute("signupdata", new signupData());
return "home";
}
Method called upon submitting login form:
#RequestMapping(value = "login", method=RequestMethod.POST)
public String submitloginForm(#Valid loginData logindata, SessionStatus state, Model model, BindingResult result)
{
if((loginservice.loggedin()) || (result.hasErrors()))
{
return showHome(model);
}
else
{
String uname = logindata.getUname();
String pwd = logindata.getPwd();
if(loginservice.login(uname, pwd))
{
model.addAttribute("user",uname);
return "redirect:profile";
}
else
{
model.addAttribute("message","Invalid Username/Password");
return showHome(model);
}
}
}
The login works fine when the data entered is 'valid' (either correct or wrong). However, when it is invalid, for instance, when the password field is empty or the username is less than four characters long, following error is shown:
The request sent by the client was syntactically incorrect.
Any idea how this might be fixed?
You have to modify the order of your arguments. Put the BindingResult result parameter always directly after the parameter with the #Valid annotation.
#RequestMapping(value = "login", method=RequestMethod.POST)
public String submitloginForm(#Valid loginData logindata, BindingResult result,
SessionStatus state, Model model)
This was even mentioned in this weeks This Week in Spring - March 5th, 2013 blog entry
Someone asked me this the other day and I felt like it was worthy of a
mention: in your Spring MVC #Controller class handler methods, make
sure that the BindingResult argument is immediately after the model or
command argument, like this: #RequestMapping(...) public String
handleRequest( #ModelAttribute #Valid YourCustomPojo attempt,
BindingResult result). In this example, handleRequest will validate
the POJO (YourCustomPojo) - checking the POJO for JSR303-annotations
and attempting to apply the constraints because the POJO is annotated
with #Valid - and stash any errors in the BindingResult, which it
makes available if we ask for it.
Spring will
0) determin the handler method
1) create an instance of loginData
2) populate it
3) validate it, and store the validation result in BindingResult
4) invoke the method (with loginData and BindingResult values), no matter whenever the binding Result contains an error or not