Self-Incremental ID for saving in MVC Springframework - java

In my Maven-based MVC Springframework project, I have a class named Rentals, which I've mapped to a PostgreSQL table of the same name. In it, I've configured the Id to be a Serial int, which increments automatically for every new entry.
My question is: How do I make it so that Spring knows that if it saves a new Rental object, it saves it with its predestined id?
Here is my Rentals java:
#Entity
#Table(name = "rentals")
public class Rentals implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id")
#SequenceGenerator(name="rentals_id_seq", sequenceName="rentals_id_seq", allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="rentals_id_seq")
private int id;
// Other column data
// Empty, only id, full and all except id constructors
// Getters and setters, hashcode, equals and tostring
}
My DAO implementation is very simple, which implements from a simple DAO interface and uses a private SessionFactory sessionFactory to realize the CRUD operations.
My Service implementation is also very simple, which implements a Service interface (which is an exact copy of the DAO interface) and uses a private MurDao murDao to realize the CRUDs.
This is my Controller:
#Controller
public class MurController {
private MurService murService;
#Autowired(required = true)
#Qualifier(value = "murService")
public void setMurService(MurService murService) {
this.murService = murService;
}
// other CRUDs for other entities
#RequestMapping(value = "/rentals", method = RequestMethod.GET)
public String listRentals(Model model) {
model.addAttribute("rental", new Rentals());
model.addAttribute("listUsers", this.murService.getUsers());
model.addAttribute("listMovies", this.murService.getMovies());
model.addAttribute("listRentals", this.murService.getRentals());
return "rental";
}
#RequestMapping(value = "/rentals/add", method = RequestMethod.POST)
public String addRental(#ModelAttribute("rental") Rentals rnt) {
try {
this.murService.getRental(rnt.getId());
this.murService.updateRental(rnt);
} catch (ObjectNotFoundException ex) {
this.murService.addRental(rnt);
}
return "redirect:/rentals";
}
#RequestMapping("/rentals/remove/{id}")
public String removeRental(#PathVariable("id") int id) {
this.murService.deleteRental(id);
return "redirect:/rentals";
}
#RequestMapping("/rentals/edit/{id}")
public String editRental(#PathVariable("id") int id, Model model) {
model.addAttribute("rental", this.murService.getRental(id));
model.addAttribute("listRentals", this.murService.getRentals());
return "rental";
}
}
Finally, the .jsp:
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%# taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%# taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%# page session="false" %>
<html><head><title>Rental Page</title><style type="text/css">.tg {border-collapse:collapse;border-spacing:0;border-color:#ccc;}.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#ccc;color:#333;background-color:#fff;}.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#ccc;color:#333;background-color:#f0f0f0;}.tg .tg-4eph{background-color:#f9f9f9}</style></head>
<body>
<c:url var="addAction" value="/rentals/add" ></c:url>
<form:form action="${addAction}" modelAttribute="rental">
<table>
<tr><td>
<form:label path="iduser">
<spring:message text="ID User"/>
</form:label></td><td>
<form:select path="iduser">
<form:options items="${listUsers}" itemValue="id" itemLabel="fullName"/>
</form:select>
</td></tr><tr><td>
<form:label path="idmovie">
<spring:message text="ID Movie"/>
</form:label></td><td>
<form:select path="idmovie">
<form:options items="${listMovies}" itemValue="id" itemLabel="title"/>
</form:select>
</td></tr><tr><td>
<form:label path="dateof">
<spring:message text="Date Of"/>
</form:label></td><td>
<form:input path="dateof" cssClass="form-control"/></td><td>
<spring:message text="Please use dd-mm-yyyy" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="<spring:message text="Add Rental"/>" />
</td>
</tr>
</table>
</form:form> <br> <h3>Rentals List</h3>
<c:if test="${!empty listRentals}"><table class="tg"><tr>
<th>ID User</th>
<th>ID Movie</th>
<th>Date Of</th>
<th width="60">Delete</th>
</tr><c:forEach items="${listRentals}" var="rental"><tr>
<td>${rental.iduser}</td>
<td>${rental.idmovie}</td>
<td>${rental.dateof}</td>
<td><a href="<c:url value='/rentals/remove/${rental.id}'/>" >Delete</a></td>
</tr>
</c:forEach>
</table></c:if></body></html>
What is currently happening is that, whenever I set some information in the "add new rental" button, the .jsp sends through a new Rental Object, but it has an id of 0. I don't want this, I want to create a new Rental with an id of Null or something like that so that spring knows that it has to wait to know what number of id to give it given it is auto-generated and self-incremental.
How do I approach this? Why does the jsp sends through an object with id 0? Any and all help is appreciated!

As Alan Hay commented, an int primitive cannot be null. And it was exactly what I needed. Just after I switched all ints that deal with my Rental's id, this happened.
Información: Rentals{id=null, iduser=testUser01, idmovie=testMovie01, dateof=Tue Jan 01 00:01:00 CST 2019}
Información: Hibernate: select nextval ('rentals_id_seq')
Información: Hibernate: insert into rentals (dateof, iduser, idmovie, id) values (?, ?, ?, ?)
And it was saved to my database. Thanks a lot, Alan Hay!

Related

Values not appeared in drop down

there is an issue in my web application, the below code i wrote for entire application and it is working fine..but not in this case.
I am using the correct variables name in JSTL, my query is also running fine and produces the required result that i want, but still those values didn't appeared in the drop down .. i am even not able to figure it out
can anybody help me to sort out this
<td>
<span id="store_${i}"></span>
<f:select class="form-control" path="boqList[${i}].organizationCode" id="storeId${i}" onchange="chekeAvailibiltyAtStore(this.value,'${b.itemCode}','${b.itemUnit}','${i}')" required="true">
<f:option value="">Select Area Store</f:option>
<c:forEach items="${areaStors}" var="as" >
<f:option value="${as.organizationCode}">${as.organizationName}</f:option>
</c:forEach>
</f:select>
</td>
Inside controller
mav.addObject("areaStors", areaStoreDAO.findAll());
Inside Service (Query working Fine)
public List<ErpAreaStore> findAll() {
String query = "SELECT ORGANIZATION_CODE "
+ " , ORGANIZATION_NAME "
+ " FROM XXAP_AREA_STORE "
+ " ORDER BY ORGANIZATION_CODE ASC ";
MapSqlParameterSource param = new MapSqlParameterSource();
List<ErpAreaStore> inventoryOnhands = getNamedParameterJdbcTemplate().query(query, param, new RowMapper<ErpAreaStore>() {
#Override
public ErpAreaStore mapRow(ResultSet rs, int rowNo) throws SQLException {
ErpAreaStore areaStore = new ErpAreaStore();
areaStore.setOrganizationCode(rs.getInt("ORGANIZATION_CODE"));
areaStore.setOrganizationName(rs.getString("ORGANIZATION_NAME"));
return areaStore;
}
});
return inventoryOnhands;
}
POJO
public class ErpAreaStore implements java.io.Serializable {
private int organizationCode;
private String organizationName;
public int getOrganizationCode() {
return organizationCode;
}
public void setOrganizationCode(int organizationCode) {
this.organizationCode = organizationCode;
}
public String getOrganizationName() {
return organizationName;
}
public void setOrganizationName(String organizationName) {
this.organizationName = organizationName;
}
}
see the below screenshot
Add <jsp:useBean> tag at the beginning of your JSP. It's a straightforward test that the areaStors list is present. Example:
<%#taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%#taglib prefix="f" uri="http://www.springframework.org/tags/form" %>
<jsp:useBean id="areaStors" scope="request" type="java.util.List"/>
<!-- ... -->
<td>
<span id="store_${i}"></span>
<f:select class="form-control" path="boqList[${i}].organizationCode" id="storeId${i}" onchange="chekeAvailibiltyAtStore(this.value,'${b.itemCode}','${b.itemUnit}','${i}')" required="true">
<f:option value="">Select Area Store</f:option>
<c:forEach items="${areaStors}" var="as" >
<f:option value="${as.organizationCode}">${as.organizationName}</f:option>
</c:forEach>
</f:select>
</td>

ComboBox from a List or a Map Collection

EDIT:
I want to show a spring-mvc dropdown box out of a object list which is a property from another class.
I did this after searching:
<sf:form action="${pageContext.request.contextPath}/venta/save"
method="post" commandName="venta">
<table>
<tr>
<td>Número de factura</td>
<td><sf:input path="numero_factura" type="text" /></td>
<td><sf:errors path="numero_factura" cssclass="error" /></td>
</tr>
<tr>
<td>Producto:</td>
<td><sf:select path="${producto.nombreProducto}">
<sf:option value="" label="...." />
<sf:options items="${productos}" />
</sf:select></td>
<td><sf:errors path="${producto.nombreProducto}"
cssclass="error" cssStyle="color: #ff0000;" /></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Guardar cambios"></td>
</tr>
<tr>
</table>
</sf:form>
The class I'm mapping is this:
Venta.class
#Entity
public class Venta implements Serializable {
#Id
#NotNull
#Digits(integer=12, fraction = 0)
private Integer numero_factura;
#LazyCollection(LazyCollectionOption.FALSE)
#ManyToMany
#NotEmpty
private List<Producto> productos = new ArrayList<Producto>();
private Date fechaDeIngreso;
#ManyToOne(cascade = CascadeType.ALL)
private Empleado empleado;
public Venta() {
}
...
}
And this is the controller:
#Controller
public class VentaController {
#Autowired
public IServiceProducto serviceProducto;
#Autowired
public IServiceVenta serviceVenta;
#Autowired
public IServiceEmpleado serviceEmpleado;
#RequestMapping("/venta")
public String showVenta(Model model, HttpSession session) {
// Model es una interfaz que nos permite definir atributos en el modelo
init(model);
return "venta";
}
#RequestMapping(value = "/venta/save", method = RequestMethod.POST)
public String handleVenta(#Valid #ModelAttribute("venta") Venta ventaForm, BindingResult result, Model model,
HttpSession session, RedirectAttributes ra) {
try {
if (result.hasErrors()) {
model.addAttribute("productos", serviceProducto.getAll());
return "venta";
}
Empleado empleado = (Empleado) session.getAttribute("empleado");
empleado.setVenta(ventaForm);
serviceVenta.exist(ventaForm);
serviceEmpleado.addChild(empleado, ventaForm);
ra.addFlashAttribute("resultado", "La venta fué agregada exitosamente");
return "redirect:/venta";
} catch (ServicioException e) {
ra.addFlashAttribute("resultado", e.getMessage());
return "redirect:/venta";
}
}
public void init(Model model){
Venta venta = new Venta();
Producto producto = new Producto();
model.addAttribute("venta", venta);
model.addAttribute("producto", producto);
model.addAttribute("productos", serviceProducto.getAll());
}
}
It gives me the view I was looking for, but this code is doing nothing when it comes to select an item from the dropdown box and hitting the submit button. It gives me no error and stay static in http://localhost:8585/electronicaDonPepe/venta/save
Please please help me, I'm almost there !
OLD VERSION:
I want to show a combobox in jsp from a list. It gives me a dropdownlist which I don't want.
I made a map with the list and it gives me the same result.
Venta.jsp
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<sf:form action="${pageContext.request.contextPath}/venta/save"
<td>Producto</td>
<td>
<sf:select path="productos">
<sf:option label="---select---" value="NONE"/>
<sf:options items="${productos}"/>
</sf:select>
</td>
<td><sf:errors path="productos" cssClass="error"/> </td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Guardar cambios"></td>
</tr>
</table>
</sf:form>
<c:out value="${resultado}"></c:out>
</body>
</html>
Necessary VentaController.java code to understand
#Controller
public class VentaController {
#Autowired
public IServiceProducto serviceProducto;
#Autowired
public IServiceVenta serviceVenta;
#Autowired
public IServiceEmpleado serviceEmpleado;
#RequestMapping("/venta")
public String showVenta(Model model, HttpSession session) {
// Model es una interfaz que nos permite definir atributos en el modelo
Venta venta = new Venta();
model.addAttribute("venta", venta);
List<Producto> listaProductos = serviceProducto.getAll();
Map<String, String> mapaProductos = new LinkedHashMap<String, String>();
for (Producto producto : listaProductos) {
mapaProductos.put(producto.getNombreProducto(), producto.getNombreProducto());
}
model.addAttribute("productos", mapaProductos);
return "venta";
}
The result is a list with multiple selections. I'm aware that if I use the property multiple=false the multiple selection will be off but it still work in my case.
DropdownList with multiple selection
Still, I want a combobox function and not a dropdownlist without multiple selection.
This is my idea of what I want:
select with options
I have this approach:
<sf:form action="${pageContext.request.contextPath}/venta/save"
method="post" commandName="ventaDTO">
<table>
<tr>
<td>Número de factura</td>
<td><sf:input path="numero_factura" type="text" />
<td><sf:errors path="numero_factura" cssclass="error" /></td>
</tr>
<tr>
<td>Producto:</td>
<td><sf:select path="nombreProducto">
<sf:option value="" label="...." />
<sf:options items="${productos}" />
</sf:select> <sf:errors path="nombreProducto" cssclass="error"
cssStyle="color: #ff0000;" /></td>
<td>
<td><sf:input path="cantidadProductos" type="text" />
<sf:errors
path="cantidadProductos" cssclass="error"
cssStyle="color: #ff0000;" /></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Guardar cambios"></td>
</tr>
</table>
</sf:form>
but still can't add another whole table in order to add a particular product in quantities. I was thinking about using angularjs but I'm new to that technology. I'll be very thankful for your help.

How to bind an object list with thymeleaf?

I am having a lot of difficulty with POSTing back a form to the controller, which should contain simply an arraylist of objects that the user may edit.
The form loads up correctly, but when it's posted, it never seems to actually post anything.
Here is my form:
<form action="#" th:action="#{/query/submitQuery}" th:object="${clientList}" method="post">
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>Select</th>
<th>Client ID</th>
<th>IP Addresss</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox" th:checked="${currentClient.selected}" /></td>
<td th:text="${currentClient.getClientID()}" ></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}" ></td>
</tr>
</tbody>
</table>
<button type="submit" value="submit" class="btn btn-success">Submit</button>
</form>
Above works fine, it loads up the list correctly. However, when I POST, it returns a empty object (of size 0). I believe this is due to the lack of th:field, but anyway here is controller POST method:
...
private List<ClientWithSelection> allClientsWithSelection = new ArrayList<ClientWithSelection>();
//GET method
...
model.addAttribute("clientList", allClientsWithSelection)
....
//POST method
#RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(#ModelAttribute(value="clientList") ArrayList clientList, Model model){
//clientList== 0 in size
...
}
I have tried adding a th:field but regardless of what I do, it causes an exception.
I've tried:
...
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox" th:checked="${currentClient.selected}" th:field="*{}" /></td>
<td th th:field="*{currentClient.selected}" ></td>
...
I cannot access currentClient (compile error), I can't even select clientList, it gives me options like get(), add(), clearAll() etc, so it things it should have an array, however, I cannot pass in an array.
I've also tried using something like th:field=${}, this causes runtime exception
I've tried
th:field = "*{clientList[__currentClient.clientID__]}"
but also compile error.
Any ideas?
UPDATE 1:
Tobias suggested that I need to wrap my list in a wraapper. So that's what I did:
ClientWithSelectionWrapper:
public class ClientWithSelectionListWrapper {
private ArrayList<ClientWithSelection> clientList;
public List<ClientWithSelection> getClientList(){
return clientList;
}
public void setClientList(ArrayList<ClientWithSelection> clients){
this.clientList = clients;
}
}
My page:
<form action="#" th:action="#{/query/submitQuery}" th:object="${wrapper}" method="post">
....
<tr th:each="currentClient, stat : ${wrapper.clientList}">
<td th:text="${stat}"></td>
<td>
<input type="checkbox"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:checked="${currentClient.selected}" />
</td>
<td th:text="${currentClient.getClientID()}" ></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}" ></td>
</tr>
Above loads fine:
Then my controller:
#RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(#ModelAttribute ClientWithSelectionListWrapper wrapper, Model model){
...
}
The page loads correctly, the data is displayed as expected. If I post the form without any selection I get this:
org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'clientList' cannot be found on null
Not sure why it's complaining
(In the GET Method it has: model.addAttribute("wrapper", wrapper);)
If I then make a selection, i.e. tick the first entry:
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='clientWithSelectionListWrapper'. Error count: 1
I'm guessing my POST controller is not getting the clientWithSelectionListWrapper. Not sure why, since I have set the wrapper object to be posted back via the th:object="wrapper" in the FORM header.
UPDATE 2:
I've made some progress! Finally the submitted form is being picked up by the POST method in controller. However, all the properties appear to be null, except for whether the item has been ticked or not. I've made various changes, this is how it is looking:
<form action="#" th:action="#{/query/submitQuery}" th:object="${wrapper}" method="post">
....
<tr th:each="currentClient, stat : ${clientList}">
<td th:text="${stat}"></td>
<td>
<input type="checkbox"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:checked="${currentClient.selected}"
th:field="*{clientList[__${stat.index}__].selected}">
</td>
<td th:text="${currentClient.getClientID()}"
th:field="*{clientList[__${stat.index}__].clientID}"
th:value="${currentClient.getClientID()}"
></td>
<td th:text="${currentClient.getIpAddress()}"
th:field="*{clientList[__${stat.index}__].ipAddress}"
th:value="${currentClient.getIpAddress()}"
></td>
<td th:text="${currentClient.getDescription()}"
th:field="*{clientList[__${stat.index}__].description}"
th:value="${currentClient.getDescription()}"
></td>
</tr>
I also added a default param-less constructor to my wrapper class and added a bindingResult param to POST method (not sure if needed).
public String processQuery(#ModelAttribute ClientWithSelectionListWrapper wrapper, BindingResult bindingResult, Model model)
So when an object is being posted, this is how it is looking:
Of course, the systemInfo is supposed to be null (at this stage), but the clientID is always 0, and ipAddress/Description always null. The selected boolean is correct though for all properties. I'm sure I've made a mistake on one of the properties somewhere. Back to investigation.
UPDATE 3:
Ok I've managed to fill up all the values correctly! But I had to change my td to include an <input /> which is not what I wanted... Nonetheless, the values are populating correctly, suggesting spring looks for an input tag perhaps for data mapping?
Here is an example of how I changed the clientID table data:
<td>
<input type="text" readonly="readonly"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:field="*{clientList[__${stat.index}__].clientID}"
/>
</td>
Now I need to figure out how to display it as plain data, ideally without any presence of an input box...
You need a wrapper object to hold the submited data, like this one:
public class ClientForm {
private ArrayList<String> clientList;
public ArrayList<String> getClientList() {
return clientList;
}
public void setClientList(ArrayList<String> clientList) {
this.clientList = clientList;
}
}
and use it as the #ModelAttribute in your processQuery method:
#RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(#ModelAttribute ClientForm form, Model model){
System.out.println(form.getClientList());
}
Moreover, the input element needs a name and a value. If you directly build the html, then take into account that the name must be clientList[i], where i is the position of the item in the list:
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:checked="${currentClient.selected}" />
</td>
<td th:text="${currentClient.getClientID()}" ></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}" ></td>
</tr>
Note that clientList can contain null at
intermediate positions. Per example, if posted data is:
clientList[1] = 'B'
clientList[3] = 'D'
the resulting ArrayList will be: [null, B, null, D]
UPDATE 1:
In my exmple above, ClientForm is a wrapper for List<String>. But in your case ClientWithSelectionListWrapper contains ArrayList<ClientWithSelection>. Therefor clientList[1] should be clientList[1].clientID and so on with the other properties you want to sent back:
<tr th:each="currentClient, stat : ${wrapper.clientList}">
<td><input type="checkbox" th:name="|clientList[${stat.index}].clientID|"
th:value="${currentClient.getClientID()}" th:checked="${currentClient.selected}" /></td>
<td th:text="${currentClient.getClientID()}"></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}"></td>
</tr>
I've built a little demo, so you can test it:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
ClientWithSelection.java
public class ClientWithSelection {
private Boolean selected;
private String clientID;
private String ipAddress;
private String description;
public ClientWithSelection() {
}
public ClientWithSelection(Boolean selected, String clientID, String ipAddress, String description) {
super();
this.selected = selected;
this.clientID = clientID;
this.ipAddress = ipAddress;
this.description = description;
}
/* Getters and setters ... */
}
ClientWithSelectionListWrapper.java
public class ClientWithSelectionListWrapper {
private ArrayList<ClientWithSelection> clientList;
public ArrayList<ClientWithSelection> getClientList() {
return clientList;
}
public void setClientList(ArrayList<ClientWithSelection> clients) {
this.clientList = clients;
}
}
TestController.java
#Controller
class TestController {
private ArrayList<ClientWithSelection> allClientsWithSelection = new ArrayList<ClientWithSelection>();
public TestController() {
/* Dummy data */
allClientsWithSelection.add(new ClientWithSelection(false, "1", "192.168.0.10", "Client A"));
allClientsWithSelection.add(new ClientWithSelection(false, "2", "192.168.0.11", "Client B"));
allClientsWithSelection.add(new ClientWithSelection(false, "3", "192.168.0.12", "Client C"));
allClientsWithSelection.add(new ClientWithSelection(false, "4", "192.168.0.13", "Client D"));
}
#RequestMapping("/")
String index(Model model) {
ClientWithSelectionListWrapper wrapper = new ClientWithSelectionListWrapper();
wrapper.setClientList(allClientsWithSelection);
model.addAttribute("wrapper", wrapper);
return "test";
}
#RequestMapping(value = "/query/submitQuery", method = RequestMethod.POST)
public String processQuery(#ModelAttribute ClientWithSelectionListWrapper wrapper, Model model) {
System.out.println(wrapper.getClientList() != null ? wrapper.getClientList().size() : "null list");
System.out.println("--");
model.addAttribute("wrapper", wrapper);
return "test";
}
}
test.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<form action="#" th:action="#{/query/submitQuery}" th:object="${wrapper}" method="post">
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>Select</th>
<th>Client ID</th>
<th>IP Addresss</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr th:each="currentClient, stat : ${wrapper.clientList}">
<td><input type="checkbox" th:name="|clientList[${stat.index}].clientID|"
th:value="${currentClient.getClientID()}" th:checked="${currentClient.selected}" /></td>
<td th:text="${currentClient.getClientID()}"></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}"></td>
</tr>
</tbody>
</table>
<button type="submit" value="submit" class="btn btn-success">Submit</button>
</form>
</body>
</html>
UPDATE 1.B:
Below is the same example using th:field and sending back all other attributes as hidden values.
<tbody>
<tr th:each="currentClient, stat : *{clientList}">
<td>
<input type="checkbox" th:field="*{clientList[__${stat.index}__].selected}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].clientID}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].ipAddress}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].description}" />
</td>
<td th:text="${currentClient.getClientID()}"></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}"></td>
</tr>
</tbody>
When you want to select objects in thymeleaf, you dont actually need to create a wrapper for the purpose of storing a boolean select field. Using dynamic fields as per the thymeleaf guide with syntax th:field="*{rows[__${rowStat.index}__].variety}" is good for when you want to access an already existing set of objects in a collection. Its not really designed for doing selections by using wrapper objects IMO as it creates unnecessary boilerplate code and is sort of a hack.
Consider this simple example, a Person can select Drinks they like. Note: Constructors, Getters and setters are omitted for clarity. Also, these objects are normally stored in a database but I am using in memory arrays to explain the concept.
public class Person {
private Long id;
private List<Drink> drinks;
}
public class Drink {
private Long id;
private String name;
}
Spring controllers
The main thing here is that we are storing the Person in the Model so we can bind it to the form within th:object.
Secondly, the selectableDrinks are the drinks a person can select on the UI.
#GetMapping("/drinks")
public String getDrinks(Model model) {
Person person = new Person(30L);
// ud normally get these from the database.
List<Drink> selectableDrinks = Arrays.asList(
new Drink(1L, "coke"),
new Drink(2L, "fanta"),
new Drink(3L, "sprite")
);
model.addAttribute("person", person);
model.addAttribute("selectableDrinks", selectableDrinks);
return "templates/drinks";
}
#PostMapping("/drinks")
public String postDrinks(#ModelAttribute("person") Person person) {
// person.drinks will contain only the selected drinks
System.out.println(person);
return "templates/drinks";
}
Template code
Pay close attention to the li loop and how selectableDrinks is used to get all possible drinks that can be selected.
The checkbox th:field really expands to person.drinks since th:object is bound to Person and *{drinks} simply is the shortcut to referring to a property on the Person object. You can think of this as just telling spring/thymeleaf that any selected Drinks are going to be put into the ArrayList at location person.drinks.
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" >
<body>
<div class="ui top attached segment">
<div class="ui top attached label">Drink demo</div>
<form class="ui form" th:action="#{/drinks}" method="post" th:object="${person}">
<ul>
<li th:each="drink : ${selectableDrinks}">
<div class="ui checkbox">
<input type="checkbox" th:field="*{drinks}" th:value="${drink.id}">
<label th:text="${drink.name}"></label>
</div>
</li>
</ul>
<div class="field">
<button class="ui button" type="submit">Submit</button>
</div>
</form>
</div>
</body>
</html>
Any way...the secret sauce is using th:value=${drinks.id}. This relies on spring converters. When the form is posted, spring will try recreate a Person and to do this it needs to know how to convert any selected drink.id strings into the actual Drink type. Note: If you did th:value${drinks} the value key in the checkbox html would be the toString() representation of a Drink which is not what you want, hence need to use the id!. If you are following along, all you need to do is create your own converter if one isn't already created.
Without a converter you will receive an error like
Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'drinks'
You can turn on logging in application.properties to see the errors in detail.
logging.level.org.springframework.web=TRACE
This just means spring doesn't know how to convert a string id representing a drink.id into a Drink. The below is an example of a Converter that fixes this issue. Normally you would inject a repository in get access the database.
#Component
public class DrinkConverter implements Converter<String, Drink> {
#Override
public Drink convert(String id) {
System.out.println("Trying to convert id=" + id + " into a drink");
int parsedId = Integer.parseInt(id);
List<Drink> selectableDrinks = Arrays.asList(
new Drink(1L, "coke"),
new Drink(2L, "fanta"),
new Drink(3L, "sprite")
);
int index = parsedId - 1;
return selectableDrinks.get(index);
}
}
If an entity has a corresponding spring data repository, spring automatically creates the converters and will handle fetching the entity when an id is provided (string id seems to be fine too so spring does some additional conversions there by the looks). This is really cool but can be confusing to understand at first.

how to bind data to list in spring form

I have a spring form with having backing object for it. The form is like this-
<sf:form cssClass="form-horizontal" commandName="campaignModel" method="post">
<sf:input path="campaign.name" class="form-control" />
<sf:input path="landingPageModels.landingPage.url" class="form-control" />
</sf:form>
Model class(Form backing Object) -
CampaignModel.java
public class CampaignModel {
private Campaign campaign = new CampaignImpl();
private List<LandingPageModel> landingPageModels = new Arraylist<LandingPageModel>;
public Campaign getCampaign() {
return campaign;
}
public void setCampaign(Campaign campaign) {
this.campaign = campaign;
}
public List<LandingPageModel> getLandingPageModels() {
return landingPageModels;
}
public void setLandingPageModels(List<LandingPageModel> landingPageModels) {
this.landingPageModels = landingPageModels;
}
LandingPageModel.java is -
public class LandingPageModel {
private LandingPage landingPage = new LandingPageImpl();
private List<LandingPageParameterImpl> landingPageParameters = new ArrayList<LandingPageParameterImpl>();
public LandingPage getLandingPage() {
return landingPage;
}
public void setLandingPage(LandingPage landingPage) {
this.landingPage = landingPage;
}
public List<LandingPageParameterImpl> getLandingPageParameters() {
return landingPageParameters;
}
public void setLandingPageParameters(List<LandingPageParameterImpl> landingPageParameters) {
this.landingPageParameters = landingPageParameters;
}
}
LandingPage.java is -
public class LandingPageImpl extends EntityImpl implements LandingPage {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
} }
So i want that i can insert many objects of landingPage (having their own url property) in landingPageModels list. That means i can have mulitple input tag having url property like this -
<sf:input path="landingPageModels.landingPage.url" class="form-control" />
<sf:input path="landingPageModels.landingPage.url" class="form-control" />
<sf:input path="landingPageModels.landingPage.url" class="form-control" />
But when executing this code, spring gives me error that landingPage property of landingPageModels has not getter setter method. How to solve it and how to take multiple value like this ?
In order to bind a list model property to multiple input fields, you need this in the rendered form:
<input type="text" name="landingPageModels[0].landingPage.url" class="form-control" />
<input type="text" name="landingPageModels[1].landingPage.url" class="form-control" />
<input type="text" name="landingPageModels[2].landingPage.url" class="form-control" />
Which is accomplished by:
<c:forEach items="${campaignModel.landingPageModels}" varStatus="s">
<sf:input path="landingPageModels[${s.index}].landingPage.url" class="form-control" />
</c:forEach>
The error you are getting is correct as landingPageModels is a list.
You would need to use index access like this landingPageModels[0].landingPage.url.
If you have dynamic number of input/url, then you can use <c:forEach> to create dynamic input variable names
<c:forEach items="${campaignModel.landingPageModels}" var="landingPage">
<sf:input path="landingPage.url" class="form- control" />
</c:forEach>
Will the above not works for you to view data? To get them in controller you may have to use dynamic table row concept in HTML or for each single LandingPage entry add to form bean object by clicking add button and render back.
In my case Person command object having List<Token> property, in order to bind the list of tokens we have designed page as attached screen shot below, clicking on Add button hits the controller and add the each token List<Token> and render back to same view and displays added token in list view, it facilitates to add multiple token for Person.
I dont know how to do it with spring form lib input but if you want to bind using simple html input than you can bind list like this
Simple.jsp
<%#taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%#taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
</head>
<body>
<form:form method="post" action="save.html" modelAttribute="contactForm">
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
<tr>
<td><input name="contacts[0].firstname" /></td>
<td><input name="contacts[0].lastname" /></td>
</tr>
<tr>
<td><input name="contacts[1].firstname" /></td>
<td><input name="contacts[1].lastname" /></td>
</tr>
</table>
<br/>
<input type="submit" value="Save" />
</form:form>
</body>
</html>
#controller :
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView save(#ModelAttribute("contactForm") ContactForm contactForm) {
List<Contact> contacts = contactForm.getContacts();
if(null != contacts && contacts.size() > 0) {
ContactController.contacts = contacts;
for (Contact contact : contacts) {
System.out.printf("%s \t %s \n", contact.getFirstname(), contact.getLastname());
}
}
return new ModelAndView("show_contact", "contactForm", contactForm);
}
ContactForm.java
import java.util.List;
public class ContactForm {
private List<Contact> contacts;
public List<Contact> getContacts() {
return contacts;
}
public void setContacts(List<Contact> contacts) {
this.contacts = contacts;
}
}
Contact.java
public class Contact {
private String firstname;
private String lastname;
private String email;
private String phone;
public Contact() {
}
//getters and setters
}

issue with binding an arraylist in a class with checkboxes in spring

I am trying to get courseList from a JSP page by binding with the student object in Spring. Below is my Student class. However even if I check the checkboxes, the size of the ArrayList is 0. Can you please tell me why this is happening?
Thanks.
P.S.
This is the error I am getting:
[Field error in object 'student' on field 'courseList': rejected value
[mad.nurseryapplication.model.Course#1c92233b,mad.nurseryapplication.model.Course#3e470524];
codes [typeMismatch.student.courseList,typeMismatch.courseList,typeMismatch.java.util.List,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.courseList,courseList]; arguments []; default message [courseList]];
default message [Failed to convert property value of type 'java.lang.String[]' to required type 'java.util.List' for property 'courseList'
This is a part of student class. it has all the getters and setters correctly.
#Table(name="student")
public class Student {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
private String grade;
#Column(name="home_number")
private String homeNumber;
#Column(name="home_address")
#Lob
private String homeAddress;
#Column(name="date_of_registration")
#Temporal(TemporalType.DATE)
private Date dateOfRegistration;
#ManyToMany
#JoinTable(name="course_student",joinColumns=#JoinColumn(name="student_id"),inverseJoinColumns=#JoinColumn(name="course_id"))
private List<Course> courseList = new ArrayList<Course>();
This is my jsp page code.
<%#taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%#taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Food List</title>
</head>
<body>
${message}
<br /> ${operation}
<br /> Please Select Subjects for the Student ${student.firstName}
<br /> Student Id is ${student.id}
<br />
<form:form method="post" action="${action}" modelAttribute="student">
<table>
<tr>
<th>enroll</th>
<th>Subject</th>
</tr>
<form:hidden path="id" value="${student.id}"/>
<c:forEach items="${avacourses}" var="course" varStatus="status">
<tr>
<td><form:checkbox path="courseList" value="${course}" /></td>
<td>${course.courseName}</td>
</tr>
</c:forEach>
</table>
<br />
<input type="submit" value="enroll" />
</form:form>
</body>
</html>
Below is my controller code that handle it.
#RequestMapping("/addstudent")
public ModelAndView addStudent(#ModelAttribute("student")Student student,BindingResult result){
student = studentService.addStudent(student);
Collection<Course> availableCourses = courseService.getAvailableCourses(student.getGrade());
ModelAndView mav = new ModelAndView();
mav.setViewName("/course/courselist");
mav.addObject("operation", "Enroll the courses to the student");
mav.addObject("action", "enroll.html");
mav.addObject("student", student);
mav.addObject("avacourses", availableCourses);
return mav;
}
#RequestMapping("/enroll")
public ModelAndView ModelAndView(#ModelAttribute("student")Student student, BindingResult result){
System.out.println(student.getId());
System.out.println(student.getCourseList().size());
ModelAndView mav = new ModelAndView("/student/student","command",new Student());
mav.addObject("operation", "Add a new Student");
return mav;
}
Have you tried using the <form:checkboxes> tag instead?
<form:checkboxes items="${avacourses}" path="courseList" />
Also, take a look at: Spring MVC usage of form:checkbox to bind data and Spring Binding List<Object> to Form:checkboxes.
EDIT: just realised it will break your labels - being populated by the forEach loop.

Categories

Resources