I can't figure out why my field values of User class is disappearing when I'm trying to navigate between forms.
#Controller
public class WizardController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String userForm(#ModelAttribute("user") User user) {
return "form/userForm";
}
#RequestMapping(value = "/user", method = RequestMethod.POST)
public String processedForm(#ModelAttribute("user") User user, BindingResult result,
ModelMap model) {
if (result.hasErrors()) {
return "form/userForm";
}
ArrayList<String> p = new ArrayList<String>();
p.add("add_user");
p.add("delete_user");
model.addAttribute("permissions", p);
model.addAttribute("user", user);
return "form/permissionForm";
}
#RequestMapping(value = "/show", method = RequestMethod.POST)
public String show(#ModelAttribute("user") User user, BindingResult result) {
if (result.hasErrors()) {
System.out.println("Error");
return "show";
} else {
System.out.println(user);
return "show";
}
}
}
My permissionForm.jsp
<form:form action="/show" method="post" modelAttribute="user">
<form:errors path="*" cssClass="errorblock" element="div"/>
<p>
<form:select path="permissions">
<form:options items="${permissions}"/>
</form:select>
</p>
<p><input type="submit" value="Add"/></p>
</form:form>
userForm.jsp
<form:form action="/user" method="post" modelAttribute="user">
<form:errors path="*" cssClass="errorblock" element="div"/>
<p><form:input path="name" placeholder="Name"/></p>
<p><form:input path="age" placeholder="Age"/></p>
<p><form:input path="email" placeholder="Email"/></p>
<p><form:input path="password" placeholder="Password"/></p>
<p><input type="submit"></p>
</form:form>
On show.jsp I'm rendering my user
${user}
And POJO class
public class User implements Serializable {
private String name;
private String email;
private String password;
private Integer age;
private List<String> permissions;
// getters, setters, toString
}
Output: User{name='null', email='null', password='null', age=null, permissions=[delete_user]}. And I need a full object, somthing like this:
User{name='John', email='john#gmail.com', password='12345', age=21, permissions=[delete_user]}.
Thanks for any help.
Because in permissionForm.jsp, the form has inputs for the permissions field only, hence when submiting it, it will pass on a User object with the permissions field only populated, the rest will be null.
If you need to have values in the rest of the fields, add hidden inputs like this:
<form:form action="/show" method="post" modelAttribute="user">
<form:errors path="*" cssClass="errorblock" element="div"/>
<p>
<form:select path="permissions">
<form:options items="${permissions}"/>
</form:select>
<form:hidden path="name"/>
<form:hidden path="age"/>
<form:hidden path="email"/>
</p>
<p><input type="submit" value="Add"/></p>
</form:form>
However, for the password field, I wouldn't advise adding a hidden field in the JSP because it will expose the password for anyone who can read html source.
Another and better solution would be using the session, by storing and modifying the user in the session between forms, first store the user on the first submit:
#RequestMapping(value = "/user", method = RequestMethod.POST)
public String processedForm(#ModelAttribute("user") User user, BindingResult result,
ModelMap model,HttpSession session) {
if (result.hasErrors()) {
return "form/userForm";
}
session.setAttribute("user",user);
ArrayList<String> p = new ArrayList<String>();
p.add("add_user");
p.add("delete_user");
model.addAttribute("permissions", p);
model.addAttribute("user", user);
return "form/permissionForm";
}
#RequestMapping(value = "/show", method = RequestMethod.POST)
public String show(#ModelAttribute("user") User user, BindingResult result,HttpSession session) {
User userInSession = (User)session.getAttribute("user");
userInSession.setPermissions(user.getPermissions());
if (result.hasErrors()) {
System.out.println("Error");
return "show";
} else {
System.out.println(userInSession);
return "show";
}
}
Related
I have two hidden input fields to implement Friend system. I pass user and friend's ids in Model and then use them in thymeleaf page to pass them in form to PostMapping and save changes. However, PostMapping cannot see my second #RequestParam.
Both customer and friend are properly passed to model as I tried to output them on website using th:text
Snippets of code:
Adding both users to model:
#GetMapping("/customer/{customerId}")
public String getCustomer(Model theModel, #PathVariable int customerId, #AuthenticationPrincipal MyUserDetails user) {
Customer currUser = customerService.findById(user.getCustomer().getId());
Customer foundCustomer = customerService.findById(customerId);
theModel.addAttribute("friend", foundCustomer);
theModel.addAttribute("customer", currUser);
return "customerdetails";
Snippet of Thymeleaf code:
<form action="#" th:action="#{/home/addFriend}" th:object="${friend}" method="post">
<input type="hidden" th:field="${friend.id}" th:attr="name='friendId'" />
<input type="hidden" th:field="${customer.id}" th:attr="name='customerId'" />
<input type="submit" value="Add Friend" class="btn btn-primary flex-grow-1" />
</form>
PostMapping (where issue occurs):
#PostMapping("/addFriend")
public String getPost(#RequestParam("friendId") int friendId, #RequestParam("customerId") int customerId) {
Customer friendCustomer = customerService.findById(friendId);
Customer currCustomer = customerService.findById(customerId);
System.out.println(currCustomer.getFirstName());
System.out.println(friendCustomer.getFirstName());
return "redirect:/home";
}
Code of error:
[org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'friendId' for method parameter type int is not present]
It will be a lot easier to implement using a custom form object.
For example, create this class:
public class AssignFriendFormData {
private String friendId;
private String customerId;
// getter and setters here
}
Use this in your #GetMappping:
#GetMapping("/customer/{customerId}")
public String getCustomer(Model theModel, #PathVariable int customerId, #AuthenticationPrincipal MyUserDetails user) {
Customer currUser = customerService.findById(user.getCustomer().getId());
Customer foundCustomer = customerService.findById(customerId);
AssignFriendFormData formData = new AssignFriendFormData();
formData.setFriendId(foundCustomer.getId());
formData.setCustomerId(currUser.getId());
theModel.addAttribute("formData", formData);
return "customerdetails";
Change the form to this:
<form action="#" th:action="#{/home/addFriend}" th:object="${formData}" method="post">
<input type="hidden" th:field="*{friendId}" />
<input type="hidden" th:field="*{customerId}" />
<input type="submit" value="Add Friend" class="btn btn-primary flex-grow-1" />
</form>
Finally, update the #PostMapping to use the form data object:
#PostMapping("/addFriend")
public String getPost(#Valid #ModelAttribute("formData") AssignFriendFormData formData) {
Customer friendCustomer = customerService.findById(formData.getFriendId());
Customer currCustomer = customerService.findById(formData.getCustomerId());
System.out.println(currCustomer.getFirstName());
System.out.println(friendCustomer.getFirstName());
return "redirect:/home";
}
See Form handling with Thymeleaf for a more in-depth tutorial on this.
I want to show validation error in my jsp page.
My object is:
public class MyObjectDTO{ #valid private TextDTO text1; #valid private TextDTO text2 }
public class TextDTO{ #NotBlank private String code;#NotBlank private String label;}
My controller:
#RequestMapping(value = "/create", method = RequestMethod.POST)
public String creationProjet(#Valid #ModelAttribute MyObjectDTO obj, BindingResult result,
Model model) {
if (result.hasErrors()) {
model.addAttribute("hasErrors", true);
return "create";
} else {
....
return "redirect:/list";
}
}
my jsp
<div class="col-md-6 form-group ${requestScope['org.springframework.validation.BindingResult.obj'].hasFieldErrors('text1') ? 'has-error' : ''}">
<label class="col-lg-3 control-label">my label</label>
<div class="col-lg-5">
<form:select class="form-control" name="type" path="text1.code" id="selectType">
<option value="">---------</option>
<c:forEach items="${types }" var="type">
<form:option value="${type.id }">
<c:out value=" ${type.code}"></c:out>
</form:option>
</c:forEach>
</form:select>
<form:errors path="text1.code" class="has-error error"></form:errors>
</div>
</div>
My controller redirects to the page create but the errors are not showing. In debug mode there is one error that indicates text1.code cannot be a blank.
In your Jsp page add following line
<div class="col-md-6 form-group ${requestScope['org.springframework.validation.BindingResult.obj'].hasFieldErrors('text1') ? 'hasErrors' : ''}">
or use hasFieldErrors() instead
<div class="col-md-6 form-group ${requestScope['org.springframework.validation.BindingResult.obj'].hasFieldErrors()}">
And About addAttributes("hasErrors",true), use addFlashAttribute() which is store in flashmap and Object (In your case Error Message will be alive when you navigate to create page or redirect between two controller.) Look at this for more
In your controller Add RedirectAttributes Object like this
#RequestMapping(value = "/create", method = RequestMethod.POST)
public String creationProjet(#Valid #ModelAttribute MyObjectDTO obj,
BindingResult result,
RedirectAttributes redirectAtt,
Model model) {
if (result.hasErrors()) {
redirectAtt.addFlashAttribute("hasErrors","ur message");//instead of true you can write your own message
return "create";
} else {
....
}
return "redirect:/list";
}
}
If you write your own message instead of true
<c:if test="${not empty hasErrors">
<p>${hasErrors}</p>
</c:if>
only you missing is put part to model map.You are putting only flag. But you need to put result.
if(result.hasErrors()){
mm.addAttribute("errors", result);
return "create";
}
From what I can tell this is set up correctly but I am getting the following error:
java.lang.IllegalStateException: Neither BindingResult nor plain target
object for bean name 'person' available as request attribute
Form
<form action="#" th:action="#{/person}" th:object="${person}" method="post" th:required="required">
<input type="text" th:field="*{subject}" class="contact col-md-6" placeholder="Name *" th:required="required"/>
<input type="text" th:field="*{name}" class="contact col-md-6" placeholder="Name *" th:required="required"/>
<input type="text" th:field="*{lastName}" class="contact col-md-6" placeholder="Name *" th:required="required"/>
<input type="email" th:field="*{email}" class="contact noMarr col-md-6" placeholder="E-mail address *" th:required="required"/>
<textarea name="comment" class="contact col-md-12" th:field="*{message}" placeholder="Message *"></textarea>
<input type="submit" id="submit" class="contact submit" value="Send message"/>
</form>
Person.java
public class Person {
private int id;
private String name;
private String lastName;
private String email;
private String subject;
private String message;
....
}
Controller
#Controller
public class ApplicationController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String indexPage() {
return "index";
}
#RequestMapping(value="/person", method=RequestMethod.GET)
public String contactForm(Model model) {
model.addAttribute("person", new Person());
return "index";
}
#RequestMapping(value="/person", method=RequestMethod.POST)
public String contactSubmit(#ModelAttribute Person person, Model model) {
model.addAttribute("person", person);
return "result";
}
}
I looked at Spring-boot and Thmeleaf setup and it looks like my setup is identical.
--------------------- Update 1 -----------------------
I have changed my post method to include BindingResult with no success.
#RequestMapping(value="/person", method=RequestMethod.POST)
public String contactSubmit(#Valid #ModelAttribute Person person, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()){
System.out.println("There was a error "+bindingResult);
System.out.println("Person is: "+ person.getEmail());
return "index";
}
model.addAttribute("person", person);
return "result";
}
You forgot to add BindingResult after your #ModelAttribute :
#RequestMapping(value="/person", method=RequestMethod.POST)
public String contactSubmit(#ModelAttribute Person person, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
//errors processing
}
model.addAttribute("person", person);
return "result";
}
I'm already have answered to question like this :
html form validation using thymeleaf not working spring boot
Model attribute has to be initialized (using GET method) before calling post method.
In your case you need one more method in controller which does model.addAttribute("person",new Person()); and it has to be called before post.
Refer below link:
https://spring.io/guides/gs/handling-form-submission/
OR
http://forum.thymeleaf.org/Neither-BindingResult-nor-plain-target-object-for-bean-name-miniDoniie-available-as-request-attribute-td4027859.html
It has GetMapping as well as PostMapping in controller.
You need declare a #ModelAttribute for "person". Something like:
#ModelAttribute("Myperson")
public Person newPerson() {
return new Person();
}
Then, declare you #PostMapping and set in #ModelAttribute the name "person".
#RequestMapping(value="/person", method=RequestMethod.POST)
public String contactSubmit(#ModelAttribute Person person, Model model) {
model.addAttribute("person", person);
return "result";
}
I hope it works for whoever reads this :)
First I had the form in index.html
#RequestMapping(value = "/", method = RequestMethod.GET)
public String indexPage(){
return "index";
}
So when my form:
<form th:action="#{/person}" th:object="${person}" method="post" >
<input type="text" th:field="*{subject}" class="contact col-md-6" placeholder="Subject *" />
<input type="text" th:field="*{name}" class="contact col-md-6" placeholder="Name *" />
<input type="text" th:field="*{lastName}" class="contact col-md-6" placeholder="Last Name *" />
<input type="email" th:field="*{email}" class="contact noMarr col-md-6" placeholder="E-mail address *" />
<textarea name="comment" class="contact col-md-12" th:field="*{message}" placeholder="Message *" ></textarea>
<input type="submit" id="submit" class="contact submit" value="Submit" />
<input type="reset" value="Reset" />
</form>
Was looking for / it was hitting the above method, NOT:
#RequestMapping(value="/", method=RequestMethod.GET)
public String contactForm(#Valid #ModelAttribute("person") Person person, BindingResult bindingResult,
HttpServletRequest request, Model model) throws IOException {
if(bindingResult.hasErrors()){
System.out.println("There was a error "+bindingResult);
return "index";
}
model.addAttribute("person", new Person());
return "index";
}
Which was correct!
I had to remove the first method and it worked.
i would like to retrieve a value from combobox in jsp <form:select /> which get its values from another Entity named Group, and assign it to a User. both of Entities are mapped together with #OneToMany and #ManyToOne. so many users can be assigned to one Group.
PS: the combobox Content is dynamic.
here is my Controller file:
#RequestMapping(value = "/user/add")
public ModelAndView addUserPage() {
ModelAndView mav = new ModelAndView("/admin/user/addUser");
List<Group> groups = groupService.listGroups();
List<String> groupNames = new ArrayList<String>();
for (Group m : groups) {
groupNames.add(m.getGroupName());
}
mav.addObject("user", new User());
mav.addObject("groupNames", groupNames);
return mav;
}
#RequestMapping(value = "/user/add/process")
public ModelAndView addingUser(#ModelAttribute(value = "user") User user,
#ModelAttribute(value = "groupName") String groupName) {
ModelAndView mav = new ModelAndView("redirect:/spring/user/list.html");
user.setDateModification(new Date());
Group group = groupService.getGroupByGroupName(groupName);
user.setGroup(group);
userService.addUser(user);
String message = "User was successfully added.";
mav.addObject("message", message);
return mav;
}
and my JSP file:
<form:form method="POST" commandName="user"
action="/myApplication/spring/user/add/process.html">
<label>username</label>
<form:input path="username" class="span12"></form:input>
<label>Password</label>
<form:input path="password" type="password" class="span12"></form:input>
<label>Groupe</label>
<form:select path="group">
<form:option value="NONE" label="--- Choisir un groupe ---" />
<form:options items="${groupNames}" />
</form:select>
<label>Code Banque</label>
<form:input path="codeBanque" class="span12"></form:input>
<label>Code Agence</label>
<form:input path="codeAgence" class="span12"></form:input>
<input value="Ajouter" type="submit"
class="btn btn-primary pull-right">
<div class="clearfix"></div>
</form:form>
My goal is to assign the User to a Group threw the form of adding users. i was able to get one ModelAttribure which is user, but i can't get Two !
thanks
i fixed my problem and here is the solution:
UserController:
#RequestMapping(value = "/user/add")
public ModelAndView addUserPage() {
ModelAndView mav = new ModelAndView("/admin/user/addUser");
List<Group> groups = groupService.listGroups();
mav.addObject("groups", groups);
mav.addObject("user", new User());
return mav;
}
and my Jsp page:
<label>Groupe</label>
<form:select path="group.idGroup" items="${groups}"
itemValue="idGroup" itemLabel="groupName" multiple="false"
class="span12">
</form:select>
here is the jsp:
<c:if test="${!empty USERS}">
<form:form method="post" action="requestForFriends.html" commandName="user">
<form:select path="userName">
<c:forEach items="${USERS}" var="user">
<form:option value="${user.userName}"></form:option>
</c:forEach>
</form:select>
<input type="submit" value="Send freindship request" />
</form:form>
</c:if>
here is the controller's relevant part:
#RequestMapping("/toAddFriend")
public ModelAndView toAddNewFriend() {
Map<String, Object> model = new HashMap<String, Object>();
model.put("USERS", userService.getUsers());
ModelAndView ret=new ModelAndView("addFriend", model);
ret.addObject("user", new User());
return ret;
}
if I do the code above then a brand new User object will be created when I submit the form. But when I click "submit" I would like to get one of the ALREADY EXISTING User instance, which are clearly present in here:
<c:forEach items="${USERS}" var="user">
<form:option value="${user.userName}"></form:option>
How can I modify my code to get the existing object?
Actually I know an ugly way to get the existing instance. By submitting the form I can create a new User instance with the same username as the one has that I am looking for. Then in the DaoImpl class I can query for the "old" User which shares the userName with the newly created one. But I guess it is quite wasteful and ugly so I can't believe that there is no better way.
UPDATE
<!-- language: lang-java -->
#RequestMapping(value = "/requestForFriends", method = RequestMethod.POST)
public ModelAndView requestNewFriend(#ModelAttribute("user") User user, BindingResult result) {
System.out.println(user.getUserName());
System.out.println(user.getEmail());
Map<String, Object> model = new HashMap<String, Object>();
model.put("USERS", userService.getUsers());
ModelAndView ret=new ModelAndView("addFriend", model);
ret.addObject("user", new User());
return ret;
}
#ModelAttribute
public void fillUsers(Model model) {
model.addAttribute("USERS", userService.getUsers());
}
#ModelAttribute
public void getUser(#RequestParam(required = false) String userName, Model model) {
User user = null;
if (userName != null) {
user = userService.getUserByName(userName);
}
if (user == null) {
user = new User();
}
model.addAttribute("user", user);
}
#RequestMapping("/toAddFriend")
public String toAddNewFriend() {
return "addFriend";
}
#RequestMapping(value = "/requestForFriends", method = RequestMethod.POST)
public String requestNewFriend(#ModelAttribute("user") User user) {
System.out.println(user.getUserName());
System.out.println(user.getEmail());
return "addFriend";
}