How to save the return value of a get method from controller to jsp view? - spring

Im having the next question. Ive made a get method in my controller thar receive a product and it returns true if the product is in the wishlist and false if its not.
#RequestMapping(value = "/entryExist", method = RequestMethod.GET)
public boolean checkEntryExists(#RequestParam("productCode") final String productCode){
final UserModel user = userService.getCurrentUser();
final Wishlist2Model wishlist = wishListFecade.getUserWishlist(user);
final ProductModel product = productService.getProductForCode( productCode );
return wishListFecade.checkEntryExists(wishlist, product);
}
Then i have a jsp for the view, and i have to get the boolean value from the controller to show differents buttons depends on the value. I want to know how to call the controller method and save the boolean value into a variable.
<c:if test=" BOOLEAN FROM THE CONTROLLER ">
<button onclick="openModal(modal_alert);">
<div class="col-xs-3 col-sm-4">
<img src="${fn:escapeXml(commonResourcePath)}/images/getWishlist.png">
</div>
</button>
</c:if>

Related

How to show appropriate message on page with Thymeleaf if list is empty

I have some controller that is connected with some lists, now that list can be empty, I'm trying to provide a user message on page if list is empty. To be more clear, user can have wallet, inside that wallet user can create a transactions, because all of that I have a page Transactions now, if user still didnt create any transaction but visit that page, I want to show him message like You didnt create any transaction.
I found this example, and tried to apply on my problem, but so far it didnt work: How to check null and empty condition using Thymeleaf in one single operation?
This is my controller:
#GetMapping("/userTransactions/{user_id}")
public String getUserTransactions(#PathVariable("user_id") long user_id, TransactionGroup transactionGroup, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
model.addAttribute("userId", userId);
List<Transaction> transactions = transactionRepository.getTransactionsByUserId(user_id);
List<TransactionGroup> transactionByDate = new ArrayList<>();
List<Transaction> transOnSingleDate = new ArrayList<>();
boolean currDates = transactions.stream().findFirst().isPresent();
if (currDates) {
LocalDate currDate = transactions.get(0).getDate();
TransactionGroup transGroup = new TransactionGroup();
for (Transaction t : transactions) {
if (!currDate.isEqual(t.getDate())) {
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
transGroup = new TransactionGroup();
transOnSingleDate = new ArrayList<>();
}
transOnSingleDate.add(t);
currDate = t.getDate();
}
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
} else {
System.out.println("Empty");
model.addAttribute("transactionGroup", "NoData");
}
model.addAttribute("transactionGroup", transactionByDate);
return "transactions";
}
And that seems to work fine, I mean, if I didn't create a transaction, message System.out.println("Empty"); will be printed, but message on page is not displayed neither.
This is thymeleaf:
<div class="date" th:each="singleGroup : ${transactionGroup}">
<h1 th:text="${singleGroup .date}"></h1>
<div class="transaction" th:each="singleTrans : ${singleGroup.transactions}">
<h2>Amount: <span th:text="${singleTrans.amount}"></span></h2><br>
<h2>Note: <span th:text="${singleTrans.note}"></span></h2><br>
<h2>Wallet name: <span th:text="${singleTrans .walletName}"></span></h2><br>
<h2>Expense Category: <span th:text="${singleTrans .expenseCategories}"></span></h2><br>
<h2>IncomeCategory: <span th:text="${singleTrans .incomeCategories}"></span></h2>
<a class="card__link" th:href="#{/api/transaction/delete/{id}(id=${singleTrans.id})}"
th:data-confirm-delete="|Are you sure you want to delete ${singleTrans.walletName} wallet?|"
onclick="if (!confirm(this.getAttribute('data-confirm-delete'))) return false">Delete</a>
<hr>
</div>
<th:block th:if="${transactionGroup=='NoData'}"> No Data Found </th:block>
</div>
And here is the part: <th:block th:if="${transactionGroup=='NoData'}"> No Data Found </th:block> But its not displayed if list is empty
What I'm missing?
You can check if your list is empty like this
<div th:if="${#lists.isEmpty(transactionGroup)}"> No Data Found </div>
<div th:if="${#lists.size(transactionGroup) ==0}"> No Data Found </div>
In addition to that, your th:block is placed inside the list outer loop and it should be outside. That's why you're not seeing anything when the list is empty.

Form with a select (drop-down) doesn't show error code

I have a form that contains a select to list all teachers by id in the system but it is not working properly.
Here is the code part of the form
and the corresponding path controller requests
I'm Using Thymeleaf and Spring Boot, so 'pr' corresponds a name for a variable of a repository of teachers.
<form th:action="#{/professor/updateProfessor/}" method="post" th:object="${professor}">
<div class= "form-group">
<label th:for = "id">Id</label>
<select th:field="*{id}">
<option
th:value = "${id}"
th:text = "${professor.id}">
</option>
</select>
</div>
<input type = "submit" value = "Add Professor">Save</button>
</form>
#GetMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView professorSelecao(){
ModelAndView atualizaProfessor = new ModelAndView("/atualizaProfessor");
atualizaProfessor.addObject("Add Professor");
return atualizaProfessor;
}
#PostMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView selecaoProfessor(){
ModelAndView pagSucesso = new ModelAndView("/pagSucesso");
pagSucesso.addObject(pr.findAll());
return pagSucesso;
}
From your controller, send a list of professors as per following to your view. Here you are associating the list of professors to the "professorList" :
model.addAttribute("professorList", pr.findAll());
And then to access above "professorList" in your thymeleaf do (similar to) this :
<option th:each="professor: ${professorList}" th:value="${professor}"> </option>
Not a full code but i hope you got the idea to get started.
For a full example, take a look here and here.
First of all what is not working? because I see a lot of things that may not work maybe because I don't see the all code or I am guessing some things, let's see
When you enter to your controller using
localhost:8080/professor/selecionaProfessor
are you expecting to use the form you put right? (the next code)
<form th:action="#{/professor/updateProfessor/}" method="post" th:object="${professor}">
<div class= "form-group">
<label th:for = "id">Id</label>
<select th:field="*{id}">
<option
th:value = "${id}"
th:text = "${professor.id}">
</option>
</select>
</div>
<input type = "submit" value = "Add Professor">Save</button>
</form>
because if that's correct you have a problem in your method:
#GetMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView professorSelecao(){
ModelAndView atualizaProfessor = new ModelAndView("/atualizaProfessor");
atualizaProfessor.addObject("Add Professor");
return atualizaProfessor;
}
you will get an error saying:
Neither BindingResult nor plain target object for bean name 'professor' available as request attribute
So you're missing to add the Key professor and a List so change:
atualizaProfessor.addObject("Add Professor");
with something like:
atualizaProfessor.addObject("professor", someListOfProfessorHereFromTheService (List<Professor>));
and it should work if your profesor object have the attributes you have on your form.
Now let's suppose that that worked before and the error wasn't that.
When you enter to your form if you see here:
form th:action="#{/professor/updateProfessor/}"
you're using updateProfessor I don't see that on your controller you have
#PostMapping(value = {"/selecionaProfessor"})
So I think that you should change the url mapping inside the html page or the controller and use the same as error 1, map the object using a key and value and iterate the list into the html as I showed in the 1st error
Hope it helps

ASP.NET Core Boolean property binding on form POST does not work as expected

I have got a bare-bones ASP.NET Core MVC application generated off of the MVC project template. The HomeController has been modified to expose two Index method overloads – one for GET and one for POST request, both of which render the same Index.cshtml view with a simple BooleanInputsViewModel:
public class BooleanInputsViewModel
{
public bool IsImportant { get; set; }
public bool IsActive { get; set; }
public List<string> Messages { get; } = new List<string>();
}
The Index.cshtml view looks like this:
#model AspNetCoreBooleanInputs.Models.BooleanInputsViewModel
<h2>
#nameof(this.Model.IsImportant) = #this.Model.IsImportant.ToString()
<br />
#nameof(this.Model.IsActive) = #this.Model.IsActive.ToString()
</h2>
<form class="form-horizontal" method="post">
<div class="col-md-12">
<input asp-for="IsImportant" />
<label asp-for="IsImportant">
</label>
<input asp-for="IsActive" />
<label asp-for="IsActive">
</label>
</div>
<div class="col-md-12">
<ul>
#foreach(string message in this.Model.Messages)
{
<li>#message</li>
}
</ul>
</div>
<div class="col-md-12">
<button type="submit">Submit</button>
</div>
</form>
Finally, the HomeController Index methods are implemented like this:
public IActionResult Index()
{
var model = new BooleanInputsViewModel();
model.Messages.Add($"GET values: {nameof(model.IsImportant)} = {model.IsImportant}, {nameof(model.IsActive)} = {model.IsActive}");
return View(model);
}
[HttpPost]
public IActionResult Index(BooleanInputsViewModel model)
{
model.Messages.Add($"POST values: {nameof(model.IsImportant)} = {model.IsImportant}, {nameof(model.IsActive)} = {model.IsActive}");
model.IsActive = !model.IsActive;
model.IsImportant = !model.IsImportant;
model.Messages.Add($"Negated POST values: {nameof(model.IsImportant)} = {model.IsImportant}, {nameof(model.IsActive)} = {model.IsActive}");
return this.View(model);
}
The POST handler negates the two model properties and passes the modified model back to the view. However, the negated values are not reflected in the rendered form as it always renders the originally POST-ed values. To me this looks like a bug. Do I miss something obvious?
The complete ASP.NET Core project is posted here - https://github.com/PaloMraz/AspNetCoreBooleanInputs.
Edit based on #Chris Platt's answer below:
Hi Chris, thank you for the prompt answer. I have verified that using the ModelState dictionary as you suggested works as expected, e.g.:
[HttpPost]
public IActionResult Index(BooleanInputsViewModel model)
{
model.Messages.Add($"POST values: {nameof(model.IsImportant)} = {model.IsImportant}, {nameof(model.IsActive)} = {model.IsActive}");
// This does NOT work:
//model.IsActive = !model.IsActive;
//model.IsImportant = !model.IsImportant;
// This works:
this.ModelState[nameof(model.IsActive)].RawValue = !model.IsActive;
this.ModelState[nameof(model.IsImportant)].RawValue = !model.IsImportant;
model.Messages.Add($"Negated POST values: {nameof(model.IsImportant)} = {model.IsImportant}, {nameof(model.IsActive)} = {model.IsActive}");
return this.View(model);
}
However, I still find it a very cumbersome behavior, because the model has already been bound once the Index method gets called. Why is the binding occurring again in the call to the View method, effectively ignoring the passed in model instance? This does not look right to me, sorry.
Besides, can you tell me please where did you get the information about the ModelState dictionary composition? The official documentation at https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding does not mention ViewData/ViewBag as sources; only form values, route values and query string...
The values your form fields are set to come from ModelState, which is composed of values from Request, ViewData/ViewBag, and finally Model. Importantly, Model is a last resort, so once you've done a post, the posted value (in Request) will be what the field is set to, regardless of any changes you make to Model.
I haven't tried doing this in ASP.NET Core, but you should be able to set ModelState["IsActive"].RawValue and ModelState["IsImportant"].RawValue instead. Assuming you can change the value in ModelState, then it will display as you want on your view.

Spring MVC: How do I preserve model attributes in spring validation errors

I searched around on Stack Overflow, but could not find the solution to my query. I have a controller function that adds multiple model attributes on a GET request
#RequestMapping(method = RequestMethod.GET, value = "/showdeletesearchqueryform")
public String showDeleteSearchQuery(final Model model) {
if (LOG.isDebugEnabled()) {
LOG.debug("Fetching all the search query results.");
}
ImmutableList<ArtQueryResults> results = this.searchQueriesService
.getSearchQueries(APPNAME);
// Adding model attribute # 1
model.addAttribute("searchResults", results);
if (LOG.isDebugEnabled()) {
LOG.debug("\"searchResults\" model attribute has been intialized from "
+ results);
}
ArtDeleteQueryRequest request = new ArtDeleteQueryRequest();
request.setAppName(APPNAME);
if (LOG.isDebugEnabled()) {
LOG.debug("Model attribute initialized = " + request);
}
// Adding model attribute # 2
model.addAttribute("deletedAttributes", request);
return "deletesearchqueries";
}
My JSP
<div class="column-group">
<form:form method="POST" action="${pageContext.request.contextPath}/arttestresults/showdeletesearchqueryform" modelAttribute="deletedAttributes">
<form:errors path="*" cssClass="alert alert-danger column lg-units-5 units-2" element="div"/>
<form:hidden path="appName" id="appNameId" htmlEscape="true"/>
<div class = "units-1 column lg-units-12">
<!-- Hidden Key for app name. -->
<form:select path="idsToBeDeleted" id="IdsToBeDeletedSelectId">
<c:forEach items="${searchResults}" var="searchResult" varStatus="loop">
<form:option label="${searchResult.searchQuery}" value="${searchResult.id}" />
</c:forEach>
</form:select>
</div>
<div class="units-1 column lg-units-12">
<%-- This is a hack that make sure that form is submitted on a click. Not sure why form is not being submitted. --%>
<button class="button" type="submit" onclick="javascript:$('form').submit();">Delete Selected Queries</button>
</div>
</form:form>
My controller POST function
#RequestMapping(method = RequestMethod.POST, value = "/showdeletesearchqueryform")
public String deleteSearchQueries(
Model model,
#ModelAttribute(value = "deletedAttributes") #Valid final ArtDeleteQueryRequest request,
final BindingResult result) {
if (result.hasErrors()) {
LOG.warn("There are " + result.getErrorCount() + " validation errors.");
return "deletesearchqueries";
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("The ids to be deleted are " + request.getIdsToBeDeleted());
}
this.searchQueriesService.deleteSearchQueriesById(
ImmutableList.copyOf(request.getIdsToBeDeleted()));
return "redirect:/arttestresults/showdeletesearchqueryform";
}
}
If there is a validation failure, the model attribute searchResults is not being picked up when I return a view on error condition? Is there a way to preserve the other defined model attributes as well?
Seems that you need flash attributes which were added in spring 3.1. Please take a look at example/explanation:
http://viralpatel.net/blogs/spring-mvc-flash-attribute-example/
The get and the post are different requests. What you get in the post request, is only what comes from the form, so only the "deletedAttributes" model attribute and only the fields that are <input> in the JSP.
You need to put again the searchResults model attribute explicitely like you did in get method.
As suggested by M. Deinum, if one or more attribute(s) will be used by all methods in a controller, you can use a #ModelAttribute annotated method to put it (them) in model automatically.
You can also use SessionAttributes model attributes, that is attributes that are stored in session and not in request. But it is hard to have them properly cleaned from session if user do not post the form but go into another part of the application. You have an example of usage ofSessionAttributes` in Spring's Petclinic example.

Thymeleaf not displaying Spring form error messages

I'm migrating a Spring jsp application to Thymeleaf but having problems displaying form errors.
I'm using the SpringTemplateEngine and ThymeleafViewResolver and rendering of templates works.
Also form values are populated in form input fields.
The only thing so far not working is displaying form error messages.
My controller looks like:
#RequestMapping(method = RequestMethod.POST)
String save(#Valid CustomerForm form, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute("form", form)
return "app/customers/create"
}
....
I printed the bindingResult to verify it contains an error:
binding result = org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'customerForm' on field 'name': rejected value []; codes [customerForm.name.NotBlank,name.NotBlank,java.lang.String.NotBlank,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [customerForm.name,name]; arguments []; default message [name]]; default message [may not be empty]
When I try to display error using:
<ul>
<li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
<span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
<span th:text="${e.message}">The error message</span>
</li>
</ul>
it does not display any error.
I tried various alternatives as documented on http://www.thymeleaf.org/doc/html/Thymeleaf-Spring3.html#validation-and-error-messages but without success.
Am I missing something?
EDIT
Note I'm trying to display the error within a form set via th:object:
<form id="customer-form" action="#" th:action="#{/app/customers}" th:object="${form}" method="post" class="form-horizontal">
<ul>
<li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
<span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
<span th:text="${e.message}">The error message</span>
</li>
</ul>
</form>
I think you may be having the same issue as I did - please see :
Fields object functions (Spring)
There it is answered by Daniel Fernandez. Basically your form object th:object="${form}" is named "form"
but your controller is looking for "customerForm" (class name) not "form" (the variable name)
can be renamed with #ModelAttribute("data")
copied from that link use:
public String post(#Valid FormData formData, BindingResult result, Model model){
// th:object="${formData}"
}
or
public String post(#Valid #ModelAttribute("data") FormData data, BindingResult result, Model model){
// th:object="${data}"
}
This is how I do it in my forms:
For displaying all errors I put this at the beginning of my form:
<div class="alert alert-danger" th:if="${#fields.hasErrors('*')}">
<p th:each="err : ${#fields.errors('*')}" th:text="${err}"></p>
</div>
and for individual error I add this after the field (of course, changing field in hasErrors to correspond to the field tested):
<p th:if="${#fields.hasErrors('vehicle.licensePlate')}" class="label label-danger" th:errors="*{vehicle.licensePlate}">Incorrect LP</p>
Let me know if this works for you?
Adding to #Blejzer answer:
naming of error message inside messages.properties file must follow below naming convention, so message string will be returned instead of message key:
(Constraint Name).(Object Name).(Property Name)
note: Object Name not Class Name
For example, if you have below User class:
class User{
#NotBlank
String username;
#Length(min=6)
String password;
}
suppose in controller, we named object under validation "user", see #ModelAttribute("user"):
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public String signup(#Valid #ModelAttribute("user") User user, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "signup";
}
//do some processing
return "redirect:/index";
}
in above controller snippet, the object name is "user" not "User"
Now, to show custom messages, you have to name the message as below:
NotBlank.user.username=User Name is blank
Length.user.password=Password length must be at least 6 letters

Resources