How to re-render previous page when validating form - java

I have a controller that renders a chores.html page with various sorts of chores.
#GetMapping("/chores")
public String getDueChores(Model model)
{
var tomorrow = LocalDate.now().plusDays(1);
var chores = choreRepository.findByDueBefore(tomorrow);
model.addAttribute("choresDue", chores);
model.addAttribute("allChores", choreRepository.findAll());
model.addAttribute("chore", new Chore());
model.addAttribute("chores", new ArrayList<Chore>());
return "chores";
}
The same page also has a form for adding a new chore. Here's the controller method:
#PostMapping("/chores")
public String addNewChore(#ModelAttribute #Valid Chore chore)
{
chore.setDue(LocalDate.now().plusDays(chore.getDaysBetween()));
choreRepository.save(chore);
return "redirect:/chores";
}
Now I want to display the errors if the new chore is invalid.
Attempt 1:
#PostMapping("/chores")
public String addNewChore(#ModelAttribute #Valid Chore chore,
Errors errors)
{
if (errors.hasErrors())
{
return "chores";
}
chore.setDue(LocalDate.now().plusDays(chore.getDaysBetween()));
choreRepository.save(chore);
return "redirect:/chores";
}
This shows the error message, but sense it's not going through the logic in the GET controller method, all the other chores on the page don't get populated.
Attempt 2:
#PostMapping("/chores")
public String addNewChore(#ModelAttribute #Valid Chore chore,
Errors errors)
{
if (errors.hasErrors())
{
return "redirect:/chores";
}
chore.setDue(LocalDate.now().plusDays(chore.getDaysBetween()));
choreRepository.save(chore);
return "redirect:/chores";
}
This doesn't work because the error information is lost on the redirect, and the errors aren't displayed.
Could anyone point me in the right direction, please?
Here's chores.html, if it's relevant:
<body>
<h1>Due today...</h1>
<form method="post" th:action="#{/chore}" th:object="${chore}">
<ul>
<li th:each="chore: ${choresDue}">
<input type="checkbox" name="choreIds" th:value="${chore.id}"/>
<label th:text="${chore.name}"></label>
</li>
</ul>
<input type="submit" value="Mark Chores Complete">
</form>
<form method="post" action="#" th:action="#{/chores}" th:object="${chore}">
<input type="text" th:field="*{name}" placeholder="Chore name">
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Chore name is invalid</span>
<br>
<input type="text" th:field="*{daysBetween}" placeholder="Do chore every # days">
<span class="validationError"
th:if="${#fields.hasErrors('daysBetween')}"
th:errors="*{daysBetween}">Chore name is invalid</span>
<br>
<input type="submit" value="Add chore">
</form>
<hr>
<h1>All Chores</h1>
<form th:method="delete" th:action="#{/deleteChore}" th:object="${chore}">
<ul>
<li th:each="chore: ${allChores}">
<input type="checkbox" name="choreIds" th:value="${chore.id}"/>
<label th:text="${chore.name} + ' every ' + ${chore.daysBetween} + ' days'"></label>
</li>
</ul>
<input type="submit" value="Delete selected chores">
</form>
</body>

Solution is to add Errors to the Model.
#PostMapping("/chores")
public String addNewChore(#ModelAttribute #Valid Chore chore,
Errors errors,
Model model)
{
if (errors.hasErrors())
{
model.addAttribute("error", errors);
var tomorrow = LocalDate.now().plusDays(1);
var chores = choreRepository.findByDueBefore(tomorrow);
model.addAttribute("choresDue", chores);
model.addAttribute("allChores", choreRepository.findAll());
return "chores";
}
chore.setDue(LocalDate.now().plusDays(chore.getDaysBetween()));
choreRepository.save(chore);
return "redirect:/chores";
}

Related

How do I POST a file to my backend, using Thymeleaf with Spring's #ModelAttribute?

Using #ModelAttribute I am able to post form-data to my backend without issue, however when I try to include a multipart file the server returns a 500 error. There are no console warnings or stack traces thrown, and the POST request endpoint is never hit according to my debugger.
I've tried working backwards, and replacing the MultiPartFile with just a String and changing the file attribute in the HTML to text, and it works just find sending Strings and ints to the back end, but when I add the file type back in and switch the model to MultipartFile, it breaks...
The html form fragment, the submit button triggers the POST request to /new:
<form method="POST" enctype="multipart/form-data" th:action="#{/release/new}" class="mx-auto w-50" th:object="${package}">
<th:block th:include="fragment/error-message.html"></th:block>
<fieldset>
<legend>Release Package Information</legend>
<div class="row">
<div class="col-sm-6 mb-3">
<label>Release Package ID</label>
<input type="text" class="form-control" placeholder="Enter the release package ID" th:field="*{releasePackageId}">
</div>
</div>
<div class="row">
<div class="col-sm-12 mb-3">
<label>Nomenclature</label>
<input type="text" class="form-control" placeholder="Enter the part nomenclature" th:field="*{nomenclature}">
</div>
</div>
<div class="row">
<div class="col-sm-12 mb-3">
<label>File</label>
<input type="file" class="form-control" placeholder="Select the part to upload" th:field="*{file}">
</div>
</div>
</fieldset>
<div class="row">
<div class="col-sm-6">
<a th:href="#{/release}" class="btn btn-outline-secondary w-100">
Back
</a>
</div>
<div class="col-sm-6">
<button type="submit" class="btn btn-outline-success w-100">
Submit
</button>
</div>
</div>
</form>
And here are the controller endpoints for /release/new:
#GetMapping("/new")
public ModelAndView showCreateReleasePackageForm()
{
return new ModelAndView("new-release").addObject("package", new CreateReleasePackageDTO());
}
#PostMapping("/new")
public String createReleasePackage(#ModelAttribute("package") CreateReleasePackageDTO releasePackageDTO, BindingResult result, RedirectAttributes attributes)
{
if (result.hasErrors())
{
return "new-release";
}
String response = "a response";
System.out.println(releasePackageDTO.getReleasePackageId() + releasePackageDTO.getNomenclature() + releasePackageDTO.getFile());
// TODO: Exception handling
// try
// {
// response = this.fileService.save(ReleasePackageMapper.toEntity(releasePackageDTO));
// } catch (JsonProcessingException e)
// {
// response = e.getMessage();
// e.printStackTrace();
// }
attributes.addFlashAttribute("message", response);
return "redirect:/release";
}
Here is the DTO, the model object I am calling "package":
#Getter
#Setter
public class CreateReleasePackageDTO
{
#NotNull(message = "{NotNull.releasePackageId}")
private int releasePackageId;
#NotEmpty(message = "{NotEmpty.nomenclature}")
private String nomenclature;
#NotEmpty(message = "{NotEmpty.multipartFile}")
private MultipartFile file;
}

How to display RequestParam errors in Thymeleaf

I want to use a form without an object. How to display errors?
Controller:
#PostMapping(value="/submit")
public String doSubmit(#RequestParam("myParameter") String param, BindingResult bindingResult) {
bindingResult.rejectValue("myParameter", "Oh nooo!!!!");
return "html";
}
HTML:
<form th:action = "#{/submit}" method="post">
<label for="myParameter" class="form-label">My Parameter</label>
<input name="myParameter" class="form-control" type="text"/>
<span class="text-danger" th:if="${#fields.hasErrors('myParameter')}" th:errors="myParameter">Error</span>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
th:if="${#fields.hasErrors('myParameter')}" th:errors="myParameter" throws errors, cause "myParameter" is not known.
How to display the error, without using th:object in <form>
I did it with the help of this question Thymeleaf: display global errors by error code
#PostMapping(value="/submit")
public String doSubmit(#RequestParam("myParameter") String param, BindingResult bindingResult) {
bindingResult.reject("myParameter", "myParameter");
return "html";
}
Important: myParameter must not be a key of a message source!
<form th:action="#{/submit}" method="post">
<label for="myParameter" class="form-label">My Parameter</label>
<input name="myParameter" class="form-control" type="text"/>
<span class="text-danger" th:if="${#lists.contains(#fields.globalErrors(), "myParameter")}" th:text="An Error happened">Error</span>
<button type="submit" class="btn btn-primary">Submit</button>
Important: Use " to indicate the string. th:text? may contain a message key like th:text="#{error.code1}"`.

How to display different title depends on action?

I have got two methods that redirect to the same .html file.
First method is method responsible for saving, the second method is for updating.
It has got the same view so I just wanted to move user to the same view.
I have <h1>..</h1> in .html I would like to have title "New Mapping" for adding new mapping and "Update mapping" when we want to update mapping.
These are methods that do all the stuff (redirecting to the .html file)
#RequestMapping(path = "/.../update", method = RequestMethod.GET)
public String updatePage(#RequestParam("...") String ..., Model model) {
String[] tokens = ....split("_");
Template template= class.method(...);
model.addAttribute("template", template);
return "save";
}
#RequestMapping(path = "/.../save")
public String newMappingPage(Model model) {
Template template = new Template();
template.setCostIndex("10");
model.addAttribute("template", template);
return "save";
}
#RequestMapping(path = "/.../save", method = RequestMethod.POST)
public String saveMapping(#ModelAttribute Template template) {
class.method(template);
return "redirect:/main-page";
}
save.hml file
<html>
<head>
<title>Engine</title>
<link rel="stylesheet" th:href="#{/css/file.css}"/>
</head>
<body>
<h1>New/Update mapping</h1>
<form action="save" method="post" th:object="${template}">
<fieldset>
<label for="...">....[min]</label>
<input type="number" id="..." th:field="*{...}" step="0.1" min="0"/>
<label for="...">...</label>
<input type="number" id="...." th:field="*{...}" required="true"/>
<label for="...">....</label>
<input type="text" id="..." th:field="*{...}" maxlength="1024" size="50"/>
<br/>
<br/>
<label for="response">Response</label>
<br/>
<textarea id="response" rows="20" cols="150" th:field="*{...}" required="true"/>
<br/>
<input id="submit" type="submit" class="primary" value="Submit"/>
</fieldset>
</form>
</body>
</html>
You do not need to create two separate methods for add and update as you are storing data into DB from JSP page. use only one /save API.
#PostMapping(path = "/.../{}/save")
public String newMappingPage(Model model) {
Template template = new Template();
template.setCostIndex("10");
model = new Model();
if (model.getId() > 0) {
model = findById(model.getId());
model.addAttribute("heading", "Update Mapping");
} else {
model.addAttribute("heading", "New Mapping");
}
//Call method to store data from jsp file
return "save";
}
In JSP :
<html>
<head>
<title>Engine</title>
<link rel="stylesheet" th:href="#{/css/file.css}"/>
</head>
<body>
<h1>${heading}</h1>
<form action="save" method="post" th:object="${template}">
<fieldset>
<label for="...">....[min]</label>
<input type="number" id="..." th:field="*{...}" step="0.1" min="0"/>
<label for="...">...</label>
<input type="number" id="...." th:field="*{...}" required="true"/>
<label for="...">....</label>
<input type="text" id="..." th:field="*{...}" maxlength="1024" size="50"/>
<br/>
<br/>
<label for="response">Response</label>
<br/>
<textarea id="response" rows="20" cols="150" th:field="*{...}" required="true"/>
<br/>
<input id="submit" type="submit" class="primary" value="Submit"/>
</fieldset>
</form>
</body>
</html>
Update your method like this:
#PostMapping(path = "/.../{}/save")
public ModelAndView save(Model model) {
ModelAndView obj = new ModelAndView("save");
model = new Model();
if (model.getId() > 0) {
model = findById(model.getId());
obj .addAttribute("heading", "Update Mapping");
} else {
obj .addAttribute("heading", "New Mapping");
}
//Call method to store data from jsp file
return obj;
}

validate input in Thymeleaf

I have this input:
Masa: <input type="number" class="form-control form-text" name="masa"/>
<div class="text col-sm-12 error" th:if="${wzrost}" >
<p class="text text-center">
To pole jest wymagane
</p>
</div>
Wzrost: <input type="number" class="form-control form-text " name="wzrost"/>
<div class="text col-sm-12 error" th:if="${wzrost}" >
<p class="text text-center">
To pole jest wymagane
</p>
</div>
And this controller;
String x = String.valueOf(masa);
String y = String.valueOf(wzrost);
if(x==null ){
model.addAttribute("wzrost",true);
return"views/success";
}
if(y==null ){
model.addAttribute("wzrost",true);
return"views/success";
}
When I click form submit button I always get error nullpointerexception.
How do I validate input, so that when it is empty the message pops up
#PostMapping("/cal-bmi")
public String calculateBmiForm(Model model, Integer masa, Integer wzrost) {
String x = String.valueOf(masa);
String y = String.valueOf(wzrost);
if(x==null ){
model.addAttribute("wzrost",true);
return"views/success";
}
if(y==null ){
model.addAttribute("wzrost",true);
return"views/success";
}
}
ANd when i get a valu form masa and wzrost i check from null, i click submit alwas nullpointerexception
<form th:action="#{/cal-bmi}" method="post">
<ul class="gender-options">
<input id="man" type="radio" name="gender" value="male" required />
<label for="man">mężczyzna</label> ⁄
<input id="woman" type="radio" name="gender" value="female"/>
<label for="woman">kobieta</label>
</ul>
Masa: <input type="number" class="form-control form-text" required placeholder="(kg)" name="masa"/>
<!--<div class="text col-sm-12 error" th:if="${wzrost}">-->
<!--<p class="text text-center">-->
<!--To pole jest wymagane-->
<!--</p>-->
<!--</div>-->
Wzrost: <input type="number" class="form-control form-text " required placeholder="(cm)" name="wzrost"/>
<!--<div class="text col-sm-12 error" th:if="${wzrost}">-->
<!--<p class="text text-center">-->
<!--To pole jest wymagane-->
<!--</p>-->
<!--</div>-->
<input type="submit" class="col-lg-10 btn btn-primary" value="Oblicz"/>
</form>
Now i used required but is not good solution
It seems like you want to implement server side validation. For this the best approach is to use validators and its bindingResult. Steps to implement server side validation is
Have for model
public class PersonForm {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
Use form model in html
<form action="#" th:action="#{/personForm}" th:object="${personForm}" method="post">
<table>
<tr>
<td><label th:text="#{label.name}+' :'"></label></td>
<td><input type="text" th:field="*{name}" /></td>
<td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Generic Error</td>
</tr>
<tr>
<td><button type="submit">Submit</button></td>
</tr>
</table>
</form>
Have validator class
#Component
public class PersonFormValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return PersonForm.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "name", "field.name.empty");
PersonForm p = (PersonForm) target;
if (p.getName().equalsIgnoreCase("XXX")) {
errors.rejectValue("name", "Name cannot be XXX");
}
}}
Bind validator to controller and let spring do the magic.
#Controller
public class WebController {
#Autowired
PersonFormValidator personFormValidator;
#InitBinder("personForm")
protected void initPersonFormBinder(WebDataBinder binder) {
binder.addValidators(personFormValidator);
}
#PostMapping("/personForm")
public String checkPersonInfo(#Validated PersonForm personForm, BindingResult bindingResult, final RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
return "personForm";
}
redirectAttributes.addFlashAttribute("personResult", apiClientService.getPersonResult(personForm));
return "redirect:/spouseForm";
}
}

spring return object on button click out of list

Hi guys hope you can help me, because i cant get further at the moment
I have my Controller.
#RequestMapping(value="/kundenseite", method= RequestMethod.GET)
public String kundenLogin(ModelMap model) {
if(kundeComponent.getKunde() != null) {
List<Restaurant> restaurants = restaurantService.alleRestaurants();
model.addAttribute("restaurants", restaurants);
return "kundenseite";
}else {
return "redirect:/kunde/login";
}
}
#RequestMapping(value="/kundenseite", method= RequestMethod.POST)
public String kundenLoginAnswer(ModelMap model, #ModelAttribute Restaurant restaurant) {
System.out.println(restaurant.toString());
return "kundenseite";
And my jsp file
<%# include file="common/header.jspf" %>
<div class="jumbotron text-center">
<h1>MiMiMi Lieferservice</h1>
<p>Der schnellste Lieferservice von Passpick</p>
</div>
<div style="margin-right:auto; margin-left:auto; width: 33%">
<h2 style="text-align: center">Restaurant wählen</h2>
<div class="well">
<c:forEach items="${restaurants}" var="restaurant">
<form:form modelAttribute="${restaurant}" method="post">
<div style="margin-top: 8px" class=col-sm-4 >${restaurant.name}</div>
<div style="margin-top: 8px" class=col-sm-4 >${restaurant.restaurantTyp}</div>
<button type="submit">Bestellen</button>
</form:form>
<br style="clear:both;" />
</c:forEach>
</div>
</div>
</body>
</html>
If the user presses a button i want to return a restaurant.
But i don't know how to make that happen, my thought was to use a form but i cant get it to send a complete restaurant object back
If there is no solution for this i have to write the id with the button.
You need input hidden inside the form tab as below input hidden:
<input type="hidden" name="name" value="${restaurant.name}">
<input type="hidden" name="restaurantTyp" value="${restaurant.restaurantTyp}">

Categories