Thymeleaf - how to use Hashmap - spring-mvc

I'd like to use HashMap as List has been used in following example: http://www.thymeleaf.org/doc/thymeleafspring.html#dynamic-fields
I've tried however without effect.
Edit: I'm providing some code. So the problem is that I'd like to use hashmap products in the form, the example I've provided above works great with a list, however I'd like to use it with hashmap.
Entity:
#Entity
#Table(name = "meals")
public class Meal extends BaseEntity{
#NotEmpty
private String name;
#NotEmpty
private String recipe;
private String image;
private double cost;
#NotNull
private int kcal;
#NotNull
private int proteins;
#NotNull
private int fats;
#NotNull
private int carbs;
#NotNull
private int portions;
#ElementCollection
#Column(name = "quantity")
private Map<Product, Integer> products = new HashMap<>();
//getters & setters
Form:
<!DOCTYPE html>
<html lang="en">
<head th:replace="fragments/headTag :: headTag"/>
<body>
<div class="container">
<div th:replace="fragments/bodyHeader :: bodyHeader"></div>
<h2 th:text="#{meals.new.title}"/>
<form th:object="${meal}" th:method="post" th:action="#{${#httpServletRequest.servletPath}}"
enctype="multipart/form-data"
class="form-horizontal" id="newMealForm">
//other fields
<div class="control-group" th:classappend="${#fields.hasErrors('products')} ? error">
<label class="control-label" th:text="#{meals.products}"/>
<div class="controls"><span class="help-inline"
th:errors="*{products}">[error]</span>
<table class="table table-striped">
<thead>
<tr>
<th th:text="#{product.name}"/>
<th th:text="#{product.quantity}"/>
<th><button type="submit" name="addProduct" th:text="#{meals.addProduct}"/></th>
</tr>
</thead>
<tbody>
<tr th:each="products,rowStat : *{products}">
<td><input type="text" th:field="*{products[__${rowStat.index}__].value}" /></td>
<td>
<input type="number" th:field="*{products}" />
</td>
<td>
<button type="submit" name="removeProduct"
th:value="${rowStat.index}" th:text="#{meals.removeProduct}"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" th:text="#{submit}"/>
</div>
</form>
</div>
<div th:replace="fragments/footer :: footer"></div>
</body>
</html>
Controller methods to add/remove row:
#RequestMapping(value="/meals/new", params={"addProduct"})
public String addProduct(final Meal meal, final BindingResult bindingResult) {
meal.getProducts().put(new Product(), 1);
return "/meals/new";
}
#RequestMapping(value="/meals/new", params={"removeProduct"})
public String removeRow(
final Meal meal, final BindingResult bindingResult,
final HttpServletRequest req) {
final Integer rowId = Integer.valueOf(req.getParameter("removeProduct"));
meal.getProducts().remove(rowId.intValue());
return "/meals/new";
}
Error I've got is:
org.springframework.beans.InvalidPropertyException: Invalid property 'products[0]' of bean class [org.cybuch.incessantfeasting.model.Meal]: Invalid index in property path 'products[0]'; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.lang.String' to required type 'org.cybuch.incessantfeasting.model.Product' for property 'null'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type java.lang.String to type org.cybuch.incessantfeasting.model.Product for value '0'; nested exception is java.lang.IllegalArgumentException: Unable to parse '0'

I've solved this problem some time ago, so I guess it would be nice to share with the solution. I've changed map to list in controller with a wrapper, so it was List where ProductQuantity class contains 2 fields - Product product and String quantity. After that I'm converting that list to map and save to the database.

Related

Thymeleaf form submit with model having associations

The application is web-application using spring boot and thymeleaf. The application has a questionnaire along with some fields that needs to filled to submit the form. The model object (here answeredQuestionnaire) in template th:object="${answeredQuestionnaire}" has nested objects with as shown below.
When I submit the form, I need to assign the questionId and possibleAnswerId to answers (Set) in answeredQuestionnaire. How can I assign this in thymeleaf template. Please provide some suggestions.
AnsweredQuestionnaire .java
public class AnsweredQuestionnaire {
private UUID id;
private Boolean isRirelevant;
private Boolean isITrelevant;
private String descriptionProcess;
private int classCalculated;
private LocalDateTime createdDateAndTime;
private Questionnaire questionnaire;//separate model class
private Process process;//separate model class
private Set<Answer> answers = new HashSet<>();// Set of separate model class
}
Answer.java
public class Answer {
private UUID id;
private Question question;// separate class
private PossibleAnswer selectedPossibleAnswer;// separate class
private String answerComment;
private AnsweredQuestionnaire answeredQuestionnaire;
}
The controller handler to display the questionnaire page is as shown below.
#GetMapping("/review/process/{id}")
public String reviewProcess(#PathVariable UUID id, Model model){
Process byId = processService.findById(id);
if(byId != null){
if(byId.getAnsweredQuestionnaires().size() > 0){
Set<AnsweredQuestionnaire> answeredQuestionnaires = byId.getAnsweredQuestionnaires();
AnsweredQuestionnaire lastUpdatedAnsweredQuestionnaire = answeredQuestionnaires.stream().sorted(Comparator.comparing(AnsweredQuestionnaire::getCreatedDateAndTime).reversed())
.collect(Collectors.toList()).get(0);
System.out.println("AnsweredQuestionnaire for a given process :"+lastUpdatedAnsweredQuestionnaire);
model.addAttribute("answeredQuestionnaire", lastUpdatedAnsweredQuestionnaire);
model.addAttribute("isIT_SiG_relevant", lastUpdatedAnsweredQuestionnaire.getIsIT_SiG_relevant());
model.addAttribute("isRiDARrelevant", lastUpdatedAnsweredQuestionnaire.getIsRiDARrelevant());
model.addAttribute("possibleAnswersIds", getAllSelectedPossibleAnswersWithId(lastUpdatedAnsweredQuestionnaire.getAnswers()) );
}else{
System.out.println("Process without answeredQuestionnaire");
Questionnaire byIsActive = questionnaireService.findByIsActive(true);
AnsweredQuestionnaire emptyAnsweredQuestionnaire = new AnsweredQuestionnaire();
emptyAnsweredQuestionnaire.addQuestionnaireForAnsweredQuestionnaire(byIsActive);
System.out.println("Questionnaire found"+byIsActive);
model.addAttribute("answeredQuestionnaire", emptyAnsweredQuestionnaire);
model.addAttribute("isIT_relevant", false);
model.addAttribute("isRi_relevant", false);
// model.addAttribute("questionnaire", byIsActive);
model.addAttribute("possibleAnswersIds", getAllSelectedPossibleAnswersWithId(emptyAnsweredQuestionnaire.getAnswers()) );
}
}
review.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Review page</title>
</head>
<body>
<h1>This is the review page</h1>
<form action="#" th:action="#{/review_questionnaire}" th:object="${answeredQuestionnaire}" method="post">
<label>
isRirelevant :
</label>
<select th:field="*{isRirelevant}">
<option th:value="false" th:text="no" th:selected="${false==isRirelevant}"></option>
<option th:value="true" th:text="yes" th:selected="${true==isRirelevant}"></option>
</select>
<br>
<select th:field="*{isITrelevant}">
<option th:value="false" th:text="no" th:selected="${false==isITrelevant}"></option>
<option th:value="true" th:text="yes" th:selected="${true==isITrelevant}"></option>
</select>
<table>
<thead>
<tr>
<th>Question</th>
<th>Answer</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
<tr th:each="question : ${answeredQuestionnaire.questionnaire.getQuestions()}">
<td th:text="${question.text}" th:field="*{#lists.toList(answers)}">Question</td>
<td>
<select>
<option value="0" >Select Answer</option>
<option th:each="possibleAnswer : ${question.getPossibleAnswers()}" th:value="${possibleAnswer.id}" th:text="${possibleAnswer.text}" ></option>
</select>
</td>
</tr>
</tbody>
</table>
<br>
<input type="submit" />
<br>
<label >Class calculated : </label>
<input type="number" th:field="*{classCalculated}" th:value="${answeredQuestionnaire.classCalculated}" readonly >
</form>
</body>
</html>
Update
I separated the answers (Set) with th:if conditional, but still I cannot submit the form.
<div th:if="${answeredQuestionnaire.answers.size()>0}">
<table >
<thead>
<tr>
<th>Question</th>
<th>Answer</th>
<th>Comment</th>
</tr>
</thead>
<tbody th:field="*{answers}" >
<tr th:each="answer,iStat : ${answeredQuestionnaire.answers}" >
<td>
<input th:text="${answer.question.text}" th:value="${answer.question.id}" th:field="*{answers[__${iStat.index}__].question.id}"/>
</td>
<td>
<select th:field="*{answers[__${iStat.index}__].selectedPossibleAnswer.id}">
<option th:value="0" >Select Answer</option>
<option th:each="possibleAnswer : ${answer.question.getPossibleAnswers()}" th:value="${possibleAnswer.id}" th:text="${possibleAnswer.text}" th:selected="${possibleAnswer.id==answer.getSelectedPossibleAnswer().id}" ></option>
</select>
</td>
<td>
<input th:text="${answer.getAnswerComment()}" th:field="*{answers[__${iStat.index}__].answerComment}" >
</td>
</tr>
</tbody>
</table>
</div>
Some suggestions :
1/ You should use Command objects or DTO and not Entity from your model directly to bind datas from your web forms.
2/ You should use a List to bind datamodel relations, it works with Thymeleaf but Set will not work.
3/ You can manage relations with DTO which are containing subsets of your model objects, and simplify the business process a lot and the wireload to your services and database(s). It depends how your model are related too btw.
4/ Disable OpenSessionInView and make a service layer with Transactional annotated methods
5/ Never use an Entity inside a Controller. It's an architectural conception's nonsense.

Take object from list and pass to another method

As the title describes, using thymeleaf I display the contents of a list and then I put "Update" buttons next to each item on the list that send the particular object to an editing form page.
Here is the controller method for adding the list to the list view:
#RequestMapping("/list")
public String list(Model model){
List<Employee> employees = repository.findAll();
model.addAttribute("employees", employees);
return "list";
}
And here is the thymeleaf html code:
<tr th:each="emp : ${employees}">
<td th:text="${emp.id}"></td>
<td th:text="${emp.name}"></td>
<td th:text="${emp.surname}"></td>
<td th:text="${emp.age}"></td>
<td th:text="${emp.department}"></td>
<td>
<form th:action="#{/update}" method = "POST" th:object="${emp}">
<input type="hidden" th:field="*{id}"></input>
<input type="hidden" th:field="*{name}" ></input>
<input type="hidden" th:field="*{surname}"></input>
<input type="hidden" th:field="*{age}"></input>
<input type="hidden" th:field="*{department}"></input>
<button type = "submit">Update</button>
</form>
</td>
</tr>
And here is the receiving method:
#RequestMapping("/update")
public String update(#ModelAttribute("emp") Employee emp){
return "update";
}
I keep getting the following exception:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'emp' available as request attribute
Please let me know if you have any ideas on accomplishing this task.
This could be help.Change your controller like this.Its work for me.
model.addAttribute("emp", new Employee());
#RequestMapping("/list")
public String list(Model model){
List<Employee> employees = repository.findAll();
model.addAttribute("emp", new Employee());
model.addAttribute("employees", employees);
return "list";
}

Fill a model with the data of another model in Spring MVC

I have a .jsp page which is generated using my "menuModel", Now after visualising this page, the user inserts his/her name and clicks on the Confirm My Order button.
By clicking on this button I want to copy the data of this menuModel to another model named "ordersModel". Due to the fact that this ordersModel is not instantiated, I don't know how to copy the data of menuModel into ordersModel.
<form:form id="myOrderMenuId" action="${pageContext.request.contextPath}/OrderSent" method="post" commandName="ordersModel" >
<c:forEach items="#{menuModel.foodList}" var="foodModel" varStatus="status">
<c:if test="${foodModel.quantity != 0}">
<!-- <form:hidden value='${foodModel.foodName}' path='orders[${status.index}].foodName'/>
<form:hidden value='${foodModel.quantity}' path='orders[${status.index}].quantity'/> -->
<div>
<table class="itemList">
<tr>
<td style="width: 170px">
<span id="foodNameId${foodModel.foodId}"><c:out value="${foodModel.foodName}"/></span>
</td>
<td>
<span id="quantityId${foodModel.foodId}" style="margin-left: 110px;"><c:out value="${foodModel.quantity}"/></span>
</td>
<td>
<span id="priceId${foodModel.foodId}" style="margin-left: 110px;"><c:out value="${foodModel.price}"/></span>
<span> €</span>
</td>
<td>
<span class="eachPrice" id="totalPriceId${foodModel.foodId}" style="margin-left: 110px;"><c:out value="${foodModel.totalPrice}"/></span>
<span> €</span>
</td>
</tr>
</table>
</div>
</c:if>
</c:forEach>
<span class="itemList" style="color: #80FF00; margin-left: -300px">Total:</span>
<span id="totalPayment" class="itemList" style="color: #80FF00; margin-left: 5px"></span>
<span class="itemList" style="color: #80FF00;"> €</span><br/>
<div class="itemList" style="margin-left:-1130px">Your Name: <input path="ownerName" name="orderName" class="myInbox" id="orderName"/></div>
<button id="viewMyOrder" class="greyButton" style="position:relative; left: -530px">Confirm My Order</button>
</form:form>
And this is my OrdersModel:
public class OrdersModel {
private String ownerName;
private List<Order> orders;
//getters and setters
}
How is it possible to copy the data into the ordersModel, as I commented in the code.
UPDATE
My Controller looks like this:
#RequestMapping(value = "/myOrder", method = RequestMethod.POST)
public String viewMyOrder(Model model,
#ModelAttribute("menuModel")MenuModel menuModel) {
logger.info("You are in view my order page.");
List<FoodModel> foodModelList = new ArrayList<FoodModel>();
for (FoodModel foodModel : menuModel.getFoodList()) {
FoodModel foodModelNew = new FoodModel();
if (foodModel.getQuantity()!=0){
foodModelNew.setFoodId(foodModel.getFoodId());
foodModelNew.setFoodName(foodModel.getFoodName());
foodModelNew.setQuantity(foodModel.getQuantity());
foodModelNew.setPrice(foodModel.getPrice());
foodModelNew.setTotalPrice(foodModel.getQuantity() * foodModel.getPrice());
}
foodModelList.add(foodModelNew);
}
menuModel.setFoodList(foodModelList);
return "myOrder";
}
#RequestMapping(value = "/OrderSent", method = RequestMethod.POST)
public String orderSent(#ModelAttribute("ordersModel")OrdersModel ordersModel,
#RequestParam("orderName") String owner, Model model) {
logger.debug("Your order has been received");
model.addAttribute("owner", owner);
return "orderSent";
}
}
Why don't you just populate OrdersModel.orders with the FoodModels in the controller and then render the JSP?
<form:form id="myOrderMenuId" action="${pageContext.request.contextPath}/OrderSent" method="post" commandName="ordersModel" >
<c:forEach items="#{ordersModel.order}" var="foodModel" varStatus="status">
...
Controller:
#RequestMapping(value = "/myOrder", method = RequestMethod.POST)
public String viewMyOrder(Model model, #ModelAttribute("menuModel")MenuModel menuModel) {
logger.info("You are in view my order page.");
OrdersModel ordersModel = new OrdersModel()
List<FoodModel> foodModelList = new ArrayList<FoodModel>();
for (FoodModel foodModel : menuModel.getFoodList()) {
Order foodModelNew = new Order();
if (foodModel.getQuantity()!=0){
foodModelNew.setFoodId(foodModel.getFoodId());
foodModelNew.setFoodName(foodModel.getFoodName());
foodModelNew.setQuantity(foodModel.getQuantity());
foodModelNew.setPrice(foodModel.getPrice());
foodModelNew.setTotalPrice(foodModel.getQuantity() * foodModel.getPrice());
}
ordersModel.add(foodModelNew);
}
model.addAttribute("ordersModel", ordersModel);
return "myOrder";
}

Getting null values from checkboxes

I am working on a simple Spring MVC project. I am having trouble getting values from checkboxes. What I mean is when a user checks 2 boxes out of 3, all 3 are binded to a list with non-checked values as null. That's wrong. I just want values that are checked. Those that didn't get checked should not come to list at all.
This is a snippet of my code:
POJO:
public class Student{
private List<StudentCourses> sc;
//getters and setters
}
public class StudentCourses{
private int courseID;
private String courseName;
private Character grade;
private String semesterID;
//getters and setters
}
This is what I send from my controller:
#RequestMapping(value = "/selectclasses", method = RequestMethod.POST)
public String selectClasses(Model m) {
Student s = new Student();
List<StudentCourses> coursesList = new ArrayList<StudentCourses>();
coursesList.add(new StudentCourses("Eng 101", '-', "SP 16"));
coursesList.add(new StudentCourses("Math 140", '-', "SP 16"));
coursesList.add(new StudentCourses("CS 442", '-', "SP 16"));
m.addAttribute("coursesList", coursesList);
m.addAttribute("student", s);
return "selectclasses";
}
This is what I have in my selectclasses.jsp:
<form:form modelAttribute="student" method="post" action="/success">
<table>
<c:forEach items="${coursesList}" var="r" begin="0" varStatus="status">
<form:checkbox path="sc[${status.index }].courseName" value="${r.courseName}" label="${r.courseName}" />
</c:forEach>
</table>
<input type="submit" id="submit" name="submit" value="Submit" />
</form:form>
I don't know why null is passed to the "sc.courseName" when it's not checked. What am I doing wrong? Or is there a work around it?
Please help
Thanks.
simply writer
<input type="checkbox" value="${r.courseName}" id="id"name="name"/>
in controller class get the values using
String []values=request.getParameterValues(pass the id);
simple you get the selected values
I found the Solution!
I found two ways to solve it. This is the solution using Spring tags:
<form:checkboxes path="sc" items="${coursesList}" itemValue="courseName" itemLabel="courseName" />
In the above code, itemValue and itemLabel is the main thing! itemValue and itemLabel simply refer to bean properties of an object inside items attribute (items="${coursesList}"). In a nutshell, if you need to use a List of your Custom Beans as the items attribute you need to use also the itemValue and itemLabel attributes. This bold part of paragraph is taken from: https://stackoverflow.com/a/15529281/4828463 by #Carlos Gavidia
And now the solution using JSTL core tags:
<c:forEach items="${coursesList}" var="courses">
<tr>
<td><form:checkbox path="sc" value="${courses.courseName}" label="${courses.courseName}"/></td>
</tr>
</c:forEach>
Again the value and label attributes are important.

Thymeleaf-Spring BigDecimal formatting with #NumberFormat adds extra symbols

I'm using Thymeleaf 2.1.2.RELEASE with Spring MVC 4.0.4.RELEASE
I have a dynamic form that I use to add new order lines to an order.
The problem I am facing is that each time I add a line and the content is re-rendered then an extra symbol is added before the currency symbol on the price column of each of the previous lines.
So if I add three rows i get
£22.00
£22.00
£22.00
The price field is BigDecimal with #NumberFormat(style = NumberFormat.Style.CURRENCY) so Spring should handle the conversion.
<div>
<label th:text="#{order.lines}">Order Lines</label>
<table id="addTable" dt:table="true" dt:sort="false" dt:paginate="false" dt:info="false" dt:lengthchange="false">
<thead>
<tr>
<th th:text="#{order.lines.head.linenum}">line</th>
<th th:text="#{order.lines.head.product}">Product</th>
<th th:text="#{order.lines.head.description}">Description</th>
<th th:text="#{order.lines.head.quantity}">Quantity</th>
<th th:text="#{order.lines.head.price}">Price</th>
<th>
<button type="submit" name="addLine" th:text="#{order.line.add}">Add line</button>
</th>
</tr>
</thead>
<tbody>
<tr th:each="line,lineStat : *{lines}">
<td th:text="${lineStat.count}">1</td>
<td>
<input type="text" th:field="*{lines[__${lineStat.index}__].productIdentifier}"
th:errorclass="fieldError"/>
</td>
<td>
<input type="text" th:field="*{lines[__${lineStat.index}__].description}"
th:errorclass="fieldError"/>
</td>
<td>
<input type="text" th:field="*{lines[__${lineStat.index}__].quantity}"
th:errorclass="fieldError"/>
</td>
<td>
<input type="text" th:field="*{{lines[__${lineStat.index}__].price}}"
th:errorclass="fieldError"/>
</td>
<td>
<button type="submit" name="removeLine" th:value="${lineStat.index}"
th:text="#{order.line.remove}">Remove line
</button>
</td>
</tr>
</tbody>
</table>
</div>
This is then backed by class with
public class OrderLine implements Serializable {
#NotEmpty
private String description;
#NotNull
#NumberFormat(style = NumberFormat.Style.CURRENCY)
private BigDecimal price;
#NotEmpty
private String productIdentifier;
#NotNull
#Min(value = 1)
private Integer quantity;
and then in my controller
#RequestMapping(value="/customer/orders", params={"addLine"})
public String addLine(final Order order, final BindingResult bindingResult) {
order.getLines().add(new OrderLine());
return "customer/orders";
}
The html page aleady includes
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
and the character encoding servlet filter is set up as below
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return new Filter[] {characterEncodingFilter};
}
Further to this from using Fiddler I can see that the response header to the dandelion datatables ajax request is incorrectly encoded as ISO-88591. I'm using datatables-thymeleaf 0.93 with datatables 1.9.4
From experimenting if I set thymeleaf encoding, the spring servlet filter and the html meta tag to ISO-88591 then the currency symbol appears correctly rendered although I would like this to work with UTF-8
Eventually I found an answer in this post CharacterEncodingFilter don't work together with Spring Security 3.2.0 provided by #Christian Nilsson. Basically I needed to force the character encoding filter to be registered using the onStartup method rather than the usual getServletFilters.

Resources