How do I bind table values to a Map and post with Spring MVC and Thymeleaf? - spring

I have an entry form where users select a subset of items which populate a table. I need to bind each row's first and and third column value to a key and value, respectively, enter them into a Map<Integer, Integer> passed in through the controller, and post the data. I've poured through many different solutions online and have yet to find one that works. The map always returns empty.
Wrapper class for Map
#Getter #Setter
public class ItemForm {
private Map<Integer, Integer> items = new HashMap<>();
}
Controllers
#GetMapping(...)
public String showItemPage(Model model) {
...
model.addAttribute("itemForm", new ItemForm());
...
}
#PostMapping(...)
public String processItemUpdate(#ModelAttribute("itemForm") ItemForm itemForm, BindingResult bindingResult) {
...
}
Template
<tr th:each="item : *{items}">
<td>
<input type="text" th:value="${item.key}">
</td>
<td></td>
<td>
<input type="text" th:field="*{items[__${item.key}__]}">
</td>
</tr>
I understand that I will need something like th:field="*{items[__${item.key}__]}" to access the map, but as to extracting and combining the key-value pair I'm a bit lost.
edit:
Is something along these lines possible?
#Getter #Setter
public class ItemFormPair {
private int ID, quantity;
}
#Getter #Setter
public class ItemForm {
private List<ItemFormPair> items = new ArrayList<>();
}
<tr th:each="item, stat : *{items}">
<td>
<input type="text" th:field="*{items[__${stat.index}__].ID}">
</td>
<td></td>
<td>
<input type="text" th:field="*{items[__${stat.index}__].quantity}">
</td>
</tr>
edit:
I don't really want to spend any more time on this problem and there doesn't appear to be an elegant solution available so I'm simply going to use an Ajax POST request.

You bound the single key/values of the map to the form but not the map itself. That won't work that way. I'm quite sure there is no way to get the map as whole piece back from the form. Maybe with a converter.
An alternative could be to assign name/id to the input fields and read all key/values back to map in the processItemUpdate method:
This solution works on my site. I redefined my answer more precisely:
input.html
<!DOCTYPE HTML>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head />
<body>
<form th:action="#{/inputPost}" method="post" th:fragment="form">
<table>
<tr th:each="item,iter : ${itemForm.items.entrySet()}">
<td><input type="text" th:id="${iter.index + '.ID'}"
th:name="${iter.index + '.ID'}" th:value="${item.key}"></td>
<td><input type="text" th:id="${iter.index + '.VALUE'}"
th:name="${iter.index + '.VALUE'}" th:value="${item.value}"></td>
</tr>
</table>
<input type="submit" name="Send" value="Send" /> <input type="submit"
name="Add" value="Add new Line" />
</form>
</body>
</html>
success.html
<!DOCTYPE HTML>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head></head>
<body>
<table border="1">
<tr th:each="item : ${itemForm.items.entrySet()}">
<td th:text="${item.key}"></td>
<td th:text="${item.value}"></td>
</tr>
</table>
</body>
</html>
Controller
#GetMapping("/inputForm")
public String dummy(Model model) {
ItemForm form = new ItemForm();
form.getItems().put(form.getItems().size(), 42);
model.addAttribute("itemForm", form);
return "input";
}
#PostMapping("/inputPost")
public String processItemUpdate(HttpServletRequest request, Model model) {
Map<String, String[]> params = request.getParameterMap();
ItemForm form = new ItemForm();
String operation = null;
for (Entry<String, String[]> entry : params.entrySet()) {
if (entry.getKey().endsWith(".ID")) { // only react on ID values. The values will be directly fetched from
// map
String[] tokens = StringUtils.split(entry.getKey(), ".");
Integer id = Integer.parseInt(tokens[0]);
Integer idValue = Integer.parseInt(entry.getValue()[0]);
String[] value = params.get(id + ".VALUE"); // fetch the value to defined ID
Integer valueValue = Integer.parseInt(value[0]);
form.getItems().put(idValue, valueValue);
} else if (entry.getKey().equalsIgnoreCase("Send")) { // determine operation
operation = "send";
} else if (entry.getKey().equalsIgnoreCase("Add")) { // determine operation
operation = "add";
}
}
model.addAttribute("itemForm", form);
if (operation.equals("add")) { // add a new line and resend form again
form.getItems().put(form.getItems().size(), 42);
return "input";
}
return "success";
}

Related

How to send dropdown value from the html form to the controller?

index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Super Spy App</title>
</head>
<body>
<h1>Our Super Cool Spy App</h1>
<h2>Create a Mission</h2>
<form action="/addMission" method="post">
<p><input type="submit" value="Create a Mission"></p>
</form>
<form action="/viewMission" method="get">
<h2>View Missions for</h2>
<select id="agents" name="agents">
<option value="Johnny English">Johnny English</option>
<option value="Natasha Romanova">Natasha Romanova</option>
<option value="Austin Powers">Austin Powers</option>
</select>
<input type="submit" value="Go">
</form>
</body>
</html>
ViewMissions.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>View Missions</title>
</head>
<body>
<h1> Here are the missions for</h1>
<div th:if="${missionList.empty}">
<h2>No Current Missions</h2>
</div>
<div th:unless="${missionList.empty}">
<table border="1">
<tr>
<th>Title</th>
<th>Gadget 1</th>
<th>Gadget 2</th>
<th colspan="2">Operation</th>
</tr>
<tr th:each="mission : ${missionList}">
<td th:text="${mission.title}"></td>
<td th:text="${mission.gadget1}"></td>
<td th:text="${mission.gadget2}"></td>
<td>edit</td>
<td>delete</td>
</tr>
</table>
</div>
<p> Back to home </p>
</body>
</html>
Controller Class
#GetMapping("/")
public String Home() {
return "index";
}
#PostMapping("/addMission")
public String addMission(Model model) {
model.addAttribute("mission", new Mission());
return "create_mission";
}
#GetMapping("/createMission")
public String ViewMission1(Model model) {
List<Mission> mission1 = database.getMissions();
model.addAttribute("missionList", mission1);
return "view_missions";
}
#PostMapping("/createMission")
public String createMission(#ModelAttribute Mission mission) {
int returnValue = database.createMission(mission);
System.out.println(returnValue);
return "view_missions";
}
#GetMapping("/viewMission")
public String viewMission2(Model model) {
List<Mission> mission1 = database.getMissions();
model.addAttribute("missionList", mission1);
return "view_missions";
}
getMissions method
public List<Mission> getMissions() {
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
String query = "SELECT * FROM missions";
BeanPropertyRowMapper<Mission> missionMapper = new BeanPropertyRowMapper<Mission>(Mission.class);
List<Mission> missions = jdbc.query(query, namedParameters, missionMapper);
return missions;
}
Mission.java (the getter setter are already set but I didn't paste them here to prevent hustle and bustle)
public class Mission {
private Long id;
private String agent;
private String title;
private String gadget1;
private String gadget2;
}
So, in the above examples, I want to send the value selected from the dropdown list to my controller.
Im my html, if I select any value from the dropdown and press 'Go' it shows me the whole database for all the 3 agents but not the particular one that I selected.
Any suggestions how to curb this error.
I have tried searching for a solution on internet but they were using JSP which I haven't studied yet.
You can get the value submitted from the view to the controller in many ways. As you have a single value is passed from View to Controller you can use
#RequestParam
Your viewMission may look like this
#GetMapping("/viewMission")
public String viewMission2(#RequestParam#RequestParam(name = "agents", required = true) String agents, Model model) {
List<Mission> mission1 = database.getMissions(String agents);
model.addAttribute("missionList", mission1);
return "view_missions";
}
You have to pass the selected value to your query to filter the list based on the selected agent and your query will be
public List<Mission> getMissions(String agents) {
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
String query = "SELECT * FROM missions WHERE agent ='" + agent +"'";
BeanPropertyRowMapper<Mission> missionMapper = new BeanPropertyRowMapper<Mission>(Mission.class);
List<Mission> missions = jdbc.query(query, namedParameters, missionMapper);
return missions;
}
Which will filter the list.

Controller does not recieve data from jsp page

I have sent data from jsp page to controller. It shows error.
The origin server did not find a current representation for the target
resource or is not willing to disclose that one exists.
This is my controller::::
#GetMapping(value = "/createdistrict")
public ModelAndView createdistrict(Locale locale, Model model) {
List<Division> allDivisionList = new ArrayList<Division>();
allDivisionList = this.districtService.listdivisions() ;
Map<Integer,String> allDivision = new LinkedHashMap<Integer,String>();
for( int i=0 ; i < allDivisionList.size() ; i++) {
//System.out.println(" division id ::::::::::" + allDivisionList.get(i).getId() + " division name:::::::::" + allDivisionList.get(i).getName());
allDivision.put(allDivisionList.get(i).getId() , allDivisionList.get(i).getName());
}
return new ModelAndView("createdistrict" , "allDivision" , allDivision);
}
#RequestMapping(value="/adddistrict/{division}")
public String addDistrict(#ModelAttribute("district")District district, ModelMap model ,#RequestParam("division") int division) {
System.out.println("id:::::::::::::::::::" + division);
this.districtService.adddistrict(district, division);
return "redirect:districtlist";
}
This is my jsp page::::
<form method="POST" action="/farmvill/adddistrict" modelAtribute="district">
<table class="create-table table table-hover">
<tr>
<td>
Division
</td>
<td>
<select id="division" name="division">
<c:forEach items="${allDivision}" var="allDivision">
<option class="dropdivision" value="${allDivision.key}">${allDivision.value }</option>
</c:forEach>
</select>
</td>
</tr>
<tr>
<td>
Name
</td>
<td>
<input type="text" id="name" name="name" path="name"></input>
</td>
</tr>
<!-- End of single tr -->
</table>
<!-- End of table -->
<div class="button-set text-right">
<button type="submit" class="btn site-btn filled-btn" id="savebutton">save</button>
cancel
reset
</div>
<!-- End of button-set -->
</form>
What I should do now?
Remove division from mapping path. it's a regular request parameter
#RequestMapping(value="/adddistrict")
public String addDistrict(#ModelAttribute("district")District district, ModelMap model ,#RequestParam("division") int division) {

ModelAttribute not working with lists in spring

I want to bind a List using ModelAttribute. The list contains objects of type Transaction each of which contains transactionid (type int). This is my controller code:
#RequestMapping(value = "/approvecreditdebit.do", method = RequestMethod.POST)
public ModelAndView doActions(HttpServletRequest request,
#ModelAttribute("clc") Clc transactionList, BindingResult result,
ModelMap model) {
/*
* switch (action) { case "approve":
*/
System.out.println("Obj = " + transactionList.getClass());
System.out.println("Val = " + transactionList.getTransactionList());
Users users = new Users();
return new ModelAndView("internal","internal",users);
}
This is my jsp code:
<form:form action="approvecreditdebit.do" method="POST"
modelAttribute="clc">
<table border="1">
<tr>
<th>no</th>
<th>ID</th>
</tr>
<c:forEach items="${clc.transactionList}"
var="transaction" varStatus="status">
<tr>
<td>${status.index}</td>
<td><input name = "transaction[${status.index}].transactionId"
value="${transaction.transactionId}" /></td>
</tr>
</c:forEach>
</table>
<br>
<br>
<center>
<input type="submit" value="approve" />
</center>
</form:form>
This is the Clc class:
public class Clc{
private List<Transaction> transactionList;
public List<Transaction> getTransactionList() {
return transactionList;
}
public void setTransactionList(List<Transaction> transactionList) {
this.transactionList = transactionList;
}
}
The value of transactionList is not being set to the values received from the form. I receive the following error:
Request processing failed; nested exception is java.lang.NullPointerException
I tried searching for the solution on google and got a lot of solutions from stackoverflow but none of them seem to work.
Try something like this (notice the use of <form:input>). I just tried it on a simple Spring MVC app and it works (list is not null and has the values from the form when I try to access it in my POST method).
<c:forEach var="transaction" varStatus="status" items="${clc.transactionList}">
<tr>
<td>${status.index}</td>
<td><form:input path="transactionList[${status.index}].id" /></td>
</tr>
</c:forEach>

Spring MVC: list of checkboxes not returned to controller on POST

Why doesn't the server see my list of filled checkboxes?
This question seems to be here asked many times, but the details are so different for each requester that it seems a different answer is needed each time. Here is my story.
These are my data-bearing classes. The Offer contains a list of Filter objects in the filter attribute:.
public class Offer implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Long id = null;
#Column(name="title")
private String title = null;
[snip]
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name = "offer_filter",
joinColumns = { #JoinColumn(name = "offer_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "filter_id", nullable = false, updatable = false) })
private List<Filter> filters;
[snip]
}
public class Filter implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Long id;
#NotBlank
#Length(max=100)
#Column(name="text")
private String text;
[snip]
#Transient
private boolean owned = false;
[snip]
}
The simple controller sends the offerEdit.jsp page, with a fully-populated Offer object. The object contains a list of Filters. Because of the owned attribute, only one of the three Filters is pre-checked. This simulates my eventual plan, where the list of Filters is the whole universe and what the Offer owns is a subset.
Note the annotations, that the Offer has the filter list going to the web page but doesn't see it coming back.
public class OfferController {
[snip]
#RequestMapping(value = "/edit", method = RequestMethod.GET)
public String getEdit(#RequestParam("id") Long id, Model model, HttpSession session) {
Offer offerAttribute = offerService.get(id);
// At this point, offerAttribute.filters has three elements.
// Mockup -- tells web page that only the middle one of the three Filters should be checked.
List<Filter> filters = offer.getFilters();
Filter filter = filters.get(1);
filter.setOwned(true);
model.addAttribute("offerAttribute", offerAttribute);
return "offer/offerEdit";
}
#RequestMapping(value = "/edit", method = RequestMethod.POST)
public String postEdit(#RequestParam("id") Long id, #Valid #ModelAttribute("offerAttribute") Offer offerAttribute, BindingResult result, HttpSession session, Model model) {
// At this point, offerAttribute.filters is null.
if(result.hasErrors()) {
result.reject("offer.invalidFields");
return "offer/offerEdit";
}
offerAttribute.setId(id);
offerService.edit(offerAttribute);
return "redirect:/offer/list";
}
[snip]
}
The web page has this for its checkbox section. I use form:checkbox over form:checkboxes because I want to use a table,
[snip]
<form:form modelAttribute="offerAttribute" method="POST" action="${saveUrl}">
<table>
<tr>
<td></td>
<td><form:hidden path="id" /></td>
</tr>
<tr>
<td><form:label path="title">Title:</form:label></td>
<td><form:input path="title" size="80" /></td>
<td><form:errors path="title" cssClass="error" /></td>
</tr>
[snip]
</table>
<table>
</table>
<table>
<c:forEach items="${offerAttribute.filters}" var="filter">
<tr>
<td><form:checkbox
path="filters"
label="${filter.text}"
value="${filter.id}"
checked="${filter.owned ? 'true' : ''}" />
</td>
</tr>
</c:forEach>
</table>
[snip]
The displayed web page has three filter checkboxes displayed, with just the middle checkbox filled in.
For the returned list, I expect the server to get only the middle checkbox, which is just what I want.
Here is what the generated checkboxes look like as source:
<table style="border: 1px solid; width: 100%; text-align:left;">
<tr>
<td>
<input id="filters1" name="filters" type="checkbox" value="1"/>
<label for="filters1">Adults (18+) desired, please</label>
<input type="hidden" name="_filters" value="on"/>
</td>
</tr>
<tr>
<td>
<input id="filters2" name="filters" checked="true" type="checkbox" value="2"/>
<label for="filters2">Quiet audiences, please</label>
<input type="hidden" name="_filters" value="on"/>
</td>
</tr>
<tr>
<td>
<input id="filters3" name="filters" type="checkbox" value="4"/>
<label for="filters3">Filter Text First</label>
<input type="hidden" name="_filters" value="on"/>
</td>
</tr>
</table>
My checkbox is set, and in the HTML. To restate my question,
Why doesn't the checkbox value get seen in the controller's POST handler?
Thanks for any answers,
Jerome.
The values of checkboxes could not be directly bind to List.
To get this working you need to create a simple pojo databean that will hold the values of your form fields in jsp. And in that databean to bind the values you need to declare int[] filterId and the values of your checkboxes will bind in that array.
Hope this helps you.
After a lot of web research and different debugging sessions, I came up with a configuration I can live with.
In my controller I provide a model attribute of "filterList", containing List. The "offerAttribute" is an Offer object, containing List filters.
The view has this sequence:
<table>
<c:forEach items="${filterList}" var="filter" varStatus="status">
<tr>
<td><input type="checkbox" name="filters[${status.index}].id"
value="${filter.id}"
${filter.owned ? 'checked' : ''} /> ${filter.text}
</td>
</tr>
</c:forEach>
</table>
When the POST is done the ownerAttribute.filters list is just as long as the original filterList object that created the checkboxes. The checked ones contains a Filter.id value. The unchecked ones contain a null. (That is, the returned filters list is "sparse".) If the user clicks on just a few checkboxes then I must parse the returned list to find those that were chosen.
Once I know the ID values of the checked-on filters, I fetch each of them through Hibernate, put them into my reconstructed Offer object and then persist them to the database.
I realize that I'm not (currently) using the form:checkbox. But it works, and I'm pressed for time here. I'll come back later, perhaps, and see what form:checkbox can do for me.

How to modify a record displaying a form in a view

I am trying to accomplish the following:
I have a list of fruits, that are stored in a table with two columns "id", "name" and "color".
Next to each fruit, I got a "modify" button. What I want to do here is being able to display the fruit in a form and being able to modify the "name" and "color" attributes.
I don't understand why, but when I click the "modify" button, the form is being displayed but the properties of the fruits that I clicked are not.
Here is the code:
Controller:
#RequestMapping(value = "/fruit/modify", method = RequestMethod.POST)
public String modifyFruit( #RequestParam("id") int id, ModelMap model) {
Fruit fruit = fruitManager.getFruitById(id);
model.addAttribute("fruit", fruit);
return "redirect:/modifyfruit";
}
#RequestMapping(value = "/modifyfruit", method = RequestMethod.GET)
public String showAddForm(#ModelAttribute("fruit") Fruit fruit, ModelMap model) {
model.addAttribute("fruit", fruit);
return "/secure/modifyfruit";
}
Here is the modify button that I am displaying next to each fruit in my list:
<td>
<c:url var="modifyUrl" value="/fruit/modify.html"/>
<form id="${fruitForm}" action="${modifyUrl}" method="POST">
<input id="id" name="id" type="hidden" value="${fruit.id}"/>
<input type="submit" value="modify"/>
</form>
</td>
Here is the modifyfruit.jsp that I am using to display the form that I want to populate:
<body>
<form:form method="post" commandName="fruit">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0"
cellpadding="5">
<tr>
<td align="right">Name:</td>
<td><form:input path="title" value="${fruit.name}"/></td>
</tr>
<tr>
<td align="right">Color:</td>
<td><form:input path="color" value="${fruit.color}"/></td>
</tr>
</table>
<br>
<input type="submit" align="center" value="Post Ad">
</form:form>
</body>
Your redirect is simply going to that new URL without any request params being added. Therefore your fruit ID is being discarded, which is why nothing gets displayed.
The redirect seems pointless - why not return the same view name string as the GET version instead?
To redirect with the params, try:
return "redirect:/modifyfruit?id=" + id;
EDIT: just noticed you have added the Fruit to the model - this does not get transferred in a redirect and wouldn't work anyway.

Resources