I try to display a part of gallery's photo=> this is result when i did the search action. This result have avatar like a picture and infomation like username or email.
I create Photo.java like child of Users.java in relationship #ManytoOne
Here is my code :
Photo.java----
#Entity
public class Photo extends Model{
#Id
public Long id;
public String path;
#ManyToOne
#JoinColumn(name = "user_id")
public Users user;
}
Users.java-----
#Entity
public class Users extends Model{
#Id
public Long id;
#Constraints.Required
public String username;
#Constraints.Required
public String email;
#Constraints.Required
public String password;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
public List<Photo> photo = new ArrayList<Photo>();
public Users(){}
public Users(String username,String email,String password){
this.username=username;
this.email=email;
this.password=password;
}
}
Search.java -----
public static Result search(){
DynamicForm form = form().bindFromRequest();
String name = form.get("name");
Finder<Long, Users> find = new Finder<Long, Users>(Long.class, Users.class);
List<Users> users = find.where().like("username", '%'+ name +'%').findList();
if (form.get("name")=="" || users.isEmpty() || users==null){
return ok(search_again.render());
}
else{
return ok (search_result.render(users));
}
}
search_result.scala.html----
#(users : List[Users])
#main(nav= "search"){
<h3>Result</h3>
<input class="button" type="button" value="Back to Search">
<input class="button" type="button" value="Back to Home">
<p>Found #users.size() result(s) : </p>
<div class="sresult">
#for(user <- users){
<div id="sresult">
<div id="haha"><img src="#routes.Assets.at("upload/"+user.photo.path)"></div>
//Error here. Why "user.photo.path" not working ?
<p>
#user.username</a></br>
#user.password</a></br>
#user.email</a>
</p>
</div>
}
</div>
}
Why "user.photo.path" not working ? any ideal in my case ?
Related
I`ve ran into a problem with controller/vm data transfer and could not find any solution.
Ive got a User (see class below)
package com.Entity;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.*;
import java.util.Date;
#Entity
#Transactional
#Table(name = "USERS")
public class User {
private Long id;
private UserType type;
private String email;
private String password;
private String name;
private String tel;
private Date regDate;
private Date lastActive;
private Agent office;
//Constructors
public User(){
}
public User(UserType type, String email, String password, String name, String tel, Agent office) {
this.type = type;
this.email = email;
this.password = password;
this.name = name;
this.tel = tel;
this.regDate = new Date();
this.lastActive = null;
this.office = office;
}
//Getters
#Id
#SequenceGenerator(name = "USERID_SEQ", sequenceName = "USERID_SEQ",allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USERID_SEQ")
#Column(name = "ID")
public Long getId() {
return id;
}
#Column(name = "TYPE")
public UserType getType(){
return type;
}
#Column(name = "EMAIL")
public String getEmail() {
return email;
}
#Column(name = "PASSWORD")
public String getPassword() {
return password;
}
#Column(name = "NAME")
public String getName() {
return name;
}
#Column(name = "TEL")
public String getTel() {
return tel;
}
#Column(name = "DATE_REG")
public Date getRegDate() {
return regDate;
}
#Column(name = "LAST_ACTIVE")
public Date getLastActive() {
return lastActive;
}
#ManyToOne (targetEntity = Agent.class, fetch = FetchType.EAGER)
#JoinColumn(name = "OFFICEID")
public Agent getOffice() {
return office;
}
// Setters
}
Controller for it
package com.Controllers;
import com.Entity.AgentType;
import com.Entity.User;
import com.Services.AgentService;
import com.Services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
//TODO: TEST CONTROLLER SUBJECT TO DELETE
#Controller
public class ViewController {
#Autowired
private UserService userService;
#Autowired
private AgentService agentService;
#RequestMapping(value = "/list", method = RequestMethod.GET)
public ModelAndView listUsersPage(){
List<User>list = userService.getAll();
return new ModelAndView("fragments/userss.vm","users",list);
}
#RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
public ModelAndView edit(#PathVariable Long id){
return new ModelAndView("fragments/edit.vm",
"user", (User)userService.getById(id));
}
//FUNCTIONAL
#RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
public ModelAndView delete(#PathVariable Long id){
userService.delete(userService.getById(id));
return new ModelAndView("redirect:/list");
}
#RequestMapping(value = "/update", method = RequestMethod.POST)
public ModelAndView update(User user){
User user1 = user;
//userService.update(user1);
return new ModelAndView("redirect:/list");
}
//Model Attributes
#ModelAttribute
public void userTypesList(Model model){
model.addAttribute("types", userService.getPositions());
}
#ModelAttribute
public void officesList(Model model){
model.addAttribute("offices", agentService.getAllByType(AgentType.OFFICE));
}
}
and a pages (.vm) to add new or edit existing users(just one example the edit page):
<title>EDIT USER</title>
<body>
<form method="post" action="/update">
id:
<input type="text" name="id" path="id" value="$user.id"/> <br>
Type:
<select name="type" path="type">
<option hidden selected>$user.type</option>
#foreach($type in $types)
<option value="$type">$type</option>
#end
</select> <br>
e-mail:
<input type="text" name="email" path="email" value="$user.email"/> <br>
Password:
<input type="text" name="password" path="password" value="$user.password"/> <br>
Name:
<input type="text" name="name" path="name" value="$user.name"/> <br>
Tel:
<input type="text" name="tel" path="tel" value="$user.tel"/> <br>
Reg Date:
<input type="date" name="regDate" path="regDate" value="$user.regDate"/> <br>
Last Active:
<input type="date" name="lastActive" path="lastActive" value="$user.lastActive"/> <br>
Office:
<select name="office" path="office">
<option hidden selected value="$user.office">$user.office.name</option>
#foreach($office in $offices)
<option value="$office">$office.name</option>
#end
</select> <br>
<input type="submit" value="Update"/>
</form>
</body>
The problem is that I cant manage to save the updated User via /update(User user). Ive tried different ways, but still no success.
Whis this code I`m getting HTTP Status 400 – Bad Request. 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).
Could you please help me out? What is wrong with it?
In your code you are missing a couple of things.
Frist of all, you miss the model attribute specified in the form:
<form method="post" action="/update" modelAttribute="user">
Second, you are missing the model attribute specified in the post method:
#RequestMapping(value = "/update", method = RequestMethod.POST)
public ModelAndView update(#ModelAttribute("user") User user){
User user1 = user;
userService.update(user1);
return new ModelAndView("redirect:/list");
}
If you need further details, you can read Getting Started with Forms in Spring MVC
i've got this error on my project.
HTTP Status 400 – Bad Request.
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).
InsertClient.jsp
<form:form action="/moldar/saveCliente/" method="POST" modelAttribute="cliente">
<div class="form-group">
<label for="exampleSelect1">Pais</label>
<form:select path="pais" class="form-control" id="pais" name="wwww">
<c:forEach var="lista" items="${listaPaises}">
<option value="${lista.id}">${lista.nome}</option>
</c:forEach>
</form:select>
</div>
<div class="form-group">
<label for="exampleSelect1">Estado</label>
<form:select path="estado" class="form-control" id="estado" name="qqqqq">
<option value="">Selecione um estado</option>
<c:forEach var="lista" items="${listaEstados}">
<option value="${lista.id}">${lista.nome}</option>
</c:forEach>
</form:select>
</div>
<div class="form-group">
<label for="exampleSelect1">Cidade</label>
<form:select path="cidade" class="form-control" id="cidade" name="batata">
<option value="null">Selecione uma cidade</option>
<c:forEach var="lista" items="${listaCidades}">
<option value="${lista.id}">${lista.nome}</option>
</c:forEach>
</form:select>
</div>
My Controllers
#RequestMapping(value = "/saveCliente", method = RequestMethod.POST)
public String adicionarCliente(#ModelAttribute("cliente") Cliente cliente) {
clienteDao.saveOrUpdate(cliente);
return "redirect:/clientes";
}
#RequestMapping(value = "/addCliente", method = RequestMethod.GET, headers = "Accept=application/json")
public String inserirCliente(Model model) {
List<Estado> listaEstados = estadoDao.list(null);
List<Cidade> listaCidades = cidadeDao.list(null);
List<Pais> listaPaises = paisDao.list(null);
model.addAttribute("listaPaises", listaPaises);
model.addAttribute("listaEstados", listaEstados);
model.addAttribute("pais", new Pais());
model.addAttribute("cliente", new Cliente());
model.addAttribute("estado", new Estado());
model.addAttribute("cidade", new Cidade());
model.addAttribute("listaCidades", listaCidades);
return "inserirCliente";
}
Model
#Entity
#Table (name= "clientes")
public class Cliente {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
#Column
private Integer id;
#Column
private String nome;
#Column
private String cpf;
#OneToOne
#JoinColumn (name="pais")
private Pais pais;
#OneToOne
#JoinColumn (name="estado")
private Estado estado;
#OneToOne
#JoinColumn (name="cidade")
private Cidade cidade;
#Column
private String logradouro;
#Column
private String numero;
#Column
private String cep;
#Column
private String email;
I have trying everything.
Can someone help me ? Please ?
In your Contoller this line of code
public String adicionarCliente(#ModelAttribute("cliente") Cliente cliente)
In your Cliente class the variables like pais
<form:select path="pais" class="form-control" id="pais" name="wwww">
you cannot bind this path="pais" to Cliente class variable pais , Because pais is not a String Type it is an Object type.
You can Create another class to Bind the input Selection Options to the variables.
class Client {
private String pais;
private String estado;
private String cidade;
public String getPais() {
return pais;
}
public void setPais(String pais) {
this.pais = pais;
}
public String getEstado() {
return estado;
}
public void setEstado(String estado) {
this.estado = estado;
}
public String getCidade() {
return cidade;
}
public void setCidade(String cidade) {
this.cidade = cidade;
}
}
I have a project based in Spring Web model-view-controller (MVC) framework. The version of the Spring Web model-view-controller (MVC) framework is 3.2.8.
This class
public class DeviceForm {
Device device;
List<String> selectedItems = Collections.emptyList();
public DeviceForm() {
super();
}
public Device getDevice() {
return device;
}
public void setDevice(Device device) {
this.device = device;
}
public List<String> getSelectedItems() {
return selectedItems;
}
public void setSelectedItems(List<String> selectedItems) {
this.selectedItems = selectedItems;
}
}
and this
public class Device implements java.io.Serializable {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CRITERIA")
private BaseCriteria criteria;
public BaseCriteria getCriteria() {
return criteria;
}
public void setCriteria(BaseCriteria criteria) {
this.criteria = criteria;
}
}
and this
#Entity
#Table(name = "CRITERIA")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING)
#SequenceGenerator(name = "seqCriteria", sequenceName = "SEQ_CRITERIA", allocationSize = 1)
public abstract class BaseCriteria {
public BaseCriteria() {
super();
}
private Long id;
private String code;
private Date adoptionDate;
private Date expirationDate;
#Transient
public abstract String getGroupKey();
#Transient
public abstract Long getGroupId();
#Transient
public abstract String getRefColumnName();
#Id
#Column(name = "ID", unique = true, nullable = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqCriteria")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "CODE")
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
#Column(name = "ADOPTION_DATE")
#Temporal(TemporalType.TIMESTAMP)
public Date getAdoptionDate() {
return adoptionDate;
}
public void setAdoptionDate(Date adoptionDate) {
this.adoptionDate = adoptionDate;
}
#Column(name = "EXPIRATION_DATE")
#Temporal(TemporalType.TIMESTAMP)
public Date getExpirationDate() {
return expirationDate;
}
#Transient
public boolean isExpired() {
return getExpirationDate().before(new Date());
}
public void setExpirationDate(Date expirationDate) {
this.expirationDate = expirationDate;
}
#Override
public String toString() {
return "BaseCriteria [id=" + id + ", code=" + code + ", adoptionDate="
+ adoptionDate + ", expirationDate=" + expirationDate + "]";
}
}
and the JSP
<form:form commandName="deviceForm"
name="deviceForm"
id="deviceFormId"
method="post"
action="${contextPath}/newdesign/manage/device/${deviceForm.device.id}"
htmlEscape="yes">
<div class="col-sm-6 text-right">
<button class="btn btn-primary" type="submit">Save device</button>
</div>
</div>
<c:forEach items="${deviceForm.device.productGroup.criteria}" var="criteria">
<div class="row">
<div class="col-md-3">
<form:radiobutton path="device.criteria.id" value="${criteria.id}"/>
<label for="basic-url">Criteria:</label>
<input value="${criteria.code}" disabled="disabled" class="form-control"/>
</div>
<div class="col-md-3">
<label for="basic-url">Adoption date:</label>
<input value="<fmt:formatDate type="date" value="${criteria.adoptionDate}" />" disabled="disabled" class="form-control"/>
</div>
<div class="col-md-3">
<label for="basic-url">Expiration Date:</label>
<input value="<fmt:formatDate type="date" value="${criteria.expirationDate}" />" disabled="disabled" class="form-control"/>
</div>
</div>
</c:forEach>
</form:form>
The controller:
/**
* #throws Exception
*
*/
#RequestMapping(value = { "/newdesign/manage/device/{appId}",
"/newdesign/manage/device/{appId}/"}, method = {RequestMethod.GET})
public String viewDevicesWithStatus(
#ModelAttribute("deviceForm") DeviceForm deviceForm,
#PathVariable Long appId,
HttpServletRequest request,
Model model ) throws Exception {
Device device = manageLicenseService.getDeviceById(appId, true);
if (device.getCriteria()==null) {
device.setCriteria(device.getProductGroup().getCriteria().get(0));
}
deviceForm.setDevice(device);
fillModel (model, request, device);
return "cbViewDeviceInfo";
}
/**
* #throws Exception
*
*/
#RequestMapping(value = { "/newdesign/manage/device/{appId}",
"/newdesign/manage/device/{appId}/"}, method = {RequestMethod.POST})
public String saveDevicesWithStatus(
#ModelAttribute("deviceForm") DeviceForm deviceForm,
#PathVariable Long appId,
HttpServletRequest request,
Model model ) throws Exception {
Device device = manageLicenseService.getDeviceById(deviceForm.getDevice().getId());
if (device.getCriteria()==null) {
device.setCriteria(device.getProductGroup().getCriteria().get(0));
}
//TODO: audit
device.updateDevice(deviceForm.getDevice());
manageLicenseService.saveDevice(device);
if (device.getCriteria()==null) {
device.setCriteria(device.getProductGroup().getCriteria().get(0));
}
deviceForm.setDevice(device);
fillModel (model, request, device);
return "cbViewDeviceInfo";
}
But I got following error when I submitted the form, on GET method I got same page without error
org.springframework.beans.NullValueInNestedPathException: Invalid property 'device.criteria' of bean class [com.tdk.iot.controller.newdesign.manage.DeviceForm]: Could not instantiate property type [com.tdk.iot.domain.criteria.BaseCriteria] to auto-grow nested property path: java.lang.InstantiationException
You get the error because in your form you have this:
<form:radiobutton path="device.criteria.id" value="${criteria.id}"/>
and in your POST handler you have this:
public String saveDevicesWithStatus(#ModelAttribute("deviceForm") DeviceForm deviceForm){
}
which means that the MVC framework will try to automatically set the property
deviceForm.device.criteria.id.
Now, because there is no existing DeviceForm in any scope then it will create a new one and of course device.getCriteria() returns null,
hence the exception.
You may think that the DeviceForm you created and populated in the GET handler will be used however Spring MVC is stateless so you
would need to store it in Session scope between requests for it to be re-used or otherwise rework your logic.
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method-args
.... Given the above example where can theinstance come from? There
are several options.....[in the absence of any other option] It may
be instantiated using its default constructor
A better approach however is to change your form to be as below:
<form:radiobutton path="device.criteria" value="${criteria.id}"/>
and register a converter that would convert the submitted parameter and bind the corresponding entity instance.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert
#Component
public class StringToCriteriaConverter implements Converter<String, BaseCriteria> {
#Autowired
private CriteriaService service;
//source is the ID passed by the page
public BaseCriteria convert(String source) {
// lookup and return item with corresponding ID from the database
}
}
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;
}
}
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.