Annotated Spring MVC #ModelAttribute automapping not working with associated objects - spring

I am using Spring MVC with Annotations. Here's a quick outline of my problem.
My Domain:
public class Restaurant {
private String name;
private Address address = new Address();
//Get and set....
}
public class Address{
private String street;
//Get and set....
}
My Controller:
//Configure and show restaurant form.
public ModelAndView showAction() {
ModelAndView mav = new ModelAndView("/restaurant/showRestaurant");
restaurant = new Restaurant();
mav.addObject("restaurant", restaurant);
return mav;
}
//Save restaurant
public ModelAndView saveAction(#ModelAttribute(value="restaurant") Restaurant restaurant,BindingResult result) {
restaurant.getName();//<- Not is null
restaurant.getAddress().getStreet(); //<- is null
}
My View:
<form>
<span class="full addr1">
<label for="Nome">Name<span class="req">*</span></label>
<h:inputText class="field text large" value="#{restaurant.name}"
id="name" forceId="true" styleClass="field text addr"/>
</span>
<span class="full addr1">
<label for="Nome">Street <span class="req">*</span></label>
<h:inputText class="field text large" value="#{restaurant.address.street}"
id="street" forceId="true" styleClass="field text addr"/>
</span>
</form>
My problem is, when I fill the name and the street to call the method "saveAction" when I try to get the restaurant filled happens that the name comes from the street but did not.

I'm not all that familliar with jsf, but for binding in spring you generally need the full path, i.e. name="address.street", in order to get the street name bound properly

Try binding using the spring form tags http://static.springsource.org/spring/docs/2.0.x/reference/spring-form.tld.html. Its pretty easy.

Related

How to transfer the list of strings in DTO using Thymeleaf

I'm developing Spring + Thymeleaf application. I'm implementing search with multiple params. I have a form with the corresponding DTO. Here is the code of the DTO:
public class ClassSearchDto {
private String searchParam;
private Long programId;
private List<String> teacherNames;
//getters, setters and constructor are omitted
}
As you see, I have a list of strings in my DTO called teacherNames. Here is the way I'm displaying my form:
<form th:action="#{/classes/search}" method="get" th:object="${classSearchDto}">
<div class="form-group">
<input type="hidden" class="form-control"
th:value="${classSearchDto.programId}" th:field="*{programId}"/>
<label for="searchParam">Search</label>
<input type="text" class="form-control" id="searchParam" placeholder="keyword"
th:value="${classSearchDto.searchParam}" th:field="*{searchParam}"/>
<div>
<th:block th:each="name, iter ${classSearchDto.teacherNames}">
<input th:value="${name}" th:field="*{teacherNames[__${iter.index}__]}/>
</th:block>
</div>
</div>
<button class="btn btn-default" type="submit">Find</button>
</form>
I want to implement my search with help of #RequestParam annotation on the back-end. This is my controller:
#RequestMapping(value = "/search")
public String findClassByName(#RequestParam("searchParam") final String searchParam,
#RequestParam("programId") final Long programId,
#RequestParam("teacherNames") final List<String> teacherNames,
final Model model) {
...
}
The problem is that I can't get the list of teacher names in this way. I get this exception:
org.springframework.web.bind.MissingServletRequestParameterException:Required List parameter 'teacherNames' is not present
Could you please help me to transfer the list of elements in DTO to my back-end with this approach? Maybe you know how to do it correctly in another way. Thank you in advance.
I can suggest you one thing, I don't know whether it works or not. Try changing
public String findClassByName(#RequestParam("searchParam") final String searchParam,#RequestParam("programId") final Long programId,#RequestParam("teacherNames") final List<String> teacherNames,final Model model)
to
public String findClassByName(#ModelAttribute("classSearchDto") ClassSearchDto classSearchDto,#RequestParam("searchParam") String searchParam,#RequestParam("programId") Long programId,#RequestParam("teacherNames") List<String> teacherNames,Model model)

Form validation in spring with thymeleaf

I'm skilling in form validation with spring boot and thymeleaf and i have problem: i can't do validation in form with two #ModelAttribute fields. The example like form validation om spring official site works correctly, but when I added two #model Attribute in post i get only error at webpage and no hints at form like in spring example.
Controller class:
#Controller
public class MyController {
#Autowired
InstructorRepository instructorRepository;
#Autowired
DetailRepository detailRepository;
#GetMapping("/index")
public String mainController(){
return "index";
}
#GetMapping("/add")
public String addInstructorForm(Model model){
model.addAttribute("instructor", new Instructor());
model.addAttribute("detail", new InstructorDetail());
return "addInstructor";
}
#PostMapping("/add")
public String submitForm(#Valid #ModelAttribute Instructor instructor, #ModelAttribute InstructorDetail instructorDetail, BindingResult bindingResult1){
/* if (bindingResult.hasErrors()) {
return "instructorsList";
}
instructor.setInstructorDetail(instructorDetail);
instructorRepository.save(instructor);*/
if (bindingResult1.hasErrors()) {
return "addInstructor";
}
return "redirect:/instructorsList";
}
#GetMapping("/instructorsList")
public String getList(Model model){
Map map = new HashMap<>();
List list = new ArrayList<Instructor>();
list = instructorRepository.findAll();
List resultList = new ArrayList();
for (int i = 0; i < list.size(); i++) {
Instructor instructor = (Instructor)list.get(i);
InstructorDetail detail = detailRepository.getInstructorDetailById(instructor.getId());
InstructorAndDetail iid = new InstructorAndDetail(instructor, detail);
resultList.add(iid);
}
model.addAttribute("instructors", resultList);
return "instructorsList";
}
}
html form snippet:
<form action="#" data-th-action="#{/add}" data-th-object="${instructor}" method="post">
<div class="form-group">
<label for="1">First name</label>
<input class="form-control" id="1" type="text" data-th-field="${instructor.firstName}" placeholder="John"/>
<div data-th-if="${#fields.hasErrors('firstName')}" data-th-errors="${instructor.firstName}">name error</div>
</div>
There was next problem: then I add entity to thymeleaf form I passed only 2 fields (or one field after), but there was 3 fields with
#NotNull
#Size(min=2, max=30)
So when I commented them in my code the single field validation begin to work :).
If you stucked at the same problem check that all you fields in class that marked #Valid annotation are mirrored in your form.
(or have default valid values? UPD: dont work with valid defaults if they have no form mirroring)

Thymeleaf checkbox list passing values, but not displaying existing values

I have an edit form for a Project object. Each project has a list of roles associated with them out of the complete list of all available roles. I need a checkbox list to select the roles. I implemented it like in this code sample I found thanks to StackOverflow, using a Formatter: https://github.com/jmiguelsamper/thymeleafexamples-selectmultiple
The issue: The form I created allows me to select the roles and save them successfully. But when I display an existing project object to edit it, the roles already associated with that project are not checked in the list. All the checkboxes are clear.
The code sample above was with a String id. I use a Long id. I think that's the reason for the issue, but I don't know how to solve it. Should I drop the Formatter approach entirely? Is there a way to make this work?
This is my code so far:
Project class:
#Entity
public class Project
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany
private List<Role> rolesNeeded;
public Project()
{
rolesNeeded = new ArrayList<>();
}
//getters and setters omitted for brevity
}
Role class:
#Entity
public class Role
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column
private String name;
public Role() {}
//getters and setters omitted for brevity
}
Controller:
#Controller
public class ProjectController
{
#Autowired
private ProjectService projectService;
#Autowired
private RoleService roleService;
#RequestMapping(value = "/projects/save", method = RequestMethod.POST)
public String addProject(#Valid Project project)
{
projectService.save(project);
return "redirect:/";
}
#RequestMapping("/projects/{id}/edit")
public String editForm(#PathVariable Long id, Model model)
{
Project project = projectService.findById(id);
model.addAttribute("project", project);
model.addAttribute("allRoles", roleService.findAll());
return "project/form";
}
}
The RoleFormatter:
#Component
public class RoleFormatter implements Formatter<Role>
{
#Override
public Role parse(String id, Locale locale) throws ParseException
{
Role role = new Role();
role.setId(Long.parseLong(id));
return role;
}
#Override
public String print(Role role, Locale locale)
{
String id = role.getId() + "";
return id;
}
}
And finally the Thymeleaf form:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<section>
<div class="container wrapper">
<form th:action="#{/projects/save}" method="post" th:object="${project}">
<input type="hidden" th:field="*{id}"/>
<div>
<label for="project_name"> Project Name:</label>
<input type="text" id="project_name" th:field="*{name}"/>
</div>
<div>
<label>Project Roles:</label>
<ul class="checkbox-list">
<li th:each="role : ${allRoles}">
<input type="checkbox" th:id="${{role}}" th:value="${{role}}" th:field="*{rolesNeeded}" />
<span class="primary" th:text="${role.name}"></span>
</li>
</ul>
</div>
<div class="actions">
<button type="submit" value="Save" class="button">Save</button>
<a th:href="#{/}" class="button button-secondary">Cancel</a>
</div>
</form>
</div>
</section>
</body>
</html>
UPDATE
As discussed in the comments: when I do not use the Formatter like above, I get a 400 Bad Request error. This is the header data of the POST request. In this case I tried selecting two roles (id 1 and 3 as you can see below)
Request URL:http://localhost:8080/projects/save
Request Method:POST
Status Code:400 Bad Request
Remote Address:[::1]:8080
Referrer Policy:no-referrer-when-downgrade
Response Headers
Connection:close
Content-Language:en-GB
Content-Length:350
Content-Type:text/html;charset=ISO-8859-1
Date:Tue, 31 Oct 2017 20:10:09 GMT
Server:Apache-Coyote/1.1
Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding:gzip, deflate, br Accept-Language:en-GB,en;q=0.9,en-US;q=0.8,fr;q=0.7
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:161
Content-Type:application/x-www-form-urlencoded
Host:localhost:8080
Origin:http://localhost:8080
Referer:http://localhost:8080/projects/add
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/62.0.3202.75
Safari/537.36
Form Data
id:
name:Implement recipe site
description:description
status:RUNNING
rolesNeeded:1
_rolesNeeded:on
_rolesNeeded:on
rolesNeeded:3
_rolesNeeded:on
_rolesNeeded:on
Compleately remove formatter as you dont need it in your case and do the checkbox like that
<input type="checkbox" th:value="${role.id}" th:field="*{rolesNeeded}" th:text="${role.name}"/>
this should work. Id from checkbox will be autointerpreted as existing entities id and will be fetched from the database.
Formatters are meant to generate localized presentation of some objects not to be converters between web forms and backing beans. Yes i am aware that some tutorials are teaching pplmto do that but please dont. Maybe someday in the past, in older versions of spring or thymeleaf it was the correct solution,but right now it is more like a hack, not a how-o-do-thigs-right pattern.
PS: this is a part of working application
Controller method declaration:
public String addPlacePost(#Valid final Place place, BindingResult placeValidation, Model model) {
Checkbox markup:
<fieldset th:object="${place}" th:classappend="${#fields.hasErrors('services')} ? 'has-error' : _ ">
<legend>Select services</legend>
<div class="checkbox" th:each="service : ${allServices}">
<label> <input th:value="${service.id}" th:field="*{services}" type="checkbox"/> <span
th:text="${service.name}" th:remove="tag"> </span>
</label>
</div>
<span class="help-block" th:each="msg : ${#fields.errors('services')}" th:text="${msg}">Some error message for this field</span>
</fieldset>
And the Place entity part that contains Services
#ManyToMany
#JoinTable(joinColumns = #JoinColumn(name = "place_id"), inverseJoinColumns = #JoinColumn(name = "service_id"))
#NotEmpty
private Set<Service> services;
Works like charm both for adding new places as well as editing existing ones.
Running ahead - a complete Github example with explanations is available here -> https://stackoverflow.com/a/46926492/6332774
Now comments specific to your case:
If you did not get it resolved please check this response from Thymeleaf team:
http://forum.thymeleaf.org/The-checked-attribute-of-the-checkbox-is-not-set-in-th-each-td3043675.html
In your case your getRolesNeeded() method needs to return an Array where size of the array needs to equal to number of checkboxes.
For this you can use AttributeConverter as documented here https://stackoverflow.com/a/34061723/6332774
Add StringListConverter class (as in link above) and change your model class as:
#Convert(converter = StringListConverter.class)
private List<String> rolesNeeded = new ArrayList<>();
...
public List<String> getRolesNeeded() {
return rolesNeeded;
}
public void setRolesNeeded(List<String> rolesNeeded) {
this.rolesNeeded = rolesNeeded;
}
Then in your html checkbox input remove Id as suggested by #Antoniossss . Change it to something like this:
<div th:each="roles : ${allRoles_CanAddRolesArrayInController}">
<input type="checkbox" th:field="*{rolesNeeded}" th:value="${role.id}"/><label th:text="${role.name}">Role1</label>
</div>
Hope it helps.

Persist radio checked value in Thymeleaf Spring

When searching, if we select a given field to search within and submit, our choice is forgotten. How could someone modify the view template to keep the previous search field selected when displaying results?
I have read many other SO questions and the thymeleaf documentation here but have not found a suitable answer yet.
One can hard code a string (like employer) with the following:
search.html snippet
<span th:each="column : ${columns}">
<input
type="radio"
name="searchType"
th:id="${column.key}"
th:value="${column.key}"
th:checked="${column.key == 'employer'}"/>
<label th:for="${column.key}" th:text="${column.value}"></label>
</span>
SearchController.html snippet
#RequestMapping(value = "")
public String search(Model model) {
model.addAttribute("columns", columnChoices);
return "search";
}
How can I persist the user selected radio value upon POST in Thymeleaf Spring?
(and default to the first, value on the GET)
http://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#creating-a-form
Command object.
public class Search {
// default "employer"
private String type = "employer";
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Controller
#GetMapping
public String search(Model model) {
model.addAttribute("columns", columnChoices);
model.addAttribute("search", new Search());
return "search";
}
#PostMapping
public String search(#ModelAttribute Search search) {
// Search's attributes match form selections.
}
With search as your command object, a radio buttons should look like this:
<form th:object="${search}">
<th:block th:each="column: ${columns}">
<input type="radio" th:value="${column.key}" th:id="${column.key}" th:field="*{type}" />
<label th:for="${column.key}" th:text="${column.value}" />
</th:block>
</form>

#NumberFormat Annotation not working

Trying to show the currency symbol in JSP but I don't see it. Did my research and I just don`t know what more should I add to get it working. This is what I have.
<mvc:annotation-driven />
Controller
#NumberFormat(style = Style.CURRENCY)
private Double value = 50.00;
#ModelAttribute("value")
#NumberFormat(style = Style.CURRENCY)
public Double getValue() {
return value;
}
#RequestMapping(method = RequestMethod.GET)
public ModelAndView loadForm(#ModelAttribute("user") User user) {
ModelAndView instance
modelAndView.addObject("value", 100.00);
return modelAndView;
}
JSP
<spring:bind path="value">
<input type="text" name="${value}" value="${value}"/>
</spring:bind>
<spring:bind path="value">
${value}
</spring:bind>
Output
<input type="text" name="value" value="100.0"/>
100.0
Try using the string literal value for the name attribute instead of resolving it with EL
<spring:bind path="value">
<input type="text" name="value" value="${value}"/>
</spring:bind>
Also, move the field value into a new object. Currently I do not believe the code is using the field in the controller or the getter in the controller.
public class MyForm(){
#NumberFormat(style = Style.CURRENCY)
private Double value = 50.00;
#ModelAttribute("value")
#NumberFormat(style = Style.CURRENCY)
public Double getValue() {
return value;
}
}
Then add the object to the model in the controller:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView loadForm(#ModelAttribute("user") User user) {
ModelAndView instance
modelAndView.addObject("myForm", new MyForm());
return modelAndView;
}
Then access via the jsp:
<spring:bind path="myForm.value">
<input type="text" name="${status.expression}" value="${status.value}"/>
</spring:bind>
<spring:bind path="myForm.value">
${status.value}
</spring:bind>
The major issue at the moment with the code is that it is not using the field/accessor, it is simply placing a value in the model, which does not use any of the annotated fields/methods.
References:
http://www.captaindebug.com/2011/08/using-spring-3-numberformat-annotation.html#.UOAO_3fghvA
How is the Spring MVC spring:bind tag working and what are the meanings of status.expression and status.value?

Resources