Write value from Select in DTO Thymeleaf - java

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}">

Related

How to create a custom message/error message in Spring controller and display it in template?

I am trying to create a custom error message in my controller and then pass it into the HTML template if the email is already found in the database.
The controller method does work and it will redirect the user to "registration-form" if a duplicate email is used, but the error message is not displayed on the web page as I intend it to be.
I basically want the error message to appear on the screen when the user cannot register with that email.
In HTML form:
<p th:text="*{errorMessage}"></p>
In Controller:
#PostMapping("/process-register")
private String processRegistrationForm(Model model, WebUser webUser) {
String email = webUser.getEmail();
if(webUserRepo.findByEmail(email) == null) {
webUser.setRole("user");
WebUser savedWebuser = webUserRepo.save(webUser);
model.addAttribute("userID", savedWebuser.getUserID());
String userID = String.valueOf(savedWebuser.getUserID());
return "redirect:/welcome/" + userID;
}
else {
String errorMessage = "This email address is already registered, please login to your existing account.";
model.addAttribute(errorMessage);
return "registration-form";
}
}
}
When your original request is not cached (i.e. is a POST, DELETE, PUT, ... operation) you can respond with any MVC flow, simply be sure to not use the path for "weird" things (neither server nor client side).
Then, if the email exists put the message into the model and redirect to the remember password (or login ...).
So, you can prepare your RememberController to be called from others controllers decoupling internal state and logic:
#Controller
#RequestMapping("/remember")
public class RememberController {
#Getter
#Setter
#AllArgsConstructor
public static class RememberModel {
private String email;
}
// this method decouple the RememberController knowledge (i.e. merge, remove, get messages, ... needed by this view)
public String redirect(Model model, String email) {
model.addAttribute("form", new RememberModel(email));
return get(model);
}
#GetMapping
public String get(Model model) {
if (model.getAttribute("form") == null)
model.addAttribute("form", new RememberModel(""));
return "remember";
}
#PostMapping
public String post(#ModelAttribute(name = "form") RememberModel form, Model model) {
model.addAttribute("info", "password sent to " + form.getEmail() + "!");
return "remember";
}
}
Now, your RegisterController can redirect with any required information (i.e. the error message and the user email to not to rewrite twice):
#Controller
#RequestMapping("/register")
public class RegisterController {
#Getter
#Setter
#AllArgsConstructor
public static class RegisterModel {
private String email;
}
#Autowired
protected RememberController rememberController;
#GetMapping
public String get(Model model) {
if(model.getAttribute("form") == null)
model.addAttribute("form", new RegisterModel(""));
return "register";
}
#PostMapping
public String post(#ModelAttribute(name = "form") RegisterModel form, Model model) {
// ... when email exists ...
model.addAttribute("error", "e-mail `" + form.getEmail() + "` exists!");
return rememberController.redirect(model, form.getEmail());
}
}
Then, the user UI works as expected:
GET
POST and server redirecto to remember controller
Then user simply click:
The views are:
<!doctype html>
<html th:attr="lang=en" xmlns:th="http://www.w3.org/1999/xhtml">
<body>
<h1>REGISTER FORM</h1>
<div style="color: red" th:if="${error != null}" th:text="${error}"></div>
<div style="color: green" th:if="${info != null}" th:text="${info}"></div>
<form method="post" th:object="${form}">
<input th:field="*{email}" type="text"/>
<input type="submit" value="register"/>
</form>
</body>
</html>
and
<!doctype html>
<html th:attr="lang=en" xmlns:th="http://www.w3.org/1999/xhtml">
<body>
<h1>REMEMBER FORM</h1>
<div style="color: red" th:if="${error != null}" th:text="${error}"></div>
<div style="color: green" th:if="${info != null}" th:text="${info}"></div>
<form method="post" th:action="#{/remember}" th:object="${form}">
<input th:field="*{email}" type="text"/>
<input type="submit" value="remember"/>
</form>
</body>
</html>
NOTE: for a better user experience, you can change the url when the remember view is redirected on server using window.history.pushState or so.

Customize items in form select

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>

Spring MVC conversion HOW TO

I have vehicle service that, among other has list of parts. Adding new service is not a problem, viewing of service is not a problem, but when I try to implement edit, it does not preselects the list of parts. So, thinking it is a Thymeleaf issue, I post the question here.
And the answer that I got was to try to implement spring conversion service. I did just that (I think), and now I need help to get me out of this mess. Problem is that view compares instances of parts from service with instances of parts form partsAttribute containing all parts, and never uses converters, so it does not work. I receive no errors... Just in view, parts are not selected.
Bellow you will find Converters, WebMVCConfig, PartRepository, ServiceController and html w/ thymeleaf, for your reference. What am I doing wrong???
Converters:
PartToString:
public class PartToStringConverter implements Converter<Part, String> {
/** The string that represents null. */
private static final String NULL_REPRESENTATION = "null";
#Resource
private PartRepository partRepository;
#Override
public String convert(final Part part) {
if (part.equals(NULL_REPRESENTATION)) {
return null;
}
try {
return part.getId().toString();
}
catch (NumberFormatException e) {
throw new RuntimeException("could not convert `" + part + "` to an valid id");
}
}
}
StringToPart:
public class StringToPartConverter implements Converter<String, Part> {
/** The string that represents null. */
private static final String NULL_REPRESENTATION = "null";
#Resource
private PartRepository partRepository;
#Override
public Part convert(final String idString) {
if (idString.equals(NULL_REPRESENTATION)) {
return null;
}
try {
Long id = Long.parseLong(idString);
return this.partRepository.findByID(id);
}
catch (NumberFormatException e) {
throw new RuntimeException("could not convert `" + id + "` to an valid id");
}
}
}
Relevant parts of WebMvcConfig:
#Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
...
#Bean(name="conversionService")
public ConversionService getConversionService(){
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.setConverters(getConverters());
bean.afterPropertiesSet();
ConversionService object = bean.getObject();
return object;
}
private Set<Converter> getConverters() {
Set<Converter> converters = new HashSet<Converter>();
converters.add(new PartToStringConverter());
converters.add(new StringToPartConverter());
System.out.println("converters added");
return converters;
}
}
Part repository looks like this:
#Repository
#Transactional(readOnly = true)
public class PartRepository {
protected static Logger logger = Logger.getLogger("repo");
#PersistenceContext
private EntityManager entityManager;
#Transactional
public Part update(Part part){
try {
entityManager.merge(part);
return part;
} catch (PersistenceException e) {
return null;
}
}
#SuppressWarnings("unchecked")
public List<Part> getAllParts(){
try {
return entityManager.createQuery("from Part").getResultList();
} catch (Exception e) {
return new ArrayList<Part>();
}
}
public Part findByID(Long id){
try {
return entityManager.find(Part.class, id);
} catch (Exception e) {
return new Part();
}
}
}
Edit part of ServiceController:
#Controller
#RequestMapping("/")
public class ServisController {
protected static Logger logger = Logger.getLogger("controller");
#Autowired
private ServisRepository servisRepository;
#Autowired
private ServisTypeRepository servisTypeRepo;
#Autowired
private PartRepository partRepo;
#Autowired
private VehicleRepository2 vehicleRepository;
/*-- **************************************************************** -*/
/*-- Editing servis methods -*/
/*-- -*/
/*-- **************************************************************** -*/
#RequestMapping(value="/admin/servisi/editServis", method = RequestMethod.GET)
public String getEditServis(#RequestParam(value="id", required=true) Long id, Model model){
logger.debug("Received request to show edit page");
List<ServisType> servisTypeList = servisTypeRepo.getAllST();
List<Part> partList = partRepo.getAllParts();
List<Part> selectedParts = new ArrayList<Part>();
Servis s = servisRepository.getById(id);
for (Part part : partList) {
for (Part parts : s.getParts()) {
if(part.getId()==parts.getId()){
selectedParts.add(part);
System.out.println(part);
}
}
}
s.setParts(selectedParts);
logger.debug("radjeni dijelovi " + s.getParts().toString());
logger.debug("radjeni dijelovi " + s.getParts().size());
s.setVehicle(vehicleRepository.findByVin(s.getVehicle().getVin()));
model.addAttribute("partsAtribute", partList);
model.addAttribute("servisTypesAtribute", servisTypeList);
model.addAttribute("servisAttribute", s);
return "/admin/servis/editServis";
}
#RequestMapping(value="/admin/servisi/editServis", method = RequestMethod.POST)
public String saveEditServis(#ModelAttribute("servisAttribute") #Valid Servis servis, BindingResult result){
logger.debug("Received request to save edit page");
if (result.hasErrors())
{
String ret = "/admin/servis/editServis";
return ret;
}
servisRepository.update(servis);
return "redirect:/admin/servisi/listServis?id="+servis.getVehicle().getVin();
}
}
view displays the service correctly, just it does not preselect parts.
editService:
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring3-3.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:include="fragments/common :: headFragment">
<title>Edit Vehicle Service</title>
</head>
<body>
<div th:include="fragments/common :: adminHeaderFragment"></div>
<div class="container">
<section id="object">
<div class="page-header">
<h1>Edit service</h1>
</div>
<div class="row">
<form action="#" th:object="${servisAttribute}"
th:action="#{/admin/servisi/editServis}" method="post" class="form-horizontal well">
<input type="hidden" th:field="*{vehicle.vin}" class="form-control input-xlarge" />
<div class="form-group" th:class="${#fields.hasErrors('vehicle.vin')} ? 'form-group has-error' : 'form-group'">
<label for="vehicle.licensePlate" class="col-lg-2 control-label">License Plate</label>
<div class="col-lg-10">
<input type="text" th:field="*{vehicle.licensePlate}" class="form-control input-xlarge" placeholder="License Plate" readonly="readonly"/>
<p th:if="${#fields.hasErrors('vehicle.licensePlate')}" class="label label-danger" th:errors="*{vehicle.licensePlate}">Incorrect LP</p>
</div>
</div>
<div class="form-group" th:class="${#fields.hasErrors('serviceDate')} ? 'form-group has-error' : 'form-group'">
<label for="serviceDate" class="col-lg-2 control-label">Servis Date: </label>
<div class="col-lg-10">
<input type="date" th:field="*{serviceDate}" class="form-control input-xlarge" placeholder="Servis Date" />
<p th:if="${#fields.hasErrors('serviceDate')}" class="label label-danger" th:errors="*{serviceDate}">Incorrect Date</p>
</div>
</div>
<div class="form-group" th:class="${#fields.hasErrors('serviceType.id')} ? 'form-group has-error' : 'form-group'">
<label for="serviceType.id" class="col-lg-2 control-label">Vrsta Servisa</label>
<div class="col-lg-10">
<select th:field="*{serviceType.id}" class="form-control">
<option th:each="servisType : ${servisTypesAtribute}"
th:value="${servisType.id}" th:selected="${servisType.id==servisAttribute.serviceType.id}"
th:text="${servisType.name}">Vrsta Servisa</option>
</select>
<p th:if="${#fields.hasErrors('serviceType.id')}" class="label label-danger" th:errors="${serviceType.id}">Incorrect VIN</p>
</div>
</div>
<div class="form-group" th:class="${#fields.hasErrors('parts')} ? 'form-group has-error' : 'form-group'">
<label for="parts" class="col-lg-2 control-label">Parts</label>
<div class="col-lg-10">
<select class="form-control" th:field="*{parts}" multiple="multiple" >
<option th:each="part : ${partsAtribute}"
th:field="*{parts}"
th:value="${part.id}"
th:text="${part.Name}">Part name and serial No.</option>
</select>
<p th:if="${#fields.hasErrors('parts')}" class="label label-danger" th:errors="*{parts}">Incorrect part ID</p>
</div>
</div>
<div class="form-group" th:class="${#fields.hasErrors('completed')} ? 'form-group has-error' : 'form-group'">
<label for="completed" class="col-lg-2 control-label">Is service completed?</label>
<div class="col-lg-10">
<select th:field="*{completed}" class="form-control">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
<p th:if="${#fields.hasErrors('completed')}" class="label label-danger" th:errors="*{completed}">Incorrect checkbox</p>
</div>
</div>
<hr/>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Edit Service</button>
<a class="btn btn-default" th:href="#{/admin/servisi/listServis(id=${servisAttribute.vehicle.vin})}">Cancel</a>
</div>
</form>
</div>
</section>
<div class="row right">
<a class="btn btn-primary btn-large" th:href="#{/admin/part/listPart}">Back to list</a>
</div>
<div th:include="fragments/common :: footerFragment"></div>
</div>
<!-- /.container -->
<div th:include="fragments/common :: jsFragment"></div>
</body>
</html>
UPDATE:
With help from Avnish, I made several changes and this is what I came back with:
adding conversion service did not work, so after researching and reading docs, went back and changed my WebMvcConfig file so in stead of #Bean I added this (All I had to do is look at the documentation on WebMvcConfigurationSupport:
#Override
protected void addFormatters(FormatterRegistry registry){
registry.addFormatter(new PartTwoWayConverter());
}
Then I removed my converters and made just one formatter that does the magic. Don't get confused by the name, it is formater:
public class PartTwoWayConverter implements Formatter<Part>{
/** The string that represents null. */
private static final String NULL_REPRESENTATION = "null";
#Resource
private PartRepository partRepository;
public PartTwoWayConverter(){
super();
}
public Part parse(final String text, final Locale locale) throws ParseException{
if (text.equals(NULL_REPRESENTATION)) {
return null;
}
try {
Long id = Long.parseLong(text);
// Part part = partRepository.findByID(id); // this does not work with controller
Part part = new Part(); // this works
part.setId(id); //
return part;
}
catch (NumberFormatException e) {
throw new RuntimeException("could not convert `" + text + "` to an valid id");
}
}
public String print(final Part part, final Locale locale){
if (part.equals(NULL_REPRESENTATION)) {
return null;
}
try {
return part.getId().toString();
}
catch (NumberFormatException e) {
throw new RuntimeException("could not convert `" + part + "` to an valid id");
}
}
}
Then I edited my HTML. Could not make thymeleaf work out, so I did it like this:
<div class="form-group" th:class="${#fields.hasErrors('parts')} ? 'form-group has-error' : 'form-group'">
<label for="parts" class="col-lg-2 control-label">Parts</label>
<div class="col-lg-10">
<select class="form-control" id="parts" name="parts" multiple="multiple" >
<option th:each="part : ${partsAtribute}"
th:selected="${servisAttribute.parts.contains(part)}"
th:value="${part.id}"
th:text="${part.name}">Part name and serial No.</option>
</select>
<p th:if="${#fields.hasErrors('parts')}" class="label label-danger" th:errors="*{parts}">Incorrect part ID</p>
</div>
</div>
And finally, after a lot of trouble and conversion errors that I could not figure out, changed my controller update method:
#RequestMapping(value="/admin/servisi/editServis", method = RequestMethod.POST)
public String saveEditServis(#ModelAttribute("servisAttribute") #Valid Servis servis, BindingResult result){
logger.debug("Received request to save edit page");
if (result.hasErrors())
{
logger.debug(result);
String ret = "/admin/servis/editServis";
return ret;
}
List<Part> list = new ArrayList<Part>();
for (Part part : servis.getParts()) {
list.add(partRepo.findByID(part.getId()));
}
Servis updating = servisRepository.getById(servis.getId());
updating.setCompleted(servis.getCompleted());
updating.setParts(list); // If just setting servis.getParts() it does not work
updating.setServiceDate(servis.getServiceDate());
updating.setServiceType(servis.getServiceType());
servisRepository.update(updating);
return "redirect:/admin/servisi/listServis?id="+servis.getVehicle().getVin();
}
Even though this works, I am still not happy, since this code looks more like patching than proper coding. Still puzzled why return Part from partRepository did not work. And why thymeleaf did not work... If anyone can send me to the right direction, I would greatly appreciate it!
Thymeleaf compares values (for inclusion of selected="selected" tag in option html) using spring frameworks SelectedValueComparator.isSelected which inherently depends upon java equality first. If that fails, it falls back to String representation of both the values. Following is excerpt from it's documentation
Utility class for testing whether a candidate value matches a data bound value. Eagerly attempts to prove a comparison through a number of avenues to deal with issues such as instance inequality, logical (String-representation-based) equality and PropertyEditor-based comparison.
Full support is provided for comparing arrays, Collections and Maps.
Equality Contract
For single-valued objects equality is first tested using standard Java equality. As such, user code should endeavour to implement Object.equals to speed up the comparison process. If Object.equals returns false then an attempt is made at an exhaustive comparison with the aim being to prove equality rather than disprove it.
Next, an attempt is made to compare the String representations of both the candidate and bound values. This may result in true in a number of cases due to the fact both values will be represented as Strings when shown to the user.
Next, if the candidate value is a String, an attempt is made to compare the bound value to result of applying the corresponding PropertyEditor to the candidate. This comparison may be executed twice, once against the direct String instances, and then against the String representations if the first comparison results in false.
For your specific case, I'd write down conversion service so that my part object is converted to string as described for VarietyFormatter in http://www.thymeleaf.org/doc/html/Thymeleaf-Spring3.html#configuring-a-conversion-service . Post this I'd use th:value="${part}" and let SelectedValueComparator do it's magic of comparing the objects and add selected="selected" part in the html.
Also in my design, I always implement equals method based on primary key (usually I do it at my top level abstract entity from which all other entities inherit). That further strengths the natural comparison of domain objects in my system throughout. Are you doing something similar in your design?
Hope it helps!!
I was searching about another thing and just came across to this post, thought to share a practical and much simpler solution to this issue.
Sometimes being drowned in technologies don't let us think out of the box.
For this one, instead going through all the definitions of converters or formaters, we can simply convert objects set (In this case parts) to string or primitives set inside action method and then add it to the model.
Then inside template just simply check if the set contains any option value:
//In edit action:
Set<Long> selectedPartsLongSet = selectedParts.stream().map(Part::getId).collect(Collectors.toSet);
model.addAttribute("selectedPartsLongSet", selectedPartsLongSet);
In ui:
<select class="form-control" id="parts" name="parts" multiple="multiple" >
<option th:each="part : ${partsAtribute}"
th:selected="${selectedPartsLongSet.contains(part.id)}"
th:value="${part.id}"
th:text="${part.name}">Part name and serial No.</option>
</select>

Spring MVC Formatter and ManyToOne Entity

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?

Syntactically incorrect request sent upon submitting form with invalid data in Spring MVC (which uses hibernate Validator)

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

Categories