I'm just start learning validation of users input value via modal window(bootstrap). And now I have a problem with controller/template, it doesn't work- even if I open /groups/getAll page, server drops with error:
Failed to complete request: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('name')" (template: "groups/getAll" - line 61, col 59)
20:32:50.056 [http-nio-8080-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] -
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('name')" (template: "groups/getAll" - line 61, col 59)] with root cause
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'group' available as request attribute
In console I see that constraint works well - @UniqueGroupName validator done as I want. How to fix controller and template to correct work?
Thanks in advance.
GroupDto
@UniqueGroupName
public class GroupDto {
private int id;
@NotBlank(message = "Name is mandatory")
@Pattern(regexp = "[A-Z]{2}-[0-9]{3}", message = "Invalid Group name format, should be \"AA-201\"")
private String name;
@NotNull(message = "Select faculty")
private int facultyId;
private String facultyName;
GroupController
@Controller
@RequestMapping("/groups")
public class GroupController {
private static final String REDIRECT_PAGE = "redirect:/groups/getAll";
private final GroupService groupService;
private final ModelMapper modelMapper;
private final Logger logger = LoggerFactory.getLogger(GroupController.class);
@Autowired
public GroupController(GroupService groupService, ModelMapper modelMapper) {
this.groupService = groupService;
this.modelMapper = modelMapper;
}
@GetMapping("/getAll")
public String findAllGroups(Model model) {
logger.debug("findAll() groupDtos");
List<GroupDto> groups = groupService.findAll().stream().map(g -> convertToDto(g)).collect(Collectors.toList());
logger.trace("found {} groups.", groups.size());
model.addAttribute("groups", groups);
List<Faculty> faculties = groupService.getFaculties();
model.addAttribute("faculties", faculties);
return "groups/getAll";
}
@PostMapping("/add")
public String add(@Valid @ModelAttribute(value= "group") GroupDto groupDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
//what I need here to return modal window with errors information?
} else {
groupService.add(convertToEntity(groupDto));
return REDIRECT_PAGE;
}
}
@GetMapping("/find")
@ResponseBody
public GroupDto find(int id) {
return convertToDto(groupService.find(id));
}
@PostMapping(value = "/update")
public String update(@Valid GroupDto groupDto) {
groupService.update(convertToEntity(groupDto));
return REDIRECT_PAGE;
}
@GetMapping("/delete")
public String deleteUser(int id) {
groupService.delete(id);
return REDIRECT_PAGE;
}
private GroupDto convertToDto(Group group) {
return modelMapper.map(group, GroupDto.class);
}
private Group convertToEntity(GroupDto groupDto) {
return modelMapper.map(groupDto, Group.class);
}
}
And finally template:
<!DOCTYPE html>
<html>
<head>
<div th:insert="/fragments/common :: common_head"></div>
<script type="text/javascript" src="../../resources/js/groups.js" th:src="@{/resources/js/groups.js}"></script>
<script type="text/javascript" src="../../resources/js/common.js" th:src="@{/resources/js/common.js}"></script>
<title>Groups</title>
</head>
<body class="bg-secondary">
<div class="container">
<H2 class="text-body">List of groups</H2>
</div>
<div>
<nav class="navbar navbar-expand-sm bg-success navbar-light">
<ul class="navbar-nav text-white">
<li class="nav-item"><a class="nav-link" th:href="@{/main_page}">Home</a></li>
<li class="nav-item"><a class="nav-link" th:href="@{/buildings/getAll}">Buildings</a></li>
<li class="nav-item"><a class="nav-link" th:href="@{/auditoriums/getAll}">Auditoriums</a></li>
<li class="nav-item"><a class="nav-link active" href="#">Groups</a></li>
<li class="nav-item"><a class="nav-link" th:href="@{/faculties/getAll}">Faculties</a></li>
</ul>
</nav>
</div>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addModal">Add group</button>
<div>
<table class="table table-bordered">
<thead class="bg-primary text-white">
<tr>
<td><strong>Group name</strong></td>
<td><strong>Faculty Name</strong></td>
<td><strong>Edit</strong></td>
<td><strong>Delete</strong></td>
</tr>
</thead>
<tr th:each="group:${groups}" class="table-success">
<td th:text="${group.name}"></td>
<td th:text="${group.facultyName}"></td>
<td><a th:href="@{/groups/find/(id=${group.id})}" class="btn btn-warning">Edit</a></td>
<td><a th:href="@{/groups/delete/(id=${group.id})}" class="btn btn-danger">Delete</a></td>
</tr>
</table>
</div>
<div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="addModal" aria-hidden="true">
<form action="#" th:action="@{/groups/add}" method="post" th:object="${group}">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addModal">Add group</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="name" class="col-form-label">Group Name</label> <input type="text"
class="form-control" id="addName" name="name" placeholder="Group Name">
<div class="text text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
</div>
<div class="form-group">
<label for="faculyId" class="col-form-label">Faculty</label> <select class="form-control"
id="addFaculty" name="facultyId">
<option th:value=0>Faculty not assign</option>
<option th:each="faculty : ${faculties}" th:value="${faculty.id}"
th:utext="${faculty.name}">Faculty</option>
</select>
<div class="text text-danger" th:if="${#fields.hasErrors('facultyId')}"
th:errors="*{facultyId}"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</form>
</div>
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="editModal" aria-hidden="true">
<form th:action="@{/groups/update}" method="post">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Edit building</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="recipient-name" class="col-form-label">Group Id</label> <input type="text"
class="form-control" id="idEdit" name="id" readonly>
</div>
<div class="form-group">
<label for="recipient-name" class="col-form-label">Group Name</label> <input type="text"
class="form-control" id="nameEdit" name="name">
</div>
<div class="form-group">
<label for="facultyId" class="col-form-label">Faculty</label> <select class="form-control"
id="facultyEdit" name="facultyId">
<option th:each="faculty : ${faculties}" th:value="${faculty.id}"
th:utext="${faculty.name}" th:selected="${faculty.id}==facultyEdit">Faculty</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</form>
</div>
<div th:insert="fragments/common :: deleteEntry"></div>