Spring MVC conversion HOW TO - java

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>

Related

Spring #Autowired service is strangely null inspite of using required fields all over [duplicate]

This question already has answers here:
#Autowired for #ModelAttribute
(2 answers)
Closed 2 years ago.
I am using a pojo for handling form request and I am trying to access service using Autowired. But it keeps returning null inspite of whatever I try changing. I will publish all my code, please take a look into it.
CategoryAddForm:
In this page, I am trying to use categoryService but it seems be empty(null) and when I try using any function inside the service, I get nullpointerexception. I have not tried to initialize the autowired class anywhere.
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Component
public class CategoryAddForm {
private Long id;
private String title;
private String description;
private String language;
private String image_url;
#Autowired
CategoryService categoryService;
public Category save() {
Category category;
if(this.id==null) {
if(this.language==null || this.language.trim().equals("en")) {
category = Category.builder()
.title_en(this.title)
.description_en(this.description)
.image_url(this.image_url)
.en_available(true)
.count(0l).build();
System.out.println("New for English: \n id: "+this.id+" \n title:"+this.title+"\n description: "+this.description+"\n language: "+this.language);
System.out.println(categoryService);
// categoryService.save(category);
}
else {
category = Category.builder()
.title_np(this.title)
.description_np(this.description)
.np_available(true)
.en_available(false)
.count(0l).build();
System.out.println("New for Nepali: \n id: "+this.id+" \n title:"+this.title+"\n description: "+this.description+"\n language: "+this.language);
System.out.println(categoryService);
// categoryService.save(category);
}
}
else {
if(this.language==null || this.language.trim().equals("en")) {
category = Category.builder()
.id(this.id)
.title_en(this.title)
.description_en(this.description)
.en_available(true)
.image_url(this.image_url).build();
}
else {
category = Category.builder()
.id(this.id)
.title_np(this.title)
.description_np(this.description)
.np_available(true)
.image_url(this.image_url).build();
}
}
System.out.println(category);
return categoryService.save(category);
}
}
Form page:
This page sends categoryForm object to /save in controller which invokes the save function that is inside CategoryAddForm
<form th:action="#{'/admin/category/save'}" method="post" th:object="${categoryForm}">
<div class="form-group my-1 p-2 row">
<label for="categoryTitle">Title</label>
<input type="title" class="form-control" name="title" id="categoryTitle" th:field="*{title}">
</div>
<div class="form-group my-1 p-2 row">
<label for="categoryDescription">Description</label>
<textarea rows="5" type="description" class="form-control" name="description" id="categoryDescription" th:field="*{description}">
</textarea>
</div>
<div class="form-check my-1 p-2 row">
<label for="languages">Select Language</label>
<div id="languages" class="row ml-2">
<div class="col-2" >
<input class="form-check-input" type="radio" value="en" th:field="*{language}" id="englishLanguage" th:selected="selected">
<label class="form-check-label" for="englishLanguage">
English
</label>
</div>
<div class="col-2" >
<input class="form-check-input" type="radio" value="ne" th:field="*{language}" id="nepaliLanguage">
<label class="form-check-label" for="nepaliLanguage">
Nepali
</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary m-2">Submit</button>
</form>
Category Service Implementation:
This is just for the reference and proof that I have not done anything wrong.
#Service
public class CategoryServiceImpl implements CategoryService {
#Autowired
private CategoryRepository repository;
#Override
public Category save(Category document) {
return repository.save(document);
}
#Override
public Category getById(Long id) {
return repository.getOne(id);
}
#Override
public void delete(Category document) {
repository.delete(document);
}
#Override
public void deleteById(Long id) {
repository.deleteById(id);
}
#Override
public List<Category> getAllCategories() {
return repository.findAll();
}
}
save route in CategoryAdminController:
This function is taking categoryAddForm from the form and calls save() function in categoryAddForm.
#PostMapping(value = "/save")
public ModelAndView submitCategory(#ModelAttribute("categoryForm") CategoryAddForm categoryForm, BindingResult result){
categoryForm.save();
if(result.hasErrors()){
return new ModelAndView("redirect:/admin/category/add?error");
}
return new ModelAndView("redirect:/admin/category/add");
}
The problem is that you are creating a new CategoryAddForm every time you make a reuqest and you are not letting spring manage your beans. If you are creating a new instance of CategorAddForm, spring won't know that he needs to inject your service into it. I think you can try to inject by constructor.

I am working on a spring mvc project and I want to submit values from two drop boxes and a text box

I have to take month (drop box), year (drop box) and text from the user, which is packaged as a 'Permissions' class object, and sent to the server.
<div id="Setpermissions" class="modal">
<div class="modal-content animate" >
<div class="imgcontainer">
<span onclick="document.getElementById('Setpermissions').style.display='none'" class="close" title="Close Modal">×</span>
</div>
<form id="my-form" action = "Permissions" method="post">
<select name="mon" multiple="multiple" size="10">
<%
for(i=1;i<13;i++) {
out.println("<option value = " + i + "> " + Month.of(i) + "<option>");
}
%>
</select>
<select name="year" multiple="multiple" size="10">
<%
int year = Integer.parseInt(Year.now().toString());
for(i=1;i<13;i++) {
out.println("<option value = " + year + "> " + year + "<option>");
year=year-1;
}
%>
</select>
<fieldset>
<label><input id="Reason" placeholder="Reason" type="text" name="Reason"></label>
</fieldset>
<!-- use this for server-side processing -->
<input type="submit" name="submitted" value="submit" />
</form>
</div>
</div>
Here is my servlet code:
#RequestMapping(value = "/Permissions", method = RequestMethod.POST)
public String Permissions(#Validated Permissions per, Model model) {
System.out.println(per.getReason());
return "user";
}
}
Permissions class:
public class Permissions {
private int mon;
private int year;
private String reason;
private String permission;
public String getPermission() {
return permission;
}
public String getReason() {
return reason;
}
}
But I am not getting directed to the right page (user.jsp), and the values are not reaching the servlet. Please help
2 Things you should try:
Your backing object (Permissions in this case) should have both Getters and Setters for all fields. This is because, when you click submit and send your request to your backend, Spring will call the setX(x) methods for each field. (ex. setYear(year)). As you have no Setters, I don't believe this will work.
You are using capitalized names in your HTML (Reason) instead of the exact field name (reason). I'm not sure if this affects anything, but might try it.

How to stay on the same page after form submit thymleaf and Spring boot?

Hello guys i have a question regarding what is mentioned in the title. Is it possible to stay on the same page and submit . I found something with javascript but it is not working for me because i m using thymleaf and spring boot. Or i just don't know how to adapt it to my case.
thymeleaf code:
<form th:action="#{/tweets/tweet}" th:object="${tweet}" method="post">
<div class="row">
<div class="col">
<input type="text" th:field="*{content}" class="form-control" placeholder="What's happening? Tell us!">
</div>
<div class="col">
<input class="form-control" type="submit" value="Submit" />
</div>
</div>
</form>
the controller class:
#Controller
#RequestMapping("tweets")
#Slf4j
public class TweetController {
private TweetService tweetService;
public TweetController(TweetService tweetService) {
this.tweetService = tweetService;
}
#PostMapping("/tweet")
#ResponseStatus(CREATED)
public Tweet tweet(#Valid #ModelAttribute("tweet") Tweet tweet, Principal
principal, BindingResult result) {
if(result.hasErrors()){
//do somethign
}
if (!tweet.getContent().equals(null) && !tweet.getContent().equals("") && !tweet.getContent().isEmpty()) {
tweetService.createTweet(tweet.getContent(), principal);
}
}
#GetMapping("/")
public String goToIndex(Model model){
model.addAttribute("tweet",new Tweet());
return "overview";
}
And i have server.context-path=/api
I have one more additional question to this topic. When i wanted to redirect it to another page i was getting a blank page. Not an error not an exception just a blank page. Any help ? I m new to this.
Yes, this is possible using ajax. I would recommend doing it using jQuery though. So, if you would like to submit your form and stay in the same page, you could do the following.
HTML
<form id="tweet-form" th:action="#{/tweets/tweet}" th:object="${tweet}" method="post">
<div class="row">
<div class="col">
<input type="text" th:field="*{content}" class="form-control" placeholder="What's happening? Tell us!">
</div>
<div class="col">
<input id="submit-form" class="form-control" type="button" value="Submit" />
</div>
</div>
</form>
Changes:
Added an id to the form.
Added an id to your input.
Change submit input's type for button.
jQuery
$('#submit-form').on('click', function() {
var form = $('#tweet-form');
$.ajax({
url: form.attr('action'),
data: form.serialize(),
type: post,
success: function(result) {
// Do something with the response.
// Might want to check for errors here.
}, error: function(error) {
// Here you can handle exceptions thrown by the server or your controller.
}
})
}
Controller
#PostMapping("/tweet")
#ResponseStatus(CREATED)
public Tweet tweet(#Valid #ModelAttribute("tweet") Tweet tweet, Principal
principal, BindingResult result) {
if(result.hasErrors()){
// Throw an exception or send a null Tweet.
}
if (!tweet.getContent().equals(null) && !tweet.getContent().equals("") && !tweet.getContent().isEmpty()) {
tweetService.createTweet(tweet.getContent(), principal);
}
// You are returning a Tweet, so you must return something.
return tweet;
}
Your controller pretty much stay the same. Just remember to return something.
Your example doesn't show what the tweet() method returns. It should return a Tweet object but doesn't have any return value. What are you attempting to do with that return value? If you're not handling it with Javascript someway, then get rid of #ResponseStatus(CREATED) and return a either a Model or a String pointing to your html file, like so:
#PostMapping("/tweet")
public String tweet(#Valid #ModelAttribute("tweet") Tweet tweet, Principal
principal, BindingResult result) {
if(result.hasErrors()){
//do somethign
}
if (!tweet.getContent().equals(null) && !tweet.getContent().equals("") && !tweet.getContent().isEmpty()) {
tweetService.createTweet(tweet.getContent(), principal);
}
return "redirect:/name-of-html-file";
}
reference
If you want thymeleaf to handle the tweet and the HttpStatus you could instead return something along the lines of
ModelAndView model = new ModelAndView("your-view");
model.addAttribute(tweet);
model.setStatus(HttpStatus.CREATED);
return model;

Neither BindingResult nor plain target object for bean name 'matrix[0][0]' available as request attribute

I have a matrix (array of arrays) of boolean values which I want to show in a form and then submit after changing. I have problems rendering this and I have no more ideas why it is not working. Can anybody give me an advice?
My Controller:
#Controller
#RequestMapping(value = "/konfiguration")
public class VerteilungController {
#ModelAttribute("matrix")
public List<List<Boolean>> getVerteilungenMatrix() {
List<List<Boolean>> result2 = new ArrayList<>();
for (int i = 0; i < kategorien.size(); i++) {
result2.add(new ArrayList<>());
}
//...
return result2;
}
#RequestMapping(value = "/verteilung", method = RequestMethod.GET)
public String showPage(Model model) {
model.addAttribute("matrix", getVerteilungenMatrix());
return "konfiguration/verteilung";
}
}
The form:
<form id="verteilung_form" class="form-horizontal" method="post" action="/verteilung"
th:action="#{/konfiguration/verteilung}"
th:object="${matrix}">
<table class="table-hover">
<tr th:each="row: ${matrix}">
<td th:each="value: ${row}">
<input type="checkbox" th:field="${matrix[__${rowStat.index}__][__${valueStat.index}__]}"/>
</td>
</tr>
</table>
<div >
<button th:text="#{button.save}" class="btn btn-default" type="submit" name="save">Speichern</button>
<button th:text="#{button.reset}" name="reset" class="btn btn-default">Zurücksetzen</button>
</div>
</form>
Openening the page I get
Exception: Error during execution of processor 'org.thymeleaf.spring4.processor.attr.SpringInputCheckboxFieldAttrProcessor'
And in the log
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'matrix[0][0]' available as request attribute
You've misused th:object and th:field. th:object stands for command objects which represents the entire form.
Command object is the name Spring MVC gives to form-backing beans, this is, to objects that model a form’s fields and provide getter and setter methods that will be used by the framework for establishing and obtaining the values input by the user at the browser side.
On the other hand th:field does all the heavy work of binding your input with a property in the form-backing bean. Values inside th:field should point to a field of the object from th:object.
Values for th:field attributes must be selection expressions (*{...}), which makes sense given the fact that they will be evaluated on the form-backing bean and not on the context variables (or model attributes in Spring MVC jargon).
Please check it out here.
So back to your code. To fix it you should create a form-backing bean class and provide the matrix as a field inside that class, for example:
public class FormBean {
private List<List<Boolean>> matrix;
FormBean() { }
public FormBean(List<List<Boolean>> matrix) {
this.matrix = matrix;
}
public List<List<Boolean>> getMatrix() {
return matrix;
}
public void setMatrix(List<List<Boolean>> matrix) {
this.matrix = matrix;
}
}
Next please provide object of FormBean as a model attribute. When you provide method marked as #ModelAttribute then assignment to a model will be done for you. Update your controller body to the following:
#ModelAttribute("formBean")
public FormBean getFormBean() {
return new FormBean(getVerteilungenMatrix());
}
#RequestMapping(value = "/verteilung", method = RequestMethod.GET)
public String showPage() {
return "konfiguration/verteilung";
}
private List<List<Boolean>> getVerteilungenMatrix() {
List<List<Boolean>> result2 = new ArrayList<>();
for (int i = 0; i < kategorien.size(); i++) {
result2.add(new ArrayList<>());
}
//...
return result2;
}
Finally please update your form to the following:
<form id="verteilung_form" class="form-horizontal" method="post" action="/verteilung"
th:action="#{/konfiguration/verteilung}"
th:object="${formBean}">
<table class="table-hover">
<tr th:each="row: *{matrix}">
<td th:each="value: ${row}">
<input type="checkbox" th:field="*{matrix[__${rowStat.index}__][__${valueStat.index}__]}"/>
</td>
</tr>
</table>
<div >
<button th:text="#{button.save}" class="btn btn-default" type="submit" name="save">Speichern</button>
<button th:text="#{button.reset}" name="reset" class="btn btn-default">Zurücksetzen</button>
</div>
</form>
Now everything should work as expected.

How to validate date format in spring form without using Annotation?

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)

Categories