Thymeleaf not able to post collections / lists of objects - java

I'm creating a web-application with spring MVC, hibernate and thymeleaf.
I have a page where I can manage users, on this page you should be able to place and remove users from groups.
I am doing this with 2 multiple select boxes.
I added a jquery script what handles the movement of users from the one select box to the other one.
But when i submit, my Group.users object list is empty and I do not get any exceptions.
Does anyone has some advice?
Thanks in advance.
Edit
I just discovered that all thymeleaf attributes inside the html tag "option", aren't compiled. Except for the th:each attr.
So it's pretty clear the problem is in my thymeleaf file.
Thymeleaf / edit.html:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<div th:replace="template :: css"></div>
<title>Edit group</title>
</head>
<body>
<script>
$(document).ready(function() {
$(".clickable").click(function() {
if ($(this).hasClass("selected")) {
$(this).removeClass("selected").addClass("unselected");
$('#userGroupContainer').append(this);
$("option:selected").css("background-color", "red");
} else {
$(this).removeClass("unselected").addClass("selected");
$('#userGroupContainerSelected').append(this);
$("option:selected").css("background-color", "green");
}
});
});
</script>
<div id="bodyWrap">
<div th:replace="template :: logo">Logo</div>
<div th:replace="template :: nav">Nav</div>
<div th:replace="template :: messages">Header</div>
<div id="backGround">
<div id="contentWrap">
<form action="#{edit}"
th:action="#{${#httpServletRequest.servletPath}}"
th:object="${group}" th:method="post">
<h1 th:unless="${group.id}">Add group</h1>
<h1 th:if="${group.id}">Edit group</h1>
<hr />
<div th:replace="template :: messages">Header</div>
<div class="newFile">
<input type="hidden" th:field="*{id}" />
<table class="newFile">
<tr>
<th>Name:</th>
<td><input type="text" size="50" th:field="${group.name}" /></td>
</tr>
<tr>
<th>Description:</th>
<td><textarea th:field="${group.description}"></textarea></td>
</tr>
<tr>
<td> </td>
</tr>
</table>
<br /> users <br />
<select multiple="multiple" id="userGroupContainer">
<option th:each="u : ${userNotInGroup}" th:text="${u.displayName}" class="clickable unselected" th:value="${u}" ></option>
</select>
<!-- It's all about the select box under this comment -->
<select multiple="multiple" id="userGroupContainerSelected" th:field="*{users}">
<option th:each="ug, rowStat : ${group.users}" th:text="${ug.displayName}" th:value="${ug}" class="clickable selected">Selected</option>
</select>
<div class="form-actions">
<button th:unless="${group.id}" type="submit">Add</button>
<button th:if="${group.id}" type="submit">Update</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
example of the 2 multiple select boxes:
$(document).ready(function() {
$(".clickable").click(function() {
if ($(this).hasClass("selected")) {
$(this).removeClass("selected").addClass("unselected");
$('#userGroupContainer').append(this);
$("option:selected").css("background-color", "red");
} else {
$(this).removeClass("unselected").addClass("selected");
$('#userGroupContainerSelected').append(this);
$("option:selected").css("background-color", "green");
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
<select multiple="multiple" id="userGroupContainer">
<option class="clickable unselected" >Example</option>
</select>
<select multiple="multiple" id="userGroupContainerSelected" th:field="*{users}">
<option class="clickable selected">Selected</option>
</select>
Controller:
#RequestMapping(value = "/management/edit/{groupId}", method = RequestMethod.GET)
public String editGroup(ModelMap model, Principal principal, #PathVariable("groupId") Long groupId) {
Group group = groupService.findGroupById(groupId);
User user = new User();
List<User> userNotInGroup = userService.findUsersNotInGroup(group);
model.addAttribute("userNotInGroup", userNotInGroup);
model.addAttribute("group", group);
return "management/groups/edit";
}
#RequestMapping(value = "/management/edit/{groupId}", method = RequestMethod.POST)
public String editGroup(#Valid Group group, BindingResult result, Model model, #PathVariable("groupId") Long groupId) {
model.addAttribute("group", group);
System.out.println("USERS: " + group.getUsers());
groupService.saveGroup(group);
return "redirect:/management/list";
}
Group Entity / object:
#Entity
#Table(name = "GROUPS")
public class Group extends DomainObject {
private static final long serialVersionUID = ;
#Id
#GeneratedValue(generator = "GROUPS_SEQ", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "GROUPS_SEQ", sequenceName = "GROUPS_SEQ")
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "DESCRIPTION")
private String description;
#JoinTable(name = "USERS_GROUPS")
#ManyToMany(fetch = FetchType.EAGER)
private Collection<User> users;
#JoinTable(name = "GROUPS_ROLES")
#ManyToMany
private Collection<Role> roles;
public Collection<User> getUsers() {
return users;
}
public void setUsers(Collection<User> users) {
this.users = users;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Collection<Role> getRoles() {
return roles;
}
public void setRoles(Collection<Role> roles) {
this.roles = roles;
}
}
User Entity / Object:
#Entity
#Table(name = "USERS")
public class User extends DomainObject implements UserDetails {
private static final long serialVersionUID = ;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#NotNull
private Long ID;
#Column(name = "DISPLAY_NAME")
#NotNull
private String displayName;
#Column(name = "EMAIL_ADDRESS")
#NotNull
private String emailAddress;
#Column(name = "PASSWORD")
private String password;
#Column(name = "USERNAME")
#NotNull
private String username;
#Column(name = "LAST_LOGIN")
private Date lastLogin;
#Column(name = "MODIFIED_DATE")
private Date modifiedDate;
#Column(name = "MODIFIED_BY")
private String modifiedBy;
#Transient
private Collection<? extends GrantedAuthority> authorities;
private boolean admin;
#Nullable
#JoinTable(name = "USERS_GROUPS")
#ManyToMany
private Collection<Group> groups;
public Date getLastLogin() {
return lastLogin;
}
public void setLastLogin(Date lastLogin) {
this.lastLogin = lastLogin;
}
public Collection<Group> getGroups() {
return groups;
}
public void setGroups(Collection<Group> groups) {
this.groups = groups;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
#Override
public Long getId() {
return ID;
}
#Override
public void setId(Long id) {
this.ID = id;
}
}

Related

how to add image on the page

I want to add a picture to my table. but it does not appear on the page. I don’t get a single error. how can I specify the address of my image field in the index.html ?
the picture is uploaded to the database:
but not visible on page:
index.html
<body>
<div layout:fragment="content" class="py-5">
<div class="container">
<h2 style="text-align: center">Список ваших объявлений</h2>
<div class="col-md-12">
<table class="table">
<thead class="thead-light">
<tr>
<th>ID</th>
<th>Description</th>
<th>Price</th>
<th>Sold</th>
<th>Body</th>
<th>Brand</th>
<th>Engine</th>
<th>Model</th>
<th>Image</th>
</tr>
</thead>
<tbody>
<th:block th:each="order : ${orders}" th:remove="tag">
<tr>
<td th:text="${order.id}"></td>
<td th:text="${order.description}"></td>
<td th:text="${order.price}"></td>
<td th:text="${order.sold}"></td>
<td th:text="${order.body}"></td>
<td th:text="${order.brand}"></td>
<td th:text="${order.engine}"></td>
<td th:text="${order.model}"></td>
<td>
<a href="#" class="thumbnail">
<img th:src="#{/general/{id}/image(id = ${order.getId()})}" th:width="350" th:height="350"/>
</a>
</td>
</tr>
</th:block>
</tbody>
</table>
<p>
<a th:href="#{/general/showFormForAdd}" class="btn btn-outline-info btn-lg my-3">Новое объявление</a>
</p>
</div>
</div>
</div>
</body>
images located in Orders - byte[] image.
code below
#Entity #Table(name = "orders") public class Orders {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "description")
private String description;
#Column(name = "price")
private int price;
#Column(name = "sold")
private boolean sold;
#Column(name = "body")
private String body;
#Column(name = "brand")
private String brand;
#Column(name = "engine")
private String engine;
#Column(name = "model")
private String model;
#Lob
#Column(name = "image")
private byte[] image;
#Column(name = "str")
private String imageStr;
public Orders() {
}
public Orders(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isSold() {
return sold;
}
public void setSold(boolean sold) {
this.sold = sold;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getEngine() {
return engine;
}
public void setEngine(String engine) {
this.engine = engine;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public byte[] getImage() {
return image;
}
public void setImage(byte[] image) {
this.image = image;
}
public String getImageStr() {
return imageStr;
}
public void setImageStr(String imageStr) {
this.imageStr = imageStr;
} }
So you can do something like this
<img src="data:image/png;base64,${order.getImage()}">

Why is the controller saving the user with null property fields

Basically, Spring crashes with this error "value [null]; codes [NotNull.user.email,NotNull.email,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email]]; default message [must not be null], Field error in object 'user' on field 'firstName': rejected value [null]; codes" everytime I try to submit the completeProfile form. So, naturally, I looked at what data was saved after clicking submit on form. It turns out that the User object saved was having all the submitted property from the form as null. I checked inside the controller what was passed inside, all submitted properties from the form were null. Why does this happen?
User.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Size(min = 2, max = 80)
private String firstName;
#NotNull
#Size(min= 2, max = 80)
private String lastName;
#NotNull
#Email
private String email;
private Boolean enabled = false;
private String password = "";
private String role = "AUTHOR";
private String location = "";
private String topics = "";
private String job = "";
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getTopics() {
return topics;
}
public void setTopics(String topics) {
this.topics = topics;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
}
completeProfile.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>Complete your profile</p>
<form action="#" th:action="#{/completed-profile}" th:object="${user}" method="post">
<p>Email: <p th:text="${user.email}"></p>
<table>
<tr>
<td>Location:</td>
<td><input type="text" th:field="*{location}" /></td>
<td th:if="${#fields.hasErrors('location')}" th:errors="*{location}"></td>
</tr>
<tr>
<td>Job</td>
<td><input type="text" th:field="*{job}" /></td>
<td th:if="${#fields.hasErrors('job')}" th:errors="*{job}"></td>
</tr>
<tr>
<td>Topics of interest</td>
<td><input type="text" th:field="*{topics}" /></td>
<td th:if="${#fields.hasErrors('topics')}" th:errors="*{topics}"></td>
</tr>
<tr>
<td>Password</td>
<td><input type="password" th:field="*{password}" /></td>
<td th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></td>
<td th:text="${password_error}"></td>
</tr>
<tr>
<td><button type="submit">Submit</button></td>
</tr>
</table>
</form>
</body>
</html>
ProfileController.java
#Controller
public class ProfileController {
UserRepository userRepository;
public ProfileController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping(value = {"/profile", "/profile.html"}, method = RequestMethod.GET)
public String profileForm(){
return "auth/profile";
}
#RequestMapping(value = "/complete-profile", method = RequestMethod.GET)
public String completeProfileForm(){
return "auth/completeProfile";
}
#RequestMapping(value = "/completed-profile", method = RequestMethod.POST)
public String submitProfileForm(#Valid User user, BindingResult bindingResult, Model model){
System.err.println(user.getEmail());
if (user.getPassword() == null || user.getPassword().equals("")){
// model.addAttribute("password_error", "password cannot be null");
model.addAttribute("user", user);
return "auth/completeProfile";
}
if(bindingResult.hasErrors()){
System.out.println(bindingResult.getAllErrors());
return "auth/completeProfile";
}
userRepository.save(user);
return "auth/login";
}
}

Problems with form:options to using Spring mvc to one to many mapping

Hello I'm new with Spring and cannot find the mistake =( I'm using Spring MVC and want to select User bean for Client with one to many mapping. When I trying to save new Client I get a mistake.
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).
User Bean
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="surname")
private String surname;
#Column(name="name")
private String name;
#Column(name="second_name")
private String secondName;
#Column(name="phone_number")
private String phoneNumber;
#Column(name="type")
#Enumerated(EnumType.STRING)
private UserType type;
#OneToMany(mappedBy="user",
fetch = FetchType.LAZY
// , cascade= {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}
)
private List<Client> clients;
public User() {
}
public User(String surname, String name, String secondName, String phoneNumber, UserType type) {
this.surname = surname;
this.name = name;
this.secondName = secondName;
this.phoneNumber = phoneNumber;
this.type = type;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSecondName() {
return secondName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public UserType getType() {
return type;
}
public void setType(UserType type) {
this.type = type;
}
public List<Client> getClients() {
return clients;
}
public void setClients(List<Client> clients) {
this.clients = clients;
}
public void add(Client tempClient) {
if(clients == null) {
clients = new ArrayList<>();
}
clients.add(tempClient);
tempClient.setUser(this);
}
#Override
public String toString() {
return "User [id=" + id + ", surname=" + surname + ", name=" + name + ", secondName=" + secondName
+ ", phoneNumber=" + phoneNumber + ", type=" + type + "]";
}
Client Bean
#Entity
#Table(name="client")
public class Client {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="name")
private String name;
#Column(name="turnover")
private long turnover;
#ManyToMany(
//fetch = FetchType.LAZY,
//cascade= {CascadeType.PERSIST, CascadeType.MERGE,
// CascadeType.DETACH, CascadeType.REFRESH}
)
#JoinTable(
name="activity_client",
joinColumns = #JoinColumn(name="client_id"),
inverseJoinColumns=#JoinColumn(name="activity_id"))
private List<Activity> activities;
#ManyToOne(
//cascade= {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}
)
#JoinColumn(name="user_id")
private User user;
#Column(name="status")
#Enumerated(EnumType.STRING)
private ClientStatus status;
#OneToMany(mappedBy="client"
// , cascade= {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}
)
private List<ContactPerson> contactPersons;
#OneToMany(mappedBy="client"
// , cascade= {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}
)
private List<Action> actions;
public Client() {
}
public Client(String name, long turnover, ClientStatus status) {
this.name = name;
this.turnover = turnover;
this.status = status;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTurnover() {
return turnover;
}
public void setTurnover(long turnover) {
this.turnover = turnover;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public ClientStatus getStatus() {
return status;
}
public void setStatus(ClientStatus status) {
this.status = status;
}
public List<ContactPerson> getContactPersons() {
return contactPersons;
}
public void setContactPersons(List<ContactPerson> contactPersons) {
this.contactPersons = contactPersons;
}
public List<Action> getActions() {
return actions;
}
public void setActions(List<Action> actions) {
this.actions = actions;
}
public void add(ContactPerson tempContactPerson) {
if(contactPersons == null) {
contactPersons = new ArrayList<>();
}
contactPersons.add(tempContactPerson);
tempContactPerson.setClient(this);
}
public void add(Action tempAction) {
if(actions == null) {
actions = new ArrayList<>();
}
actions.add(tempAction);
tempAction.setClient(this);
}
public void add(Activity tempActivity) {
if(activities==null) {
activities = new ArrayList<>();
}
activities.add(tempActivity);
}
public List<Activity> getActivities() {
return activities;
}
public void setActivities(List<Activity> activities) {
this.activities = activities;
}
#Override
public String toString() {
return "Client [id=" + id + ", name=" + name + ", turnover=" + turnover + ", status=" + status + "]";
}
}
Controller for Client
#Controller
#RequestMapping("/client")
public class ClientController {
#Autowired
private ClientService clientService;
#Autowired
private UserService userService;
#GetMapping("/list")
public String listClients(Model theModel) {
List<Client> theClients = clientService.getClients();
theModel.addAttribute("clients", theClients);
return "client/list-clients";
}
#GetMapping("/showFormForAdd")
public String showFormForAdd(Model theModel) {
Client theClient = new Client();
theModel.addAttribute("client", theClient);
theModel.addAttribute("clientStatus", ClientStatus.values());
List<User> users = userService.getUsers();
theModel.addAttribute("clientUser", users);
return "client/client-form";
}
#PostMapping("/saveClient")
public String saveClient(#ModelAttribute("client") Client theClient) {
clientService.saveClient(theClient);
return "redirect:/client/list";
}
#GetMapping("/showFormForUpdate")
public String showFormForUpdate(#RequestParam("clientId") int theId, Model theModel) {
Client theClient = clientService.getClient(theId);
theModel.addAttribute("client", theClient);
theModel.addAttribute("clientType", ClientStatus.values());
return "client/client-form";
}
#GetMapping("/delete")
public String deleteClient(#RequestParam("ClientId") int theId) {
clientService.deleteClient(theId);
return "redirect:/client/list";
}
}
JSP for Client
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<title> Save Client</title>
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/style.css"/>
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/add-target-style.css"/>
</head>
<body>
<div id="wrapper">
<div id="header">
<h2>CRM - Customer Relationship Manager</h2>
</div>
</div>
<div id="container">
<h3>Save User</h3>
<form:form action="saveClient" modelAttribute="client" method="POST">
<form:hidden path="id"/>
<table>
<tbody>
<tr>
<td><label> Name:</label></td>
<td><form:input path="name"/></td>
</tr>
<tr>
<td><label> Turnover:</label></td>
<td><form:input path="turnover"/></td>
</tr>
<tr>
<td><label> Manager:</label></td>
<td>
<form:select path="user">
<form:options items="${clientUser}" itemValue="id" itemLabel="surname" />
</form:select>
</td> </tr>
<tr>
<td><label> Status:</label></td>
<td>
<form:select path="status">
<form:options items="${clientStatus}" itemLabel="status" />
</form:select>
</td>
</tr>
<tr>
<td><label> </label></td>
<td><input type="submit" value="Save" class="save"/></td>
</tr>
</tbody>
</table>
</form:form>
<div style="clear; both;"></div>
<p>
Back to List
</p>
</div>
</body>
</html>
If I comment next peace of code its work OK
<tr>
<td><label> Manager:</label></td>
<td>
<form:select path="user">
<form:options items="${clientUser}" itemValue="id"
itemLabel="surname" />
</form:select>
/td>
I try to search this question in internet but example of code looks like the same as my. Please help me to find the mistake ;)
This I have in Tomcat logs
127.0.0.1 - - [20/Nov/2017:12:24:56 +0200] "GET / HTTP/1.1" 404 1073 0:0:0:0:0:0:0:1 - - [20/Nov/2017:12:25:05 +0200] "GET /crmdemo/
HTTP/1.1" 302 - 0:0:0:0:0:0:0:1 - - [20/Nov/2017:12:25:12 +0200] "GET
/crmdemo/target/list HTTP/1.1" 200 944 0:0:0:0:0:0:0:1 - -
[20/Nov/2017:12:25:28 +0200] "GET /crmdemo/client/list HTTP/1.1" 200
652 0:0:0:0:0:0:0:1 - - [20/Nov/2017:12:25:30 +0200] "GET
/crmdemo/client/showFormForAdd HTTP/1.1" 200 1726 0:0:0:0:0:0:0:1 - -
[20/Nov/2017:12:25:30 +0200] "GET
/crmdemo/resources/css/add-target-style.css HTTP/1.1" 304 -
0:0:0:0:0:0:0:1 - - [20/Nov/2017:12:25:38 +0200] "POST
/crmdemo/client/saveClient HTTP/1.1" 400 1130
I find an answer. I need to put .id in path
<td>
<form:select path="user.id">
<form:options items="${clientUser}" itemValue="id"
itemLabel="surname" />
</form:select>
</td>

Spring binding checkbox with Set of Objects

I would like to build a form to save an User. I have 2 tables, User and UserRole as described in Spring Security. To save an user I need to create a User Object that contains a field Set. I want to create a list of checkboxes and map them into an Set of UserRoles, but I don't know how to map them with checkboxes. I have the following classes:
#Controller
public class UserController {
#Autowired
IUserService userService;
/** Default GET form handler for users, in submission will call saveRegistration */
#RequestMapping(value="/createuser", method=RequestMethod.GET)
public String createUser(Model model) {
// User form will be bind to this User object
model.addAttribute("user", new User());
// Code about adding the user roles to JSP?
// Maybe something like this?:
// User u = new User ("useruser","123456",false);
// Set<UserRole> roles = new HashSet<UserRole>();
// roles.add(new UserRole(u,"ROLE_ADMIN"));
// roles.add(new UserRole(u,"ROLE_USER"));
// model.addAttribute("roles", roles);
return "createuser";
}
/** This method will be called on form submission, handling POST request,
* It also validates the user input */
#RequestMapping(value="/createuser", method=RequestMethod.POST)
public String doCreateUser(Model model, #Valid User user, BindingResult result) {
if(result.hasErrors()) {
return "createuser";
}
userService.createUser(user,user.getUserRole()) //createUser(User user, Set<UserRole> role)
return "success";
}
}
My JSP:
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%# taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<link rel="stylesheet" type="text/css" href="resources/css/createuser.css" />
</head>
<body onload='document.createUserForm.username.focus();'>
<sf:form name="createUserForm" method="post"
action="${pageContext.request.contextPath}/createuser"
commandName="user">
<table class="formtable">
<tr>
<td class="label">Username:</td>
<td><sf:input class="control" path="username" name="username"
type="text" /><br />
<div class="error">
<sf:errors path="username"></sf:errors>
</div></td>
</tr>
<tr>
<td class="label">Role:</td>
<td>
<ul>
<sf:checkboxes element="li" path="userRole" items="${roles}"></sf:checkboxes>
</ul></td>
</tr>
<tr>
<td class="label">Password:</td>
<td><sf:input class="control" path="password" name="password"
type="password" />
<div class="error">
<sf:errors path="password"></sf:errors>
</div></td>
</tr>
<tr>
<td class="label">Confirm Password:</td>
<td><input class="control" name="confirmpass" type="password" />
<div class="error">
<sf:errors path="password"></sf:errors>
</div></td>
</tr>
<tr>
<td class="label"></td>
<td><input class="control" value="Create account" type="submit" /></td>
</tr>
</table>
</sf:form>
User Role:
public class UserRole {
private Integer userRoleId;
private User user;
private String role;
public UserRole () {
}
public UserRole(User user, String role) {
this.user = user;
this.role = role;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "user_role_id",
unique = true, nullable = false)
public Integer getUserRoleId() {
return userRoleId;
}
public void setUserRoleId(Integer userRoleId) {
this.userRoleId = userRoleId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "username", nullable = false)
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#Column(name = "role", nullable = false, length = 45)
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String toString () {
return role;
}
}
And User:
public class User {
#NotNull
#NotBlank(message="Username cannot be blank.")
#Size(min=4, max=15, message="Username must be between 4 and 15 characters long.")
#Pattern(regexp="^\\w{6,}$", message="Username can only consist of numbers, letters and the underscore character.")
private String username;
#NotBlank(message="Password cannot be blank.")
#Pattern(regexp="^\\S+$", message="Password cannot contain spaces.")
#Size(min=6, message="Username must be longer than 6 characters.")
private String password;
//private String confirmPassword;
private boolean enabled;
private Set<UserRole> userRole = new HashSet<UserRole>(0);
public User() {
}
public User(String username, String password, boolean enabled) {
this.username = username;
this.password = password;
this.enabled = enabled;
}
public User(String username, String password,
boolean enabled, Set<UserRole> userRole) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.userRole = userRole;
}
#Id
#Column(name = "username", unique = true, nullable = false, length = 45)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "password", nullable = false, length = 60)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Column(name = "enabled", nullable = false)
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
public Set<UserRole> getUserRole() {
return userRole;
}
public void setUserRole(Set<UserRole> userRole) {
this.userRole = userRole;
}
/*public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}*/
}
Thanks in advance.
Refert this post, and i dont see the itemlabel and itemid attribute, which maps to userrole object.
also refer post
Found solution using Properties Editor. Here and here

thymeleaf binding collections

I have a problem with binding collections using spring and thymeleaf. Every time I send form, my object collections are set to null (User.postions), my example below:
My Controller:
#RequestMapping(value = urlFragment + "/add", method = RequestMethod.GET)
public String addPosition(Model model) {
HashSet<Position> positions = new HashSet<Position>(positionRepository.findByEnabledTrueOrderByNameAsc());
User employee = new User();
for (Position position : positions) {
employee.addPosition(position);
}
model.addAttribute("employee", employee);
return "crud/employee/add";
}
#RequestMapping(value = urlFragment + "/add", method = RequestMethod.POST)
public String processNewEmployee(Model model, #Valid #ModelAttribute("employee") User employee, BindingResult result) {
String templatePath = "crud/employee/add";
if (!result.hasErrors()) {
userRepository.save(employee);
model.addAttribute("success", true);
}
return templatePath;
}
And my employee form:
<form action="#" th:action="#{/panel/employee/add}" th:object="${employee}" method="post">
<div class="row">
<div class="col-md-6">
<label th:text="#{first_name}">First name</label>
<input class="form-control" type="text" th:field="*{userProfile.firstName}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label th:text="#{last_name}">Last name</label>
<input class="form-control" type="text" th:field="*{userProfile.lastName}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label th:text="#{email}">Email</label>
<input class="form-control" type="text" th:field="*{email}"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label th:text="#{position}">Position</label>
<select th:field="*{positions}" class="form-control">
<option th:each="position : *{positions}"
th:value="${position.id}"
th:text="${position.name}">Wireframe
</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-5">
<div class="checkbox">
<button type="submit" class="btn btn-success" th:text="#{add_employee}">
Add employee
</button>
</div>
</div>
</div>
</form>
User entity:
#Entity
#Table(name="`user`")
public class User extends BaseModel {
#Column(unique = true, nullable = false, length = 45)
private String email;
#Column(nullable = false, length = 60)
private String password;
#Column
private String name;
#Column
private boolean enabled;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "user_role",
joinColumns = {#JoinColumn(name = "user_id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "role_id", nullable = false)}
)
private Collection<Role> roles = new HashSet<Role>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "user_position",
joinColumns = {#JoinColumn(name = "user_id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "position_id", nullable = false)}
)
private Collection<Position> positions = new HashSet<Position>();
public User() {
}
public User(String email, String password, boolean enabled) {
this.email = email;
this.password = password;
this.enabled = enabled;
}
public User(String email, String password, boolean enabled, Set<Role> roles) {
this.email = email;
this.password = password;
this.enabled = enabled;
this.roles = roles;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Collection<Position> getPositions() {
return positions;
}
private void setPositions(Collection<Position> positions) {
this.positions = positions;
}
public boolean addPosition(Position position) {
return positions.add(position);
}
public boolean removePosition(Position position) {
return positions.remove(position);
}
public Collection<Role> getRoles() {
return roles;
}
private void setRoles(Collection<Role> roles) {
this.roles = roles;
}
public boolean addRole(Role role) {
return roles.add(role);
}
public boolean removeRole(Role role) {
return roles.remove(role);
}
#Override
public String toString() {
return User.class + " - id: " + getId().toString() + ", email: " + getEmail();
}
}
I have read somewhere that I have to create equals() and hashCode(), so I did it in my Position Entity.
public boolean equals(Position position) {
return this.getId() == position.getId();
}
public int hashCode(){
return this.getId().hashCode() ;
}
Here are data sent by post method:
And here is my result:
My spring version: 4.1.6.RELEASE
thymeleaf-spring4 version: 2.1.4.RELEASE
thymeleaf-layout-dialect version: 1.2.8
O course I wish positions to were HashCode with one element of object Position with id = 2.
Could you help me? What I am doing wrong?
It's because you're using ${position.id} for your option value. This means spring can't work out the relationship between the id used in the value and the actual Position objects. Try just ${position} for your value and it should work:
<select th:field="*{positions}" class="form-control">
<option th:each="position : *{positions}"
th:value="${position}"
th:text="${position.name}">Wireframe
</option>
</select>
(Make sure you've implemented hashCode and equals on your Position class)
If that still doesn't work you might have to implement a Formatter for Position, to make the conversion explicit. See this example thymeleafexamples-selectmultiple.
I had similar problem that I resolved by adding Formatter class and adding Formatter to the configuration of the MVC:
#Override
protected void addFormatters(FormatterRegistry registry){
registry.addFormatter(new PositionFormater());
...
}
and Position class formatter should look something like this:
PositionFormatter:
public class PositionFormatter implements Formatter<Position>{
/** String representing null. */
private static final String NULL_REPRESENTATION = "null";
#Resource
private PositionRepository positionRepository;
public PositionFormatter() {
super();
}
#Override
public String print(Position position, Locale locale) {
if(position.equals(NULL_REPRESENTATION)){
return null;
}
try {
Position newPosition = new Position();
newPosition.setId(position.getId());
return newPosition.getId().toString();
} catch (NumberFormatException e) {
throw new RuntimeException("Failed to convert `" + position + "` to a valid id");
}
}
#Override
public Position parse(String text, Locale locale) throws ParseException {
if (text.equals(NULL_REPRESENTATION)) {
return null;
}
try {
Long id = Long.parseLong(text);
Position position = new Position();
position.setId(id);
return position;
} catch (NumberFormatException e) {
throw new RuntimeException("Failed to convert `" + text + "` to valid Position");
}
}
}
In my case, these two solved all of the problems. I have several formatters, all I do is make one and add it to the config file (WebMVCConfig in my case)
Check my original post where I resolved this problem
Thanks Guys for answering my question. You help me a lot. Unfortunately I have to disagree with you in one thing. You have shown me example with:
newPosition.setId(position.getId());
The same example was in Andrew github repository. I think that this is bad practice to use setId() method. So I will present my solution and I will wait for some comments before I will mark it as an answer.
WebMvcConfig Class
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.smartintranet")
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#PersistenceContext
private EntityManager entityManager;
// (....rest of the methods.......)
#Override
public void addFormatters(FormatterRegistry formatterRegistry) {
formatterRegistry.addFormatter(new PositionFormatter(entityManager));
}
}
PositionFormatter class
public class PositionFormatter implements Formatter<Position> {
private EntityManager entityManager;
public PositionFormatter(EntityManager entityManager) {
this.entityManager = entityManager;
}
public String print(Position position, Locale locale) {
if(position.getId() == null){
return "";
}
return position.getId().toString();
}
public Position parse(String id, Locale locale) throws ParseException {
return entityManager.getReference(Position.class, Long.parseLong(id));
}
}
employeeForm.html
<div class="col-md-6">
<label th:text="#{position}">Position</label>
<select th:field="*{position}" class="form-control">
<option th:each="position : ${allPositions}"
th:value="${position.id}"
th:text="${position.name}">Wireframe
</option>
</select>
</div>
And last one, EmployeeController Class
#Controller
public class EmployeeController extends AbstractCrudController {
// (...rest of dependency and methods....)
#Transactional
#RequestMapping(value = urlFragment + "/create", method = RequestMethod.GET)
public String createNewEmployee(Model model) {
prepareEmployeeForm(model);
return "crud/employee/create";
}
#Transactional
#RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
public String processNewEmployee(Model model, #ModelAttribute("employee") Employee employee, BindingResult result) {
if (!result.hasErrors()) {
// Look here it is important line!
entityManager.merge(employee.getUser());
}
prepareEmployeeForm(model);
return "crud/employee/create";
}
}
It is my solution. What is bad here? I think that line:
entityManager.merge(employee.getUser());
I can't use here:
userRepository.save(employee.getUser());
Because Position entity is detached, and when I use save method it runs in this situation em.persist() so I ran manually em.merge(). I know that this code is not perfect but I think that this solution is better then use setId(). I will be grateful for constructive critic.
One more time thanks Andrew and Blejzer for help without you I would not do it. I have marked yours answer as useful.

Categories