I use Spring MVC and Spring form validation in my project.
There is class named Group in object model and I created form for editing it.
Form
<spring:url var="saveGroup" value="/teacher/groups/save"/>
<form:form action="${saveGroup}" method="post" modelAttribute="group">
<form:hidden path="id"/>
<div id="nameDiv" class="control-group">
<form:label path="title">Title:</form:label>
<form:input path="title"/>
<form:errors path="title"/>
</div>
<div id="specDiv" class="control-group">
<form:label path="title">Specialty:</form:label>
<form:select path="specialty">
<form:options items="${specialties}" itemValue="id" itemLabel="title"/>
</form:select>
</div>
<div class="center">
<spring:url var="groups" value="/teacher/groups"/>
<input class="btn btn-primary" type="submit" value="Save"/>
<a class="btn" href="${groups}"> Cancel </a>
</div>
</form:form>
Controller
#Controller
#RequestMapping("/teacher/groups")
public class GroupsController {
#Autowired
private GroupService groupService;
#Autowired
private SpecialtyService specialtyService;
#ModelAttribute("group")
public Group setGroup(Long id) {
if (id != null) {
return groupService.read(id);
} else {
return new Group();
}
}
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Specialty.class, "specialty",
new SpecialtyEditor(specialtyService));
binder.setValidator(new GroupValidator());
}
#RequestMapping("")
public ModelAndView groups() {
return new ModelAndView("teacher/groups/list", "groups",
groupService.list());
}
#RequestMapping("/edit")
public ModelAndView editGroup() {
return new ModelAndView("teacher/groups/edit", "specialties",
specialtyService.list());
}
#RequestMapping(value = "/save", method = RequestMethod.POST)
public String saveGroup(#Valid Group group, BindingResult result) {
if (result.hasErrors()) {
return "forward:/teacher/groups/edit";
}
groupService.update(group);
return "redirect:/teacher/groups";
}
}
I want to set the adequate behavior of my form in the case of failed validation. I.e. it should save its state but only show validation error message (as when using javascript to validate).
I thought that "forward:/teacher/groups/edit" will again forward request to editGroup() with saved objects group and result. But when I failed validation form just reloads and show start state of edited group: no errors and no saved changes.
How can I do that correctly?
Thanks!
I solved problem by not forwarding request to other method but sending answer to user immediately. Now it works and looks like:
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView saveGroup(#Valid Group group, BindingResult result) {
if (result.hasErrors()) {
return new ModelAndView("/teacher/groups/edit", "specialties", specialtyService.list());
}
groupService.update(group);
return new ModelAndView("redirect:/teacher/groups");
}
Related
I'm trying to classappend a few classes dependant on errors found in the validation process of a form. But for some reason, it's not appending the classes. My CSS is correct as it works without using the classappend error check and also my validation is correct as it will detect an error in the form but just won't append the css classes. The specific method used is updateRole() in the RoleController class. Below is all my code:
RoleController.java
#Controller
public class RoleController {
#Autowired
private RoleServiceImpl roleService;
#RequestMapping("/roles")
public String viewAllRoles(ModelMap model) {
List<Role> roles = roleService.findAll();
model.put("roles", roles);
if (!model.containsAttribute("role")) {
model.put("role", new Role());
}
return "roles";
}
#RequestMapping(value = "/roles", method = RequestMethod.POST)
public String addRole(#Valid Role role, BindingResult result, RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
// Add the error message as a flash attribute
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.role", result);
// Add the role object created so the user can see their inital input after redirect
redirectAttributes.addFlashAttribute("role", role);
return "redirect:/roles";
}
roleService.save(role);
redirectAttributes.addFlashAttribute("flash", new FlashMessage("Role successfully added", FlashMessage.Status.SUCCESS));
return "redirect:/roles";
}
#RequestMapping(value = "/roles/{roleId}")
public String viewRole(#PathVariable Long roleId, ModelMap model) {
Role role = roleService.findById(roleId);
model.put("role", role);
return "role";
}
#RequestMapping(value = "/roles/{roleId}", method = RequestMethod.POST)
public String updateRole(#Valid Role role, BindingResult result, RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.role", result);
redirectAttributes.addFlashAttribute("role", role);
return String.format("redirect:/roles/%s/edit", role.getId());
}
roleService.save(role);
redirectAttributes.addFlashAttribute("flash", new FlashMessage("Role successfully updated", FlashMessage.Status.SUCCESS));
return "redirect:/roles";
}
#RequestMapping(value = "/roles/{roleId}/edit")
public String editRole(#PathVariable Long roleId, ModelMap model) {
Role role = roleService.findById(roleId);
model.put("action", String.format("/roles/%s", roleId));
model.put("role", role);
return "role_edit";
}
}
role_edit.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="base :: head('role_edit')"></head>
<body>
<header th:replace="base :: header"></header>
<nav>
<ul>
<li><a th:href="#{projects}">Projects</a></li>
<li><a th:href="#{collaborators}">Collaborators</a></li>
<li class="selected"><a th:href="#{roles}">Roles</a></li>
</ul>
</nav>
<section>
<div class="container wrapper">
<form th:action="#{${action}}" method="post" th:object="${role}">
<input type="hidden" th:field="*{id}">
<div class="edit-input" th:classappend="${#fields.hasErrors('name')}? 'edit-error test' : ''">
<label> Role Name:</label>
<input type="text" th:field="*{name}">
</div>
<div th:class="${#fields.hasErrors('name')}? 'error' : ''">
<div class="testtwo error-message" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
</div>
<div class="actions">
<button class="button" type="submit" value="Submit">Save</button>
<a th:href="#{|/roles/*{id}|}" class="button button-secondary">Cancel</a>
</div>
</form>
</div>
</section>
</body>
</html>
If anyone could help it would be great, as I'm really stuck on this one.
In my registration form, I have a field that give to users the possiblity to choose roles. But, I want to, only, show two roles : USER and MODERATOR.
But, I don't know how to proceed.
this registration form jsp :
<div class="row">
<div class="form-group col-md-12">
<label style="color:#ffffff;" class="col-md-3 control-lable" for="userProfiles">Roles</label>
<div class="col-md-7">
<form:select path="userProfiles" items="${roles}" multiple="true" itemValue="id" itemLabel="type" class="form-control input-sm" />
<div class="has-error">
<form:errors path="userProfiles" class="help-inline"/>
</div>
</div>
</div>
</div>
I have an enum : UserProfileType
public enum UserProfileType implements Serializable{
USER("USER"),
DBA("DBA"),
ADMIN("ADMIN"),
MODERATOR("MODERATOR");
String userProfileType;
private UserProfileType(String userProfileType){
this.userProfileType = userProfileType;
}
public String getUserProfileType(){
return userProfileType;
}
}
And this is how it look :
This my controller, the part responsible of registration
//new user
#RequestMapping(value = "/registrationForm", method = RequestMethod.GET)
public String newUser(User user, ModelMap model) {
//User user = new User();
model.addAttribute("user", user);
model.addAttribute("loggedinuser", getPrincipal());
return "registrationForm";
}
//save user
#RequestMapping(value = { "/registrationForm" }, method = RequestMethod.POST)
public String saveUser1(#Valid User user, BindingResult result,
ModelMap model) {
if (result.hasErrors()) {
return "registrationForm";
}
if(!userService.isUserSSOUnique(user.getId(), user.getSsoId())){
FieldError ssoError =new FieldError("user","ssoId",messageSource.getMessage("non.unique.ssoId", new String[]{user.getSsoId()}, Locale.getDefault()));
result.addError(ssoError);
return "registrationForm";
}
userService.saveUser(user);
model.addAttribute("success", "User " + user.getFirstName() + " "+ user.getLastName() + " registered successfully");
model.addAttribute("loggedinuser", getPrincipal());
//return "success";
return "registrationsuccess";
}
I am using spring mvc/security and hibernate.
Thanks in advance
The data to be displayed by the page should be provided and manipulated by the controller. Filter the roles in your controller before handing over to the view.
Depending on how you are adding attributes to the model you can do it as foLlows (or any equivalent way as Spring MVC is flexible)
You could create methods in your Enum class to return the filtered values according to yours needs:
public enum UserProfileType implements Serializable{
USER("USER"),
DBA("DBA"),
ADMIN("ADMIN"),
MODERATOR("MODERATOR");
String userProfileType;
private UserProfileType(String userProfileType){
this.userProfileType = userProfileType;
}
public String getUserProfileType(){
return userProfileType;
}
public static List<UserProfileType> getAdminRoles(){
return Arrays.asList(UserProfileType.USER , UserProfileType.MODERATOR);
}
}
#ModelAttribute("roles")
public List<UserProfileType> roles(){
return UserProfileType.getAdminRoles();
}
You can also filter the roles in the view but its not recommended to have control logic in your view
<form:select path="userProfiles"multiple="true" class="form-control input-sm">
<c:forEach items="${roles}" var = "role">
<c:if test="${role.type eq 'USER' or role.type eq 'MODERATOR'}">
<form:option value="${role.id}" label="${role.type}"/>
</c:if>
</c:forEach>
</form:select>
index.jsp
<f:form action="emp_change.htm" method="POST" commandName="index">
<div id="login_box">
<div id="form_val">
<div class="label">Username:</div>
<div class="control"><f:input path="emailId" /></div>
<div style="clear:both;height:0px;"></div>
<div class="label">Password:</div>
<div class="control"><f:input path="password" type="password" /></div>
<div style="clear:both;height:0px;"></div>
<div id="msgbox"></div>
<div id="login_foot">
<input type="submit" name="action" id="login" value="Login" class="send_button" />
</div>
</div>
</div>
</f:form>
AdminInfoControl.java
#Controller
public class AdminInfoControl {
private AdminInfoService service;
#RequestMapping(value = "/emp_change", method = RequestMethod.POST)
public String doActions(#ModelAttribute JtAdminInfo emp, BindingResult result, #RequestParam String action, Map<String, Object> map) {
service = new AdminInfoService();
JtAdminInfo empResult = new JtAdminInfo();
switch (action.toLowerCase()) {
case "login":
JtAdminInfo search = service.getFindAdmin(emp.getEmailId());
empResult = search != null ? search : new JtAdminInfo();
break;
}
map.put("index", empResult);
map.put("empList", service.getAll());
return "index";
}
}
I'm getting following error :
Neither BindingResult nor plain target object for bean name 'emp_change' available as request attribute
Anyone please help me to correct this. Please let me know, if information is not sufficient.
Just answered on almost the same question.. You are obtaining emp_change as a model attribute in doActions method but you didn't set it as a model attribute previously. So, set your emp_change as a model attribute in some method that e.g. shows your index.jsp page. Something like this:
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView showIndex() {
ModelAndView mav = new ModelAndView("index");
mav.addObject("emp_change", new JtAdminInfo ());
return mav;
}
I have added following method in my controller and works fine.
#RequestMapping("/index")
public String setupForm(Map<String, Object> map) {
map.put("index", new JtAdminInfo());
return "index";
}
I have a controller with the following methods:
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String register(Model model) {
model.addAttribute("user", new User());
return "register";
}
#RequestMapping(value = "/registration", method = RequestMethod.POST)
public String registration(#ModelAttribute("user") User user, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
UserValidator validator = new UserValidator();
validator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX + "user", bindingResult);
redirectAttributes.addFlashAttribute("user", user);
return "redirect:/register";
}
...
}
My jsp:
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<form:form method="POST" modelAttribute="user" action="/registration">
<div>
<label>First Name</label>
<form:input path="firstName"/>
<form:errors path="firstName"/>
</div>
<div>
<label>Last Name</label>
<form:input path="lastName"/>
<form:errors path="lastName"/>
</div>
<div>
<label>E-mail</label>
<form:input path="email"/>
<form:errors path="email"/>
</div>
<div>
<label>Password</label>
<form:password path="password"/>
<form:errors path="password"/>
</div>
<input type="submit" value="Submit"/>
</form:form>
However, if there are errors they are not shown after the redirect because I create a new user every time the /register is called. Is there a way to fix this without storing the user in a session?
I can create another method just for this case so I can redirect to /register2 without losing the user:
#RequestMapping(value = "/register2", method = RequestMethod.GET)
public String register2() {
return "register";
}
It worked like I wanted, but it's a very bad approach.
How about checking first if a model named "user" exist before creating one on "/register" ?
if(!model.containsAttribute("user")) model.addAttribute("user", new User());
Also if I'm not wrong you can simply use #ModelAttribute on parameter and it will serve similar purpose
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String register(#ModelAttribute("user") User user) {
Login form:
<f:form class="form-horizontal" method="post" action="/login"
commandName="logindata">
<fieldset>
<legend class="text-info">Login</legend>
<div class="control-group">
<f:label path="uname" class="control-label" for="uname">Username</f:label>
<div class="controls">
<f:input type="text" path="uname" name="uname" id="uname"
placeholder="Username" />
</div>
</div>
<div class="control-group">
<f:label path="pwd" class="control-label" for="pwd">Password</f:label>
<div class="controls">
<f:input type="password" path="pwd" name="pwd" id="pwd"
placeholder="Password" />
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" id="login">
Login <i class="icon-chevron-right"></i>
</button>
</div>
</div>
<div id="errormsg" class="alert alert-error">${message}</div>
</fieldset>
</f:form>
the loginData class:
package com.demo.forms;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
public class loginData {
#Length(min=4)
private String uname;
#NotEmpty
private String pwd;
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
Controller methods for showing and submitting the form: (Shows homepage which contains signup form and login form)
#RequestMapping(value = "/", method=RequestMethod.GET)
public String showHome(Model model)
{
loginservice.logout();
model.addAttribute("logindata", new loginData());
model.addAttribute("signupdata", new signupData());
return "home";
}
Method called upon submitting login form:
#RequestMapping(value = "login", method=RequestMethod.POST)
public String submitloginForm(#Valid loginData logindata, SessionStatus state, Model model, BindingResult result)
{
if((loginservice.loggedin()) || (result.hasErrors()))
{
return showHome(model);
}
else
{
String uname = logindata.getUname();
String pwd = logindata.getPwd();
if(loginservice.login(uname, pwd))
{
model.addAttribute("user",uname);
return "redirect:profile";
}
else
{
model.addAttribute("message","Invalid Username/Password");
return showHome(model);
}
}
}
The login works fine when the data entered is 'valid' (either correct or wrong). However, when it is invalid, for instance, when the password field is empty or the username is less than four characters long, following error is shown:
The request sent by the client was syntactically incorrect.
Any idea how this might be fixed?
You have to modify the order of your arguments. Put the BindingResult result parameter always directly after the parameter with the #Valid annotation.
#RequestMapping(value = "login", method=RequestMethod.POST)
public String submitloginForm(#Valid loginData logindata, BindingResult result,
SessionStatus state, Model model)
This was even mentioned in this weeks This Week in Spring - March 5th, 2013 blog entry
Someone asked me this the other day and I felt like it was worthy of a
mention: in your Spring MVC #Controller class handler methods, make
sure that the BindingResult argument is immediately after the model or
command argument, like this: #RequestMapping(...) public String
handleRequest( #ModelAttribute #Valid YourCustomPojo attempt,
BindingResult result). In this example, handleRequest will validate
the POJO (YourCustomPojo) - checking the POJO for JSR303-annotations
and attempting to apply the constraints because the POJO is annotated
with #Valid - and stash any errors in the BindingResult, which it
makes available if we ask for it.
Spring will
0) determin the handler method
1) create an instance of loginData
2) populate it
3) validate it, and store the validation result in BindingResult
4) invoke the method (with loginData and BindingResult values), no matter whenever the binding Result contains an error or not