Spring MVC passing ArrayList back to controller - java

I am new to Spring. I display a list with users. Every row has a checkbox for removing the users.
Controller:
#Controller
public class AdminController {
#Autowired
private UserDao userDao;
#RequestMapping(value = "/admin", method = RequestMethod.GET)
public ModelAndView adminPage() {
ModelAndView model = new ModelAndView();
model.addObject("users", userDao.findAll());
model.setViewName("admin");
return model;
}
#RequestMapping(value = "admin/remove", method = RequestMethod.POST)
public ModelAndView removeUser(#ModelAttribute(value = "users") ArrayList<User> users) {
ModelAndView model = new ModelAndView();
//UPDATE USERS HERE
model.setViewName("redirect:/admin");
return model;
}
JSP:
<form:form action="/admin/remove" method="POST" modelAttribute="users">
<table class="table table-striped">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Email/login</th>
<th>Profession</th>
<th>Select<th>
</tr>
</thead>
<tbody>
<c:forEach var="user" items="${users}">
<tr>
<td>${user.firstName}</td>
<td>${user.lastName}</td>
<td>${user.login}</td>
<td>${user.profession}</td>
<td><input type="checkbox" value="${user.delete}"/></td>
</tr>
</c:forEach>
</tbody>
</table>
<input type="submit" value="Delete user(s)" class="btn-danger" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form:form>
The list is rendered correctly. If i press the "Delete user(s)" button. The #modelAttribute users is empty.
I also tried wrapping the list in a new class, but i get the same results.
Any ideas?

Thanks to minion, i found the answer
Wrapper:
public class UserListWrapper {
private ArrayList<User> users;
public ArrayList<User> getUsers() {
return users;
}
public void setUsers(ArrayList<User> users) {
this.users = users;
}
Controller:
#Controller
public class AdminController {
#Autowired
private UserDao userDao;
#RequestMapping(value = "/admin", method = RequestMethod.GET)
public ModelAndView adminPage() {
ModelAndView model = new ModelAndView();
UserListWrapper wrapper = new UserListWrapper();
wrapper.setUsers(new ArrayList<User>(userDao.findAll()));
model.addObject("userListWrapper",wrapper);
model.setViewName("admin");
return model;
}
#RequestMapping(value = "admin/remove", method = RequestMethod.POST)
public ModelAndView removeUser(#ModelAttribute(value = "userListWrapper") UserListWrapper userListWrapper) {
ModelAndView model = new ModelAndView();
userDao.removeFlaggedUsers(userListWrapper.getUsers());
model.setViewName("redirect:/admin");
return model;
}
}
View:
<form:form action="/admin/remove" method="POST" modelAttribute="userListWrapper">
<table class="table table-striped">
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
<th>Email/login</th>
<th>Profession</th>
<th>Select<th>
</tr>
</thead>
<tbody>
<c:forEach varStatus="us" var="user" items="${userListWrapper.users}" >
<tr>
<td><form:input type="hidden" path="users[${us.index}].firstName"/>${user.firstName}</td>
<td><form:input type="hidden" path="users[${us.index}].lastName"/> ${user.lastName}</td>
<td><form:input type="hidden" path="users[${us.index}].login"/>${user.login}</td>
<td><form:input type="hidden" path="users[${us.index}].profession"/>${user.profession}</td>
<td><form:checkbox path="users[${us.index}].delete" value="${user.delete}"/></td>
<form:input type="hidden" path="users[${us.index}].id"/>
</tr>
</c:forEach>
</tbody>
</table>
<input type="submit" value="Delete user(s)" class="btn-danger" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form:form>
Thank you!
EDIT: Dont forget to also add the fields you are not displaying.
For example:
If you dont add the id, your delete will not work because the id in the returned User object will be NULL.

Your ModelAttribute is empty as there is no form data binding happening from your jsp to your model attribute. Take a look at how Spring sample for binding collections "http://developer.ucsd.edu/develop/user-interface/building-a-form/form-binding-with-collections.html". This will help you to understand.
Most of the Spring application typically uses form:input with "path" parameter to do data binding.

You should build your functionality around spring-mvc select tag. Few changes would be in order though, push a list to a POJO class e.g.
public class FormBean {
private List<String> users;
public FormBean() {
}
public List<String> getUsers() {
return users;
}
public void setUsers(List<String> users) {
this.users = users;
}
}
change your mapping to
#RequestMapping(value = "admin/remove", method = RequestMethod.POST)
public ModelAndView removeUser(#ModelAttribute(value = "formBean") FormBean formBean) {
finally, swap your c:forEach with springs select tag, so something like
<form:form action="/admin/remove" method="POST" modelAttribute="formBean">
...
<form:select path="users" items="${users}" multiple="true" />
...
</form>

This is because you are using a redirect: in your view. Have a look on Flash Attributes :
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/support/RedirectAttributes.html
http://viralpatel.net/blogs/spring-mvc-flash-attribute-example/
You should be able to get the updated list :)

Related

JSP Input value To Java Method -> HttpServletRequest gives NULL value

Hello Guys.
I have simple Storage page whichs display all Products from DB.
I set to each of them with Unique name to change the Amount of product.
When i want to catch this value in Java method its returns me null.
Can you help me what i need to do to corretly catching value's in this text inputs ?
Controller :
#Controller
public class StoragePageController extends HttpServlet {
#GET
#RequestMapping(value = "/storage/subamount/{id}")
public String substractTheAmountValue(#PathVariable("id") int id, Model model, HttpServletRequest request) {
String amount_req = request.getParameter("amount_sub_" + id);
System.out.println(amount_req);
return null;
}
}
JSP fragment :
<c:set var="licznik" value="${recordStartCounter }" />
<div align="center">
<table width="1000" border="0" cellpadding="6" cellspacing="2">
<c:forEach var="u" items="${productList }">
<c:set var="licznik" value="${licznik+1}" />
<tr onmouseover="changeTrBg(this)" onmouseout="defaultTrBg(this)">
<td align="right"><c:out value="${licznik }" /></td>
<td align="left"><c:out value="${u.description }" /></td>
<td align="left"><c:out value="${u.amount }" /></td>
<td align="center"><input type="text" name="amount_sub_${licznik}" id="amount_sub_${licznik}"></td>
<td align="center"><input type="button" value="Substract the value" onclick="window.location.href='${pageContext.request.contextPath}/storage/subamount/${licznik}'"/></td>
</tr>
</c:forEach>
</table>
You should be having your API controller like the one given below given that your UI is posting the data to your API / Controller (assuming you are using the latest version of Spring Boot). You have a #Get mapping which does not accept request payload in the body.
#RestController
public class StoragePageController {
#PostMapping(value = "/storage/subamount/{id}", produces = {"application/json"})
public String substractTheAmountValue(#PathVariable("id") int id, Model model) {
String amount_req = id;
System.out.println(amount_req);
return null;
}
}

Spring MVC (text field and button) using CrudRepository and Thymeleaf

I need to create a simple search button via a custom method using CrudRepository and show the search results on a website.
Event.java
#Entity
#Table(name = "events")
public class Event {
private String name;
}
EventRepository.java
public interface EventRepository extends CrudRepository<Event, Long>{
public Iterable<Event> findByName(String name);
}
EventService.java
public interface EventService {
public Iterable<Event> findByName(String name);
}
EventServiceImpl.java
#Service
public class EventServiceImpl implements EventService {
#Override
public Iterable<Event> findByName(String name) {
return eventRepository.findByName(name);
}
}
EventsController.java
#Controller
#RequestMapping(value = "/events", produces = { MediaType.TEXT_HTML_VALUE })
public class EventsController {
#RequestMapping(value = "/search", method = RequestMethod.GET)
public String showEventsByName(#ModelAttribute Event event, #RequestParam (value = "search", required = false) String name, Model model) {
model.addAttribute("event", event);
model.addAttribute("searchResult", eventService.findByName(name));
return "events/index";
}
index.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/default}">
<head>
<title>All events</title>
</head>
<body>
<div layout:fragment="content">
<h3>Search for an event by name</h2>
<form th:object="${event}" th:action="#{/events/search}" method="get">
<input type="text" name="search" id="search" th:value="${search}"/>
<input type="submit" value="Search"/>
<div th:if="${not #lists.isEmpty(search)}">
<h2>Events search results</h2>
<table class="table table-striped table-hover">
<thead>
<tr>
<th><i class="fa fa-bolt"></i> Event</th>
<th><i class="fa fa-map-marker"></i> Venue</th>
<th><i class="fa fa-calendar"></i> Date</th>
<th><i class="fa fa-clock-o"></i> Time</th>
</tr>
</thead>
<tbody>
<tr th:each="event: ${searchResult}">
<td th:text="${event.name}">My Event</td>
<td th:text="${event.venue.getName()}">Event venue</td>
<td th:text="${{event.date}}">Event date</td>
<td th:text="${{event.time}}">Event time</td>
</tr>
</tbody>
</table>
</div>
</form>
<h1>All events</h1>
<table class="table table-striped table-hover">
<thead>
<tr>
<th><i class="fa fa-bolt"></i> Event</th>
<th><i class="fa fa-map-marker"></i> Venue</th>
<th><i class="fa fa-calendar"></i> Date</th>
<th><i class="fa fa-clock-o"></i> Time</th>
</tr>
</thead>
<tbody>
<tr th:each="e : ${events}">
<td th:text="${e.name}">My Event</td>
<td th:text="${e.venue.getName()}">Event venue</td>
<td th:text="${{e.date}}">Event date</td>
<td th:text="${{e.time}}">Event time</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
I am using the naming convention for custom CrudRepository methods, which means that the implementation should be provided by the interface already.
When I run the web application and submit some input in the form, the desired html view is displayed, but the search result never shows any event.
Picture 1
Picture 2
I tried removing #ModelAttribute Event event and model.addAttribute("event", event); as in this post which addresses the same issue it was not used, but the outcome was the same.
I know that there is a similar post already, but the solution suggested there does not work for me and I tried different approaches but the result was not as expected.
eventService.findByName(name);
Will look for exact name match
Create a new method for searching
Service Interface
public interface EventService {
public Iterable<Event> findByName(String name);
public Iterable<Event> searchForName(String name);
}
Repository
public interface EventRepository extends CrudRepository<Event, Long>{
public Iterable<Event> findByName(String name);
public Iterable<Event> findByNameContainingIgnoreCase(String name);
}
Service Class
#Service
public class EventServiceImpl implements EventService {
#Override
public Iterable<Event> findByName(String name) {
return eventRepository.findByName(name);
}
#Override
public Iterable<Event> searchForName(String name) {
return eventRepository.findByNameContainingIgnoreCase(name);
}
}
Update:
When searching for nothing, do you want to display nothing or everything? I prefer the latter. If you want to not show anything, check for the search parameter before calling the service.
#RequestMapping(value = "/search", method = RequestMethod.GET)
public String showEventsByName(#RequestParam (value = "search", required = false) String name, Model model) {
if(name!=null && !name.isEmpty()){
model.addAttribute("searchResult", eventService.searchForName(name));
} // else{ } create an empty list or handle null in view
// ...
}
if you want to hide the table header if there are no results use
th:if="${searchResult !=null and !searchResult.isEmpty()}" <!-- For Both -->

Spring MVC controller returning null modelAttribute when #Valid fails

I've been scratching my head for a long time on this one. I'm working on a web app that has over 20 controllers and i'm having this strange issue with only one of them. No matter how long I compare with a working one i cannot seem to find any differences. Basically, I am able to add an Interface entity no problem as long as it does not fail validation. If i try to save say a completely empty object (or leave one of the required fields blank for example) I get this error :
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'intface' available as request attribute
Here is the relevant code :
View :
<form:form method="POST" modelAttribute="intface" class="form-style-7">
<form:input type="hidden" path="id" id="id"/>
<table>
<tr>
<td><label for="name">Name: </label> </td>
<td><form:input path="name" id="name"/></td>
<td><form:errors path="name" cssClass="error"/></td>
</tr>
<tr>
<td><label for="type">Type: </label> </td>
<td><form:input path="type" id="type"/></td>
<td><form:errors path="type" cssClass="error"/></td>
</tr>
<tr>
<td><label for="ip">IP: </label> </td>
<td><form:input path="ip" id="ip"/></td>
<td><form:errors path="ip" cssClass="error"/></td>
</tr>
<tr>
<td><label for="port">Port: </label> </td>
<td><form:input path="port" id="port"/></td>
<td><form:errors path="port" cssClass="error"/></td>
</tr>
<tr>
<td><label for="test">Test: </label> </td>
<td><form:checkbox path="test" id="test"/></td>
<td><form:errors path="test" cssClass="error"/></td>
</tr>
<tr>
<td colspan="3">
<c:choose>
<c:when test="${edit}">
<input type="submit" value="Update Interface"/>
</c:when>
<c:otherwise>
<input type="submit" value="Add Interface"/>
</c:otherwise>
</c:choose>
</td>
</tr>
</table>
</form:form>
Controller :
#RequestMapping(value = {"/newInterface"}, method = RequestMethod.POST)
public String saveInterface(#Valid Interface intface, BindingResult result, ModelMap model)
{
if (result.hasErrors())
{
return "interfaceDataAccess";
}
interfaceService.saveInterface(intface);
return "redirect:/interfaces/list";
}
Entity :
#RequestMapping(value = {"/newInterface"}, method = RequestMethod.POST)
public String saveInterface(#Valid Interface intface, BindingResult result, ModelMap model)
{
if (result.hasErrors())
{
return "interfaceDataAccess";
}
interfaceService.saveInterface(intface);
return "redirect:/interfaces/list";
}
You will notice this #Unique annotation which is something i built. Essentially it allows me to check if one of the values entered by the user already exists and that works just fine on my other entities. I tried removing it completely and i still get this exception as soon as validation fails. I literally have over 20 controllers that work the exact same way, line for line, and they work fine. This completely baffles me and i'm sure it's something really stupid. I can provide the MVC code for a working entity for comparison if needed.
Thanks
* EDIT *
Here is some working code for my heightUnit class:
Controller :
#RequestMapping(value = {"/newHeightUnit"}, method = RequestMethod.GET)
public String newHeightUnit(ModelMap model)
{
HeightUnit heightUnit = new HeightUnit();
model.addAttribute("heightUnit", heightUnit);
model.addAttribute("edit", false);
return "heightUnitDataAccess";
}
#RequestMapping(value = {"/newHeightUnit"}, method = RequestMethod.POST)
public String saveHeightUnit(#Valid HeightUnit heightUnit, BindingResult result, ModelMap model)
{
if (result.hasErrors())
{
return "heightUnitDataAccess";
}
heightUnitService.saveHeightUnit(heightUnit);
return "redirect:/heightUnits/list";
}
View :
<form:form method="POST" modelAttribute="heightUnit" class="form-style-7">
<form:input type="hidden" path="id" id="id"/>
<ul>
<li>
<label for="name">Name: </label>
<form:input path="name" id="name"/>
<span><form:errors path="name" cssClass="error"/></span>
</li>
<c:choose>
<c:when test="${edit}">
<input type="submit" value="Update Height Unit"/>
</c:when>
<c:otherwise>
<input type="submit" value="Add Height Unit"/>
</c:otherwise>
</c:choose>
</ul>
</form:form>
Entity :
private int id;
private String name;
#Id
#GeneratedValue
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
#Size(min = 1, max = 15, message = "Name must be between 1 and 15 characters long")
#Unique(entityType = "heightUnit", field = "name", message = "The name needs to be unique!")
#Column(nullable = false, unique = true, length = 15)
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
#Override
public boolean equals(Object obj)
{
if (obj instanceof HeightUnit)
{
HeightUnit heightUnit = (HeightUnit)obj;
if (id == heightUnit.getId())
{
return true;
}
}
return false;
}
#Override
public int hashCode()
{
int hash = 7;
hash = 37 * hash + this.id;
hash = 37 * hash + Objects.hashCode(this.name);
return hash;
}
This works perfectly and i'm getting the errors back on the form when attempting to break a validation rule.
Your issue is that you are heading to the form page and it's looking for a pre-instantiated model that aIt's not available. What you have to do is to add the Interface object as an attribute of the model:
model.addAttribute("intface", new Interface());
Remember to add it to the #RequestMapping annoted method that takes you to the form page, something like this:
#RequestMapping(value = "/", method = RequestMethod.GET)
public String displayNewInterfaceLogin(Model model) {
model.addAttribute("intface", new Interface());
return "newInterface";
}
i FINALLY figured this out. For whatever the crap reason, i had to change the signature of my controller's saveInterface method to this :
public String saveInterface(#Valid #ModelAttribute("intface") Interface intface, BindingResult result, ModelMap model)
I have NO IDEA why only THIS entity complains about this when the CRUD operations of all my entity types are built the exact same way but there you go!
Hope this can help someone eventually!

Spring, IllegalStateException when opening page with form

I am new to Spring and I get this exception, when entering campaigns.jsp with browser.
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'campaign' available as request attribute
I have this controller :
#Controller
#RequestMapping(value = "/admin")
public class AdminIndexController {
#RequestMapping(value = "/secure/campaigns.jsp", method = RequestMethod.GET)
public String campaigns() {
return "campaigns";
}
#RequestMapping(value = "/secure/create", method = RequestMethod.POST)
public String addContact(#ModelAttribute("campaign")
Campaign campaign) {
return "campaigns";
}
}
Campaings.jsp (if I remove this part, it shows the page correctly) :
<form:form method="post" action="create" commandName="campaign">
<table>
<tr>
<td><form:label path="question">Question</form:label></td>
<td><form:input path="question" />
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Add Campaign"/>
</td>
</tr>
</table>
</form:form>
I suppose there is a problem with mapping action="create", I am not sure where it is pointing to. I thought it is pointing to the same place as the .jsp is. This is address I use http://localhost:8080/server/rest/admin/secure/campaigns.jsp
This
<form:form method="post" action="create" commandName="campaign">
expects a model attribute (or command object), an HttpServletRequest attribute in reality, with the name campaign to use as a template for the form fields.
You haven't added a request attribute with that name. You need to do that before the view is rendered. For example
#RequestMapping(value = "/secure/campaigns.jsp", method = RequestMethod.GET)
public String campaigns(Model model) {
model.addAttribute("campaign", new Campaign());
return "campaigns";
}
The object doesn't need to have any fields set since it is only used as a template.

Incorrect request from jsp form

When I try submit my form tomcat throw me "The request sent by the client was syntactically incorrect."
This is my jsp form. Please help me.
<form:form method="POST" commandName="articleDTO" id="categoryForm">
<form:errors path="*" />
<td>Title</td>
<td><form:input path="title" /></td>
<table>
<thead>
<tr>
<th>Category</th>
<th></th>
</tr>
</thead>
<c:forEach items="${articleDTO.categories}" var="Category"
varStatus="i" begin="0">
<td><form:select path="categories[${i.index}]">
<form:option value="NONE" label="--- Select ---" />
<form:options items="${categoryList}" />
</form:select></td>
</c:forEach>
</table>
<td colspan="3"><input type="submit" value="GG" /></td>
</form:form>
Conroller:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView getAdminPage() {
return new ModelAndView("admin_page").addObject("categoryList",
categoryService.getAllCategories()).addObject("articleDTO", new ArticleDTO());
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView ondas(ArticleDTO articleDTO) {
return new ModelAndView("done").addObject("article", articleDTO);
}
DTO:
private String title;
#NotEmpty
#Range(min = 100, max = 6000)
private String description;
#NotEmpty
private List<Category> categories = new ArrayList<Category>();
public ArticleDTO() {
Category category = new Category();
category.setTitle("news");
category.setId(1L);
Category category1 = new Category();
category1.setTitle("news1");
category1.setId(2L);
categories.add(category1);
categories.add(category);
}
I'm try to get from user list of article categories
I'm almost sure your POST is having validation issues... :)
Try to change your method to something like this.
#RequestMapping(method = RequestMethod.POST)
public ModelAndView ondas(ArticleDTO articleDTO, BindingResult result) {
if (result.hasErrors()) {
return new ModelAndView("admin_page").addObject("categoryList",
categoryService.getAllCategories()).addObject("articleDTO", new ArticleDTO());
}
return new ModelAndView("done").addObject("article", articleDTO);
}

Categories

Resources