I am currently sending a logged in User object to view which will be injected into a form via th:object. I want to update certain attributes in this User object but still maintain the rest of the object's content. However, when I submit this form, the User object contains null values for all values except the one I have set in the thymeleaf page. I know one solution would be to add hidden tags for the values I want to keep but that seems very tedious if the User object is huge.
#RequestMapping(value="/newprofile", method=RequestMethod.GET)
public String newProfile(Model model, Principal principal) {
String email = principal.getName();
User user = userService.findUserByEmail(email);
model.addAttribute("user", user);
return "newprofile";
}
#RequestMapping(value="/newprofile", method=RequestMethod.POST)
public String registerNewProfile(Model model,User user, Principal principal) {
userService.saveProfile(user); //this user object will contain null values
return "redirect:/profile";
}
Here is how the form looks. The user object that comes in is an existing User with its values already set. There are member variables that can be updated.
<form autocomplete="off" action="#" th:action="#{/newprofile}" th:object="${user}" method="post" class="form-signin" role="form">
<h3 class="form-signin-heading">Registration Form</h3>
<div class="form-group">
<div class="">
<input type="text" th:field="*{profile.basicInfo.age}" placeholder="Name" class="form-control" />
</div>
</div>
<div class="form-group">
<div class="">
<button type="submit" class="btn btn-primary btn-block">Update profile</button>
</div>
</div>
</form>
Once the form is submitted, I perform a save of that User object via Spring JPA's save() method. However, if the User object contains nulls, it will incorrectly "update" those values to null. Again, I can do some checking to validate which members should be updated and which should not but that seems incorrect...
#Override
public User saveProfile(User user) {
// TODO Auto-generated method stub
userRepository.save(user);
return user;
}
We can use the bean validation API annotations on model class attributes like below:
#NotNull(message = "Name cannot not be null")
private String name;
Similar use of #NotEmpty, #NotBlank
By this, it won't enter if any of the validation fails.
If copying the existing User bean before update is an option, following api may be used.
BeanUtils.copyProperties
Please do note that there are two popular BeanUtils.copyProperties. One from Apache and the other one mentioned with this post. The order of method parameters for both these api are different.
Related
My form is based on Thymeleaf, I am not using tables because it has many fields.
In the form I have 3 buttons, one is a submit and the others are links. With a link button I want to make a query using a parameter that sends a string to the controller.
I want to get the string that was passed in the URL to the controller and receive the data with an #RequestParam annotation.
I have no error messages, what happens is that the string reaches the controller empty.
This is my link button:
<a class="btn btn-success text-white btn-sm mr-3 " style="border-radius: 16px;" th:href="#{/config/item/items/select(productId=${item.productId})}">
Search
</a>
This is my text field where the user places the query:
<div class="col-2 form-group"
th:classappend="${#fields.hasErrors('item.productId')}? 'has-error':''">
<label for="productId"><strong>Item id </strong></label> <input
type="text" class="form-control" id="productId" name="productId"
th:field="${item.productId}">
</div>
And all this goes inside my form:
<form th:action="#{/config/item/items/save}" method="get"
th:model="${item}">
I have used these two but with no results:
th:href="#{'/config/item/items/select'+${item.productId}}"
th:href="#{|/config/item/items/select${item.productId}|}"
I have reviewed the documentation provided by Thymeleaf at: https://www.thymeleaf.org/doc/articles/standardurlsyntax.html, in chapter number nine.
And I have also seen tutorials but it doesn't work.
If you want to use the #RequestParam annotation you need to add a parameter in your link.
Your code should be something like:
th:href="#{select(productId=${item.productId})"
(According you added the object item to your template)
In your controller "select" you have to use the annotation #RequestParam to retrieve the info:
#GetMapping (path = "/select")
public String list(Model model,
#RequestParam(name = "productId") int productId) {
// rest of the code
}
When I get an entity "Product" in the controller, I have all the null data.
#GetMapping("/admin/product/delete")
public ModelAndView showProductDeletePage(
#RequestParam String productId
){
Long longId = Long.parseLong(productId);
Product product = productService.getProductById(longId);
ModelAndView modelAndView = new ModelAndView("/admin/product/delete");
modelAndView.addObject("product", product);
return modelAndView;
}
But, in the string
modelAndView.addObject("product", product);
I send a product to the form.
<div class="w3-container">
<form th:action="#{'/admin/product/delete?productId=' + ${product.getId()}}" method="post">
<h2>Delete product</h2>
<p>If you submit, product will be deleted irrevocably</p>
<div th:text = "${product.getProductName()}" class="w3-panel w3-card-4"><p>w3-card-4</p>
</div>
Cancel
<input class="w3-button w3-red" type="submit" value="Delete"/>
</form>
</div>
And, for example, in the strings
<div th:text = "${product.getProductName()}" class="w3-panel w3-card-4"><p>w3-card-4</p>
<form th:action="#{'/admin/product/delete?productId=' + ${product.getId()}}" method="post">
my app has not a problem and returns to the screen correctly data.
How is it possible?
An issue in it, that I cannot use the entity in the other cases. And in general - how is it possible??
Hibernate uses proxies for the entities and currently you are looking at the proxy. The proxy itself acts like a Product but doesn't contain the actual data. That is present in the handler.
If you click through on the handler you eventually will get to the actual Product instance.
Possible reasons are:
You may have different connection strings.
Your web application is not in running state.
You have cached data.
Its not possible in the way you are trying to describe the issue, add debugger/breakpoints to narrow down your issue.
I can't figure out how to do a simple, 1-field validation where I check the value against a service (or really any other logic check).
Most of the form validation I see uses a class to hold form data using javax.validation and marking up with attributes like #NotNull, #Min(10), etc. And then checking a BindingResult.hasErrors(). Like here: https://spring.io/guides/gs/validating-form-input/
I'm trying to do this for a single field and I want to validate against what a service will return to me rather than one of those validation attributes.
What do I put in my POST handler in the controller to get this going?
This is what I have in my controller to manage the result from the form
#RequestMapping(value = "/myController", method=RequestMethod.POST)
public String checkFieldVal(#RequestParam String valFromForm){
if(!myService.isThisValueGood(valFromForm)) {
//Show the user their value is bad
}
//return to some other page
}
And this is in my JSP (something simple like this):
<form id="form" method="POST">
<label for="valueToCheck">What's your value:</label>
<input type="text" id="valueToCheck" name="valueToCheck"></input>
<input type="submit" value="Submit">
</form >
Try to replace this:
<form id="form" method="POST">
With adding the action like this:
<form action="myController" id="form" method="POST">
And replace this:
#RequestMapping(value = "/myController", method=RequestMethod.POST)
public String checkFieldVal(#RequestParam String valFromForm){
With adding the right name:
#RequestMapping(value = "/myController", method=RequestMethod.POST)
public String checkFieldVal(#RequestParam("valueToCheck") String valueToCheck){
The param name must match in both controller and html.
#RequestParam String valFromForm
<input ... name="valueToCheck">
Option 1: Leaving jsp as it is, you change controller code as,
#RequestParam String valueToCheck
Or you may add qualifier to parameter name as,
#RequestParam("valueToCheck") String valFromForm
Option 2: Leaving controller as it is, you change jsp as,
<input ... name="valFromForm">
I've page editPLayer where I used Thymeleaf. I want add buttons Next and Previously to change information in forms about player.
Buttons like :
<input class="button2 button-switch" value="<<"/>
<input class="button2 button-switch" value=">>"/>
editPlayer.html loooks like that:
<div class="card">
<form action="#" th:action="#{/editPlayer}" th:object="${player}" method="post">
<input type="hidden" th:field="*{id_player}"></input>
<div class="info">Name: </div>
<input type="text" th:field="*{name}" placeholder="Imie" th:class="${#fields.hasErrors('name')}? 'error'" /><br/>
<div class="info">Surname: </div>
<input type="text" th:field="*{surname}" placeholder="Nazwisko" th:class="${#fields.hasErrors('surname')}? 'error'" /><br/>
<div class="info">Age: </div>
<input type="text" th:field="*{age}" th:class="${#fields.hasErrors('age')}? 'error'"/><br/>
<div class="info">Number: </div>
<input type="text" th:field="*{number}" th:class="${#fields.hasErrors('number')}? 'error'"/><br/>
<input type="submit" class="button button-submit" value="EDIT"/>
</form>
</div>
in code above player is object returned from Controller :
#Controller
#RequestMapping(value = "/editPlayer")
public class EditPlayerController {
#Autowired
private PlayerRepository playerRepository;
#RequestMapping(method = RequestMethod.GET)
public String editPlayerForm(Model model){
model.addAttribute("player", playerRepository.findOne((long) 1));
return "editPlayer";
}
#RequestMapping(method = RequestMethod.POST)
public String editPlayerSubmit(#Valid #ModelAttribute("player") Player player,
BindingResult bindingResult, Model model,
HttpSession session){
if(bindingResult.hasErrors()) {
return "editPlayer";
}
playerRepository.save(player);
return "redirect:/coachSite";
}
}
Now I add to model Player object with Id = 1 (in this line)
model.addAttribute("player", playerRepository.findOne((long) 1));
I want add to model List of players, like :
model.addAttribute("player", playerRepository.findAll());
... and in editPlayer.html placed switched button (Next and Prevoiusly) to show inf forms for example first user from list, click Nextand show next user from list in forms. How can I do this ? Any idea ? Maybe somebody have nice idea using JavaScript ?
I dont need pages, only currently displayed object player and option to Next and Prevoius object, it must be show in my form int editPlayer.html. I don't know how implements this.
But for sure you might need a subset of this solution for prev / next buttons. Either you are doing the paging by javascript and styling (e.g. css display property) or you are sending requests to the server.
You have two choices:
load all data in one page (your idea)
do an ajax request for every prev / next button press
My opinion:
All data just loaded once but this can be a huge amount of html to deliver to the client. Most of the time you edit one player and leave the page. This would be waste.
You have a clean page with only the needed data and can replace it by ajax call, fragment render and html replace. Downside: You get a server round trip for every page.
Maybe I got you wrong, but I read that you want to show just one player at a time. I know it is not an technical answer for your problem but a) it is too long for a comment b) it is something to think about before forcing the one list request.
My problem is the following :
I've 2 differents objects that I've to fill from a single form.
With 1 object, I simply do in the newFoo.html:
<form th:object="${foo}" th:action="#{/foo}" method="post">
<input type="text" th:field="*{name}"/>
<button type="submit">Go</button>
</form>
and in the FooController:
#RequestMapping(value = "/foo/new", method = RequestMethod.GET)
public String newFoo(final Foo foo, Model model) {
return "newFoo";
}
#RequestMapping(value = "/foo/new", method = RequestMethod.POST)
public String saveFoo(final Foo foo, final BindingResult bindingResult, Model model) {
fooService.save(foo);
return "redirect:/foo/new";
}
Let's say I've an other object bar with a "status" variable in it. How can I do to pass that object so I can submit the input within the same form?
Like:
<form th:object="${foo} && ${bar}" th:action="#{/foo}" method="post">
<input type="text" th:field="*{name}"/>
<input type="text" th:field="*{status}"/>
<button type="submit">Go</button>
</form>
So far I tried to do with to fieldset with a th:object in it, that doesn't work, I tried to put two th:object in the form, that doesn't work either.
The only way I found is to build an other object containing those two objects, and pass it. That works well, but I can't create that kind of object, it's nonsense (even if it works).
Of course, the objects aren't as simple as Foo and Bar here, otherwise I would have merge those two. But that's not something I can do.
Is it even possible to pass two objects like that to use in a form ?
Thanks already.
I don't think you need to use two th:objects. Just use th:value
<form th:action="#{/foo}" method="post">
<input type="text" th:value="${foo.name}" name="name"/>
<input type="text" th:value="${bar.status}" name="status"/>
<button type="submit">Go</button>
</form>
I would think Spring is smart enough, on the controller side, to use its mapping techniques to map your fields to their proper command object, foo or bar.
i used a div tag to surround the form input for my second object and added a th:object..... the controller processed it and added it to the database.
<form method=post th:object="${object1}" >
<div th:object="${object2}" >
code......
</div>
<input type="submit" />
</form>