I am trying to validate the input field generated dynamically. below code will give us more input:
code from HTML:
<th:block th:each="word,itera : ${credentialsForm.credentialRequirements}">
<div class="rtr_credential" style="display: inline-block;">
<span th:text="${word.attribute}" ></span>
<input type="hidden" th:name="|credentialRequirements[${itera.index}].attribute|" th:value="${word.attribute}">
</div>
<div class="rtr_credential" style="display: inline-block;">
<input type="text" name="userValue" th:field="*{credentialRequirements[__${itera.index}__].userValue}" class="userValue" maxlength="30"
th:classappend="${#fields.hasErrors('userValue')}? has-error : ''">
</div>
</th:block>
It gives error as userValue is not in credentialsForm and if I include
th:classappend="${#fields.hasErrors('{credentialRequirements[__${itera.index}__].userValue}')}? has-error : ''">
this will throw indexing error.
Java class structure:
public class CredentialRequirementForm {
private List<CredentialRequirements> credentialRequirements;
public List<CredentialRequirements> getCredentialRequirements() {
return credentialRequirements;
}
public void setCredentialRequirements(List<CredentialRequirements> credentialRequirements) {
this.credentialRequirements = credentialRequirements;
}
}
CredentialRequirements.java
public class CredentialRequirements {
private String attribute;
private String carrierDescription;
#NotBlank
#NotNull
private String userValue;
public CredentialRequirements() {
super();
// TODO Auto-generated constructor stub
}
public CredentialRequirements(String attribute, String carrierDescription, String userValue) {
super();
this.attribute = attribute;
this.carrierDescription = carrierDescription;
this.userValue = userValue;
}
public String getAttribute() {
return attribute;
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
public String getCarrierDescription() {
return carrierDescription;
}
public void setCarrierDescription(String carrierDescription) {
this.carrierDescription = carrierDescription;
}
public String getUserValue() {
return userValue;
}
public void setUserValue(String userValue) {
this.userValue = userValue;
}
#Override
public String toString() {
return "CredentialRequirements [attribute=" + attribute + ", carrierDescription=" + carrierDescription
+ "]";
}
}
How to validate the userValues, it is dynamically generated, some time only 1 attribute and some time 5 attributes. I am also trying the Jquery validate, but confuse how to implement.
Why don't you simply use a th:field attribute to bind HTML fields to your entity fields? Then you can add a div with the error message (if there will be a validation error):
<th:block th:each="word,itera : ${credentialsForm.credentialRequirements}">
<div class="rtr_credential" style="display: inline-block;">
<span th:text="${word.attribute}" ></span>
<input type="hidden" th:field="*{credentialRequirements[__${itera.index}__].attribute}">
</div>
<div th:if="${#fields.hasErrors('credentialRequirements[__${itera.index}__].userValue')}" th:errors="*{credentialRequirements[__${itera.index}__].userValue}">
Error message
</div>
<div class="rtr_credential" style="display: inline-block;">
<input type="text" name="userValue" th:field="*{credentialRequirements[__${itera.index}__].userValue}" class="userValue">
</div>
</th:block>
Thank you everyone for your answer and comments.
I need to deliver the code, so using Jquery and Javascript to validate:
Please find the code below:
submitHandler : function(form) {
let invalid = 0
$( ".userValue" ).removeClass( "has-error" )
$( ".userValue" ).each(function() {
if($( this ).val() == "" ) {
invalid++
$( this ).addClass( "has-error" );
}
});
if(invalid == 0){
//some logic
form.submit();
}
}
Related
I have an Entity "User". I have an Enum "Usertyp". Users have a field "usertyp". I want to change the "Usertyp" by passing a DTO to my controller. The problem is that the usertype gets changed to Null in the DB
Another minor problem is that I want the default value of the selectfield to have the users current usertype as default value. I tried with "
th:selected="${u.usertyp}">
and also with an if statement. Neither worked.
I am very grateful for any help. I really struggle with Thymeleaf.
Controller:
#PostMapping("editUser/{userId}")
public String editUser(#ModelAttribute("sessionUser") User sessionUser, #Valid #ModelAttribute("userDTO") UserDTO userDTO, #PathVariable Long userId, Model model){
if(sessionUser==null || sessionUser.getUsertyp() == Usertyp.USER)
{
return "redirect:/";
}
else {
User changedUser = userService.findById(userId);
changedUser.setUsertyp(Usertyp.fromString(userDTO.getUsertyp()));
userService.save(changedUser);
return "redirect:/administration";
}
}
DTO
public class UserDTO {
//TODO usertyp anpassem
private String usertyp;
public UserDTO(String usertyp) {
this.usertyp = usertyp;
}
public String getUsertyp() {
return usertyp;
}
public void setUsertyp(String usertyp) {
this.usertyp = usertyp;
}
}
Template
<ul class="list-group list-group-flush">
<span th:each="u : ${userList}">
<li class="list-group-item">
<span th:text="${u.username}"></span>
<form th:object="${userDTO}" th:action="#{/editUser/{userId}(userId=${u.id})}" method="Post" id="Rolle">
<select th:field="*{usertyp}" form="Rolle">
<option
th:each="usertyp : ${T(com.example.myproject.entities.Usertyp).values()}"
th:text="${usertyp.displayText}"
th:value="${usertyp.displayText}">
</option>
</select>
<input class="btn btn-primary" type="submit" value="Speichern">
</form>
</li>
</span>
</ul>
I already changed my DTO field type from "Usertyp" to String
th:selected="${u.usertyp}">
The th:selected property should convert to selected="selected" or just selected in raw html, I think instead you want something like th:selected="${u.usertyp == usertyp}"
You didn't show your #GetMapping setup for the thyme leaf page, so I didn't include a user list, but here's a basic example of what I think you're attempting to do:
User Controller:
#Controller
public class UserController {
#GetMapping("user")
public String getUserTypePage(#ModelAttribute("userDTO") UserDTO userDTO) {
// just setting it to admin so that it will show the default selection of not the first item
userDTO.setUserType(UserType.ADMIN);
return "user";
}
#PostMapping("user")
public void postUserTypePage(#ModelAttribute("userDTO") UserDTO userDTO) {
System.out.println("USER DTO TYPE - " + userDTO.getUserType().getDisplayText());
}
}
UserType enum:
public enum UserType {
USER("Standard User"),
ADMIN("Administrator");
private final String displayText;
UserType(String displayText) {
this.displayText = displayText;
}
public String getDisplayText() {
return displayText;
}
}
UserDTO:
public class UserDTO {
private UserType userType;
public UserDTO(UserType userType) {
this.userType = userType;
}
public UserType getUserType() {
return userType;
}
public void setUserType(UserType userType) {
this.userType = userType;
}
}
Thymeleaf view:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<title>User Type</title>
</head>
<body>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<form th:object="${userDTO}" th:action="#{/user}" method="POST" id="Rolle">
<select th:field="*{userType}" form="Rolle">
<option
th:each="uType : ${T(com.example.demo.models.UserType).values()}"
th:text="${uType.displayText}"
th:value="${uType}"
th:selected="${userType == uType}">
</option>
</select>
<input class="btn btn-primary" type="submit" value="Speichern">
</form>
</li>
</ul>
</body>
</html>
Specifically you want the th:value to be bound to the enum value (if you'd prefer to use an enum rather than a string):
th:value="${uType}"
And then the th:selected set based off of equality of the enum to the UserType option in being looped through:
th:selected="${userType == uType}">
A few additional things to check:
Are you establishing the #ModelAttribute("userDTO") in the GET variant of your controller?
Log out the value of the userType in your post method to confirm it's value before any database operation (to confirm that it is actually coming in as null from the post and not being set to null somewhere in the db operation).
Try just passing along the value for the userType in the form (instead of using a select input item) to confirm that the currentValue for your model is getting fed through i.e.: <input th:field="*{userType}" th:value="${userType}">
I am very new to java so may be this question seems duplicate, BUT i spent one day already on this issue. So I need solution now, or any guideline.
I am trying to receive post parameters from front end through web service BUT I am unable to receive any values.
This is my HTML FORM :
<form role="form" id="" name="" method="post">
<div class="form-group associateTypeFormGroup">
<label class="control-label">Associate Type : </label>
<select class="form-control associateType" id="associateType" name="associateType">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
</div>
<div class="form-group datasetNameFormGroup">
<label class="control-label">Dataset Name : </label>
<input type="text" class="form-control" name="datasetName" id="datasetName" />
</div>
<div class="form-group daterangeFormGroup">
<label class="control-label">Date Range : </label>
<input type="text" class="form-control daterange" name="daterange" id="daterange" data-time-picker="true" data-time-picker-increment="5" data-format="YYYY-MM-DD h:mm:ss A">
</div>
<div class="form-group listaFormGroup">
<label class="control-label">List A : </label>
<textarea class="form-control" id="lista" name="lista"></textarea>
</div>
<div class="form-group listbFormGroup">
<label class="control-label">List B : </label>
<textarea class="form-control" id="listb" name="listb"></textarea>
</div>
<div class="form-group minimumCallingFormGroup" style="display:none;">
<label class="control-label">Minimum Calling : </label>
<select class="form-control" id="minimumCalling" name="minimumCalling">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</div>
<div class="form-group">
<input type="button" class="btn btn-success" value="SUBMIT" onclick="return get_values(this);" />
</div>
</form>
JS function to call web service :
function get_values() {
associateType = $("#associateType").find(":selected").val();
datasetName = $("#datasetName").val();
startdate = $('#daterange').data('daterangepicker').startDate.toISOString().split('.')[0] + "Z";
enddate = $('#daterange').data('daterangepicker').endDate.toISOString().split('.')[0] + "Z";
if ($("#lista").val() != "")
lista = '\'' + $("#lista").val().split(',').join('\',\'') + '\'';
if ($("#listb").val() != "")
listb = '\'' + $("#listb").val().split(',').join('\',\'') + '\'';
minimumCalling = $('#minimumCalling').find(":selected").val();
var cdrReqParams = {};
cdrReqParams.associateType = associateType;
cdrReqParams.datasetName = datasetName;
cdrReqParams.fromDate = startdate;
cdrReqParams.toDate = enddate;
cdrReqParams.listA = lista;
cdrReqParams.listB = listb;
cdrReqParams.minimumCalling = minimumCalling;
var jsonStr = JSON.stringify(cdrReqParams);
API.call("save_associate.json", 'POST', function(data) {
console.log(data);
}, function(error) {
console.log(error);
}, jsonStr);
}
My controller
#RestController
public class AssociateController {
#RequestMapping(value = "/associate", method = RequestMethod.GET)
public ModelAndView associateIndex() {
ModelAndView mav = null;
mav = new ModelAndView("associate.html");
return mav;
}
#Layout(Layout.NONE)
#RequestMapping(value = "/save_associate.json", method = RequestMethod.POST)
public #ResponseBody AjaxResponseBody save_associate(HttpServletRequest request, Model model) throws Exception {
AssociateParams params = new AssociateParams();
try {
params.setAssociateType(request.getParameter("associateType"));
params.setDatasetName(request.getParameter("datasetName"));
params.setFromDate(request.getParameter("fromDate"));
params.setToDate(request.getParameter("toDate"));
params.setListA(request.getParameter("listA"));
params.setListB(request.getParameter("listB"));
params.setMinimumCalling(request.getParameter("minimumCalling"));
System.out.println("+++ Associate Controller +++");
System.out.println(params.getAssociateType());
System.out.println(params.getDatasetName());
System.out.println(params.getFromDate());
System.out.println(params.getToDate());
System.out.println(params.getListA());
System.out.println(params.getAssociateType());
System.out.println(params.getAssociateType());
} catch(Exception e){
e.printStackTrace();
}
finally{
//dbHelper.closeDBConnection(conn);
}
return null;
}
}
Associateparams.java
public class AssociateParams {
private String associateType;
private String datasetName;
private String fromDate;
private String toDate;
private String listA;
private String listB;
private String minimumCalling;
public String getAssociateType() {
return associateType;
}
public void setAssociateType(String associateType) {
this.associateType = associateType;
}
public String getDatasetName() {
return datasetName;
}
public void setDatasetName(String datasetName) {
this.datasetName = datasetName;
}
public String getFromDate() {
return fromDate;
}
public void setFromDate(String fromDate) {
this.fromDate = fromDate;
}
public String getToDate() {
return toDate;
}
public void setToDate(String toDate) {
this.toDate = toDate;
}
public String getListA() {
return listA;
}
public void setListA(String listA) {
this.listA = listA;
}
public String getListB() {
return listB;
}
public void setListB(String listB) {
this.listB = listB;
}
public String getMinimumCalling() {
return minimumCalling;
}
public void setMinimumCalling(String minimumCalling) {
this.minimumCalling = minimumCalling;
}
}
I receive null in all post parameters, any idea what I am doing wrong here ?
I have seen the network tab on browser and service is sending all the required data correctly
API.CALL fUNCTION
var API = {
call:function(url,type,successcallback,errorCallback,data){
var data = (!!data) ? data : {};
var callback = (!!callback) ? callback : function(){};
$.ajax({
contentType : "application/json",
dataType: "json",
//crossDomain: true,
xhrFields: { withCredentials: true },
url: url,
data:data,
type:type,
success:successcallback,
error:errorCallback
});
}
}
Spring controller code
#RequestMapping(value = "/save_associate.json", method = RequestMethod.POST)
public #ResponseBody String save_associate(#RequestBody AssociateParams associateParams , HttpServletRequest request) {
String asscociateType = associateParams.getAssociateType();
// For other parameters use as abaove
// your logic next
}
addEmployee.jsp
<form:form id="bookForm" modelAttribute="employee" method="post" action="${baseURL}employee/saveemployee" class="form-horizontal" enctype="multipart/form-data">
<div class="form-group">
<label for="publishedOn" class="col-xs-4 control-label">Birth Date
</label>
<div class="col-xs-8">
<form:input path="personalInformation.dateOfBirth" placeholder="yyyy-MM-dd" class="datepicker form-control" />
<form:errors path="personalInformation.dateOfBirth"></form:errors>
</div>
</div>
<div class="form-group">
<label for="dateOfJoining" class="col-xs-4 control-label">Date Of Joining</label>
<div class="col-xs-8">
<form:input path="dateOfJoining" placeholder="dateOfJoining" class="form-control" />
<form:errors path="dateOfJoining"></form:errors>
</div>
</div>
</form:form>
This is jsp form in which i have to validate date feilds
whenever i click on save button it gives null pointer exception.
2)EmployeeAddController.java
#RequestMapping(value="/saveemployee",method = RequestMethod.POST)
public String saveEmployee(#ModelAttribute("employee")Employee employee,BindingResult result,Errors e,ModelMap map){
employeeValidatorobj.validate(employee,e);
if(e.hasErrors())
{
map.addAttribute("employee",employee);
return "addEmployee";
}
else{
System.out.println("inside save");
System.out.println(employee.getPersonalInformation().getFirstName());
map.addAttribute("employee",new Employee());
iEmployeeService.saveEmployee(employee);
map.addAttribute("message", "EmployeeAdded sucessfully");
return "employeeList";
}
}
This is controller here i am checking if form has errors or not.`
3)emolyeeValidator.java
public class EmployeeValidator implements Validator {
private Pattern pattern;
private Matcher matcher;
private static final String DATE_PATTERN="((?:19|20)\\d\\d)/(0?[1-9]|1[012])/([12][0-9]|3[01]|0?[1-9])";
#Override
public boolean supports(Class<?> arg0) {
return Employee.class.equals(arg0);
}
#Override
public void validate(Object obj, Errors errors) {
// Employee employee=new Employee();
Employee employee = (Employee) obj;
String date=employee.getDateOfJoining().toString();
if (!date.matches(DATE_PATTERN)) {
pattern=pattern.compile(DATE_PATTERN);
matcher=pattern.matcher(date);
if(matcher.matches()){
errors.rejectValue("dateOfJoining", "dateOfJoining.incorrect","enter correct dateOfJoining");
}
}
This is employeeValidator class for custome validation of date but when i am try to save record it gives null pointer exception.i have also mapped employeevalidator in bean.and whenever iam trying to validate with .equals() string method then it gives exception on jsp page string cannot convert into java.util.Date.please check code and suggest me how to check condition in employeeValidator for date format (YYYY-MM-DD)
I have the following JSP:
<jsp:useBean id="trackingBean" class="tracking.Tracking" scope="session">
<jsp:setProperty name="trackingBean" property="*" />
</jsp:useBean>
<form action="TrackingController" method="post">
<div id="upper_frequency">
Upper Freq: <input type="text" name="upperFreq"
>
</div>
<div id="lower_frequency">
Lower Freq: <input type="text" name="lowerFreq"
>
</div>
<div id="if_frequency">
IF Freq: <input type="text" name="ifFreq"
>
</div>
<div id="cap_high">
Tuning Cap highest value: <input type="text" name="capHigh"
>
</div>
<div id="cap_low">
Tuning Cap lowest value: <input type="text" name="capLow"
>
</div>
<input type="submit" value="Submit" />
</form>
This should pass on the trackingBean to the sevlet whose doGet, the same as doPost:
doGet..
{
Tracking trackingBean = (Tracking) request.getSession(),getAttribute("tackingBean");
....
}
trackingBrean is not null, but all the values are never set?
The bean is:
package tracking;
public class Tracking {
public Tracking() {
}
private double upperFreq;
private double lowerFreq;
private double ifFreq;
private double capHigh;
private double capLow;
public double getUpperFreq() {
return upperFreq;
}
public void setUpperFreq(double upperFreq) {
this.upperFreq = upperFreq;
}
public double getLowerFreq() {
return lowerFreq;
}
public void setLowerFreq(double lowerFreq) {
this.lowerFreq = lowerFreq;
}
public double getIfFreq() {
return ifFreq;
}
public void setIfFreq(double ifFreq) {
this.ifFreq = ifFreq;
}
public double getCapHigh() {
return capHigh;
}
public void setCapHigh(double capHigh) {
this.capHigh = capHigh;
}
public double getCapLow() {
return capLow;
}
public void setCapLow(double capLow) {
this.capLow = capLow;
}
}
You're requesting "tackingBean" don't you need "trackingBean" ?
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>