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.
Related
I am having a small issue with Spring Boot forms displaying the information of the path value instead of the placeholder once you get to the editProfile.jsp. I want the input field to look like this;
Edit Profile Page instead of this Wrong Edit Profile. I do not want my users to have to click, select and delete the auto completed value. I want it to show the placeholder only and allow them to overwrite what is shown with ease.
This is the editProfile.jsp
<%--#elvariable id="editProfile" type=""--%>
<form:form method="POST" modelAttribute="editProfile">
<div class="MyForm form-group">
<h1>Edit Profile</h1>
<form:input type="email" class="MyInput" id="email" path="email" placeholder="${editProfile.email}" />
<form:button type="submit" class="from-control">Submit</form:button>
</div>
<div>
<img src="images/reg1.png" alt="picture">
</div>
</form:form>
</body>
</html>
This is the code specified in the Controller
#RequestMapping(value = "edit/{email}", method = RequestMethod.GET)
public String getEditUserData(#PathVariable("email") String email, Model model) {
AccountEntity accountInstance = accountRepo.findByEmail(email);
model.addAttribute("editProfile", accountInstance);
return "editProfile";
}
#RequestMapping(value = "edit/{email}", method = RequestMethod.POST)
public String enterEditUserData(#ModelAttribute("login") AccountEntity accountForm, #PathVariable("email") String email, Model model ) {
AccountEntity accountInstance = accountRepo.findByEmail(email);
accountInstance.setEmail(accountForm.getEmail());
accountRepo.save(accountInstance);
return "redirect:/login";
}
I have figured it out; You have to add a model of a new Entity, so the path variable does not fill in with the instance of the specific path value. Here is the new code, and compare it to the one I sent above.
#RequestMapping(value = "edit/{email}", method = RequestMethod.GET)
public String getEditUserData(#PathVariable("email") String email, Model model) {
AccountEntity accountInstance = accountRepo.findByEmail(email);
model.addAttribute("editProfile2", new AccountEntity());
model.addAttribute("editProfile1", accountInstance);
return "editProfile";
}
<%--#elvariable id="editProfile" type=""--%>
<%--#elvariable id="editProfile2" type=""--%>
<form:form method="POST" modelAttribute="editProfile2">
<div class="grid form-group">
<h1>Edit Profile</h1>
<form:input type="email" class="MyInput" id="email" path="email" placeholder='${editProfile1.email}' />
<form:button type="submit" class="from-control">Submit</form:button>
</div>
How to send data from jsp to controller in spring using form ?
<form:form action="${searchUrl}" method="post">
<div class="form-group">
<label class="control-label"> search by ID </label>
<br>
<input type="text" id="ticketId" placeholder=" Enter ticket ID ">
</div>
</form:form>
Could not able to get ticketId using this method
You need to declare name attribute (name="ticketId") and access the same on the controller. Like below.
JSP:
<input type="text" name="ticketId" id="ticketId" placeholder=" Enter ticket ID">
Controller:
#RequestParam(value = "ticketId", required = false) String ticketId
I hope it is helpful to you, for me it is working fine.
If you want to use spring forms, make sure to follow the below steps :
Step:1.In the spring controller, you should return the bean object like below to respected JSP.
Class User {
private String ticketId;
// setter & getter
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String init(Model model) {
model.addAttribute("msg", "Please Enter Your Login Details");
model.addAttribute("loginBean", new User());
return "login";
}
Step:2 add a model attribute and add taglib in the JSP page.
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<form:form action="${searchUrl}" method="post"
modelAttribute="loginBean">
<div class="form-group">
<label class="control-label"> search by ID </label>
<br>
<form:input type="text" id="ticketId" path = "ticketId"
placeholder=" Enter ticket ID " />
</div>
</form:form>
Step : 3
#RequestMapping(value = "/test", method = RequestMethod.Post)
public String init(Model model,
#ModelAttribute("user") User user,BindingResult
result) {
sout("user"+user);
return "home";
}
I have a page where I get a list of entries. Now, I want to be able to search from those list.
my current url for retrieving list is this /show/products. I want to add a search form in this page so that I can search with request parameter.
Yes, I can use ajax but I have to do it with request parameters.
So if I search for a product name, then - /show/products?name=someName
<form ui-jp="parsley" th:action="#{/show/products(name=${pName})}" th:object="${pName}" method="get">
<div class="row m-b">
<div class="col-sm-6">
Search by Name:
<input id="filter" type="text" th:field="*{pName}" class="form-control input-sm w-auto inline m-r"/>
<button class="md-btn md-fab m-b-sm indigo">
<i class="material-icons md-24"></i>
</button>
</div>
</div>
</form>
And this is what I tried in controller:
#GetMapping("/show/products")
public String getProduct(Model model,
#RequestParam(required = false) String name,
#ModelAttribute String pName) {
List<Product> products = this.productService.getAllProducts(name)
model.addAttribute("products", products);
return "show_product";
}
I am getting this error:
Neither BindingResult nor plain target object for bean name 'pName' available as request attribute
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:153)
at org.springframework.web.servlet.support.RequestContext.getBindStatus(RequestContext.java:897)
You are trying to use variable pName (Model attribute) as a form object.
In your view you are passing a model attribute to form like this th:object="${pName}" but instead you need to pass a form object.
A form object is not a class but rather a simple java object (POJO). You can think of form object as your form but on server side.
Before you can use form object in your view, you need to create it and add it to the Model.
you will define it like this
public class MyFormObject{
private String pName;
public String getPName(){
return pName;
}
public void setPName(String pName){
this.pName = pName;
}
}
now your controller method will become
#GetMapping("/show/products")
public String getProduct(Model model,
#ModelAttribute("myFormObject") MyFormObject myFormObject,
BindingResult result) {
List<Product> products = this.productService.getAllProducts(myFormObject.getPName());
model.addAttribute("products", products);
return "show_product";
}
Then you can pass the form object to your form like this
<form ui-jp="parsley" th:action="#{/show/products}" th:object="${myFormObject}" method="get">
<div class="row m-b">
<div class="col-sm-6">
Search by Name: <input id="filter" type="text" th:field="*{pName}" class="form-control input-sm w-auto inline m-r"/>
<button class="md-btn md-fab m-b-sm indigo"><i class="material-icons md-24"></i></button>
</div>
</div>
</form>
You need to read the documentation, all these are explained there in detail.
I am building a MVC application using thymeleaf and Spring and Hibernate. My question here is more about hibernate than spring.
This is what i have so far.
A UI
<form role="form" th:action="#{/user/{userId}/official(userId=${userId})}" th:object="${user}" method="post">
<!-- first form group -->
<div class="form-group">
<label class="control-label col-xs-2">First Name</label>
<div class="col-xs-2">
<input type="text" class="form-control" th:field="*{firstName}" placeholder="First Name" />
</div>
<label class="control-label col-xs-2">Last Name</label>
<div class="col-xs-3">
<input type="text" class="form-control" th:field="*{lastName}" placeholder="Last Name" />
<!-- first form group end -->
</div>
<br/><br/>
<!-- third form group -->
<div class="form-group">
<label class="control-label col-xs-2">Email Address</label>
<div class="col-xs-2">
<input type="text" class="form-control" th:field="*{emailAddress}" placeholder="Email Address" />
</div>
</div>
<div class="form-group">
<div class="col-xs-2">
<input type="submit" value="Update" class="btn btn-primary" />
</form>
Controller :
#Controller
public class UserController {
#Autowired
private IUserService userServiceImpl;
#RequestMapping(value = "/user/{userId}/official", method = RequestMethod.GET)
public String getUserOfficialInfo(#PathVariable("userId") Integer userId, Model model) throws ServiceBusinessException {
UserBO userBO = userServiceImpl.findUserByUserId(userId);
model.addAttribute("user", userBO);
model.addAttribute("userId", userId);
model.addAttribute("genders", EnumSet.allOf(Gender.class));
return "official";
}
#RequestMapping(value = "/user/{userId}/official", method = RequestMethod.POST)
public String updateUserOfficialInfo(#PathVariable("userId") String userId, #ModelAttribute("user") UserBO user,BindingResult result, Model model) throws ServiceBusinessException {
userServiceImpl.updateUser(user);
UserBO userBO = userServiceImpl.findUserByUserId(Integer.parseInt(userId));
model.addAttribute("user", userBO);
model.addAttribute("userId", userId);
model.addAttribute("genders", EnumSet.allOf(Gender.class));
return "official";
}
}
DAO :
#Override
public void updateUser(UserEntity user) throws DaoException {
entityManager.merge(user);
}
The GET method in the controller, gets the user object to the view. But on the VIew i am just displaying few of those attributes of a user object in the form.
On the form Submit, the POST method in the controller gets called, which calls the service layer and then the merge method in the DAO gets executed.
Now what I have observed is that this merge method on the entity manager is updating the attributes which are not there in the form to null.
I think this is the expected behaviour since the object is detached when its called from the POST method. So the right thing to do here is to first fetch the entity object from the database and then to that object set the fields which are changed in the form and then call the merge method.
Can some one let me know if the above what I said is correct ?
If yes, then my next question would be that isnt this quite tedious and kind of bit more effort. I mean there are going to be cases where in I would not want to display the entire object in the form. Also not in hidden fields. I am quite surprise that there is no way to handle this and I have to follow the approach I just described above each time.
Is there a better way to do this ? Wouldn't i just use JDBC template instead ? I know I would be writing boiler plate code there but I am kind of writing getters and setters here as well for each round trip to the UI.
Consider annotating your entity with org.hibernate.annotations.Entity.
#Entity
#Table(name = "user")
#org.hibernate.annotations.Entity(
dynamicInsert = true, dynamicUpdate=true
)
public class User implements java.io.Serializable {
// your properties
}
If you are using a 4.x version of Hibernate, you may want to use #DynamicUpdate instead since the usage of #org.hibernate.annotations.Entity has been deprecated recently.
References:
https://www.mkyong.com/hibernate/hibernate-dynamic-update-attribute-example/
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/Entity.html
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/DynamicInsert.html
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/DynamicUpdate.html
you can put the following code in a util class and invoke it when you want to fill your object based on another reference object:
public static <T> void fillNotNullFields(T reference, T source) {
try {
Arrays.asList(Introspector.getBeanInfo(reference.getClass(), Object.class)
.getPropertyDescriptors())
.stream()
.filter(pd -> Objects.nonNull(pd.getReadMethod()))
.forEach(pd -> {
try {
Object value = pd.getReadMethod().invoke(source);
if (value != null)
pd.getWriteMethod().invoke(reference, value);
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
So, you can do this on the service:
public UserBO updateUser(String userId, UserBO user ) {
UserBO reference = findOne(Integer.parseInt(userId));
fillNotNullFields(reference, user);
return updateUser(reference);
}
I found the suport for this answer here. Hope it helps.
You will need to store the unnecessary bean properties in hidden fields so that they get remapped when the form is posted to the controller. For example, if you do not want to show the date of birth on the page (assuming that date of birth is already an attribute of the user), simply add the following tag inside the form.
<input type='hidden' th:field="*{dateOfBirth}" />
I currently have the following method within my controller that takes the users form input and passes it to an SQL query to return a list of matches.
#RequestMapping(value = "/resultsPage", method = RequestMethod.GET)
public ModelAndView newSearch(HttpServletRequest request)
{
int id = Integer.parseInt(request.getParameter("id"));
List<newobject> listSearch = newDAO.loadSearch(id);
ModelAndView model = new ModelAndView("results");
model.addObject("listSearch", listSearch);
return model;
}
I have added #NotNull to the ID filed in the newObject however I am unsure on how to modify my method above to check the variable entered (or not entered) by the user.
I am also unsure how to display the error on the html page. My existing code is below:
<form method="get" th:action="#{/results}">
<input id="search" name="id" class="search" placeholder="Ref..."
type="text" maxlength="10" title="Numerical values only" />
<button type="submit" method="post" style="display:none;" id="search">Search</button>
</form>
Can anyone give me some advice on how I would add the validation as I am just getting errors with everything I try.