how to bind data to list in spring form - java

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
}

Related

Pass an instance of an object in each loop from thymeleaf html file to Spring controller

I have a problem with passing an instance of "threads" in each loop from thymeleaf to Spring controller using a submit button. I'm trying to solve this by using the annotation #ModelAttribute, but one more instance of MessageThread is creating.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" xmlns:form="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Форум</title>
</head>
<body>
<form method="post">
<input type="text" name="header">
<input type="text" name="text">
<button name="newThread" type="submit">Создать тред</button>
</form>
<table>
<thead>
<tr>
<th> Тред </th>
<th> ОП пост </th>
</tr>
</thead>
<tbody>
<tr th:if="${threads.isEmpty()}">
<td colspan="2"> Нет доступных тредов </td>
</tr>
<div>
<th:block th:each="thread : ${threads}">
<td th:type="id"><span th:text="${thread.getId()}"></span></td>
<td><span th:text="${thread.getHeader()}"> Title </span></td>
<td><span th:text="${thread.getText()}"> Title </span></td>
<form th:object="${thread}" th:method="post">
<td><button name="inThread" type="submit">В тред</button></td>
</form>
</th:block>
</div>
</tbody>
</table>
</body>
</html>
I can't find a way to pass an instance of "threads" from thymeleaf. All that I want is to press Submit button and pass the ${thread} to "toThread" method.
My controller:
#Controller
public class ThreadController {
private final MessageService messageService;
#Autowired
public ThreadController(MessageService messageService) {
this.messageService = messageService;
}
#GetMapping("/")
public String showThreads(Model model)
{
model.addAttribute("threads", messageService.getThreads());
return "threads-view";
}
#PostMapping(value = "/", params = "newThread")
public String addThread(Model model,
#RequestParam("header") String header,
#RequestParam("text") String text)
{
model.addAttribute("threads", messageService.getThreads());
messageService.addThread(header, text);
return "redirect:/";
}
#PostMapping(value = "/", params = "inThread")
public String toThread(#ModelAttribute("thread") MessageThread thread) {
System.out.println(thread.getId() + " " + thread.getHeader());
return "redirect:/thread:";
}
}
MessageThread class:
package com.project.imageboard.model;
import java.util.ArrayList;
import java.util.List;
public class MessageThread {
private String header;
private String text;
private int id;
private List<Message> messages = new ArrayList<>();
public MessageThread(String header, String text) {
this.header = header;
messages.add(new Message(text));
this.text = text;
this.id = messages.get(0).getId();
}
public int getId() {
return id;
}
public String getText() {
return text;
}
public String getHeader() {
return header;
}
public List<Message> getMessages() {
return messages;
}
public void insertMessage(Message message){
messages.add(message);
}
}
I would be grateful for any help.
your controller seems to be ok, you are mapping a post request to "/" and expecting to receive a MessageThread object which has to be built from the content of the request, the problem is in the template: you are not sending the data in the request from the client to the server, so spring has no way to assign the correct values to a new instance of MessageThread.
If we take this block from your template:
<th:block th:each="thread : ${threads}">
<td th:type="id"><span th:text="${thread.getId()}"></span></td>
<td><span th:text="${thread.getHeader()}"> Title </span></td>
<td><span th:text="${thread.getText()}"> Title </span></td>
<form th:object="${thread}" th:method="post">
<td><button name="inThread" type="submit">В тред</button></td>
</form>
</th:block>
1) The form tag is missing the action attribute, try adding something like th:action="#{/}" to target your request mapping on the server side.
2) You are not sending the actual content of the thread object to the server, for this you have to add input tags with the name of the imput matching the name of the field in the MessageThread object you want to populate. Something like the following:
<input type="hidden" th:field="*{header}" />
<input type="hidden" th:field="*{text}" />
In this example th:field is creating the name and value attributes for the input tags so you don't need to do it manually.
To sum up, there is no way to pass an actual "instance" of an object from the html running in the client to the java app running on the server, you can only send data using HTTP and parse that data on the server. The ModelAttribute annotation is instructing Spring to inspect an object (MessageThread in this case) and find in the data sent through the request the matching values to populate the object.
Hope this helps.

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.

Trying to create and save list of objects in spring mvc

This is my model class
public class DynamicRow {
private String id;
private String name;
private String email;
public DynamicRow(String id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public DynamicRow() {
// TODO Auto-generated constructor stub
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "DynamicRow [id=" + id + ", name=" + name + ", email=" + email
+ "]";
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
This is the form class
public class DynamicRowForm {
private List<DynamicRow> dynamicRow = LazyList.decorate(new ArrayList<DynamicRow>(), FactoryUtils.instantiateFactory(DynamicRow.class));
public DynamicRowForm() {
}
public List<DynamicRow> getDynamicRow() {
return dynamicRow;
}
public void setDynamicRow(List<DynamicRow> dynamicRow) {
this.dynamicRow = dynamicRow;
}
}
Below is my controllers
#RequestMapping(value="/createDetails")
public String list(Model model){
DynamicRowForm dynamicRowForm = new DynamicRowForm();
model.addAttribute("DynamicRowForm",dynamicRowForm);
return "test2";
}
#RequestMapping(value="/save")
public String showList(Model model,#ModelAttribute("DynamicRowForm") DynamicRowForm dynamicRowForm) {
System.out.println(dynamicRowForm.getDynamicRow());
return "success";
}
And this is my jsp page named as test2
<%#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>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript">
var rowCount = 1;
function addMoreRows(form) {
rowCount ++;
var recRow = '<p id="rowCount'+rowCount+'"><tr><td><input name="" type="text" size="17%" maxlength="120" /></td><td><input name="" type="text" maxlength="120" style="margin: 4px 5px 0 5px;"/></td><td><input name="" type="text" maxlength="120" style="margin: 4px 10px 0 0px;"/></td></tr> Delete</p>';
$('#addedRows').append(recRow);
}
function removeRow(removeNum) {
$('#rowCount'+removeNum).remove();
}
</script>
</head>
<body>
<form:form action="save" method="GET" modelAttribute="DynamicRowForm">
<input type="button" onclick="addMoreRows(this.form);" value="AddRow">
<table rules="all" style="background:#fff;">
<tr>
<td style="font-size:14px;" >Name</td>
<td style="font-size:14px;">Email</td>
<td style="font-size:14px;">Mobile</td>
<!-- <td><span style="font:normal 12px agency, arial; color:blue; text-decoration:underline; cursor:pointer;" onclick="addMoreRows(this.form);">
Add More
</span>
</td> -->
</tr>
<tr id="rowId">
<td><form:input path="${dynamicRow.id}" type="" size="17%"/></td>
<td><form:input path="${dynamicRow.name}" type="text" /></td>
<td><form:input path="${dynamicRow.email}" type="text" /></td>
</table>
<div id="addedRows"></div>
<input type="submit" value="Save">
</form:form>
</body>
I know these questions have been asked before, but as am very new to spring mvc and learning things so am trying to get some comfort in this technology..
Anyway, what here I am trying to do is to save multiple row/list of object... but its not getting saved in the list... Please help me out on what is the mistake am doing, and correct me.
Edit
And one more thing I would like to clarify, in my first controller am creating a dynamicRowForm object and adding into model so that my jsp page can access it...and in my second controller am receiving DynamicRowForm object using #ModelAttribute with same name..but here am getting different object.why?
Your inputs need to look like this:
<form:form action="save" method="POST" modelAttribute="DynamicRowForm">
...
<td><form:input path="dynamicRow[0].id" type="text" /></td>
<td><form:input path="dynamicRow[0].name" type="text" /></td>
<td><form:input path="dynamicRow[0].email" type="text" /></td>
...
</form:form>
Which essentially states: on form submission bind the id field to the DynamicRow at index 0 in the form backing Object - DynamicRowForm.
If you do this, then the following should print the values input:
#RequestMapping(value="/save")
public String showList(#ModelAttribute DynamicRowForm dynamicRowForm) {
System.out.println(dynamicRowForm.getDynamicRow().get(0).getId());
System.out.println(dynamicRowForm.getDynamicRow().get(0).getName());
System.out.println(dynamicRowForm.getDynamicRow().get(0).getEmail());
return "success";
}
And one more thing I would like to clarify, in my first controller am
creating a dynamicRowForm object and adding into model so that my jsp
page can access it...and in my second controller am receiving
DynamicRowForm object using #ModelAttribute with same name..but here
am getting different object.why?
Because it is stateless. If you want to work with the same instance you will need to store it in the session between requests. See the following useful discussion.
http://www.intertech.com/Blog/understanding-spring-mvc-model-and-session-attributes/
You are missing indexing here as you are using collection. Try...
<form:input path="${DynamicRowForm.dynamicRow[0].id}" type="" size="17%"/>
and also in your addRow(), you have to use the dynamic index with row count. If it is not working with form:input, try simple html input type.
var count=0;
function addMoreRows(form) {
rowCount ++;
count =++count;
var recRow = '<p id="rowCount'+rowCount+'"><tr><td><input path="users['+count+'].id" id="users'+count+'.id" name="users['+count+'].id" type="" size="17%" maxlength="120" /></td><td><input path="users['+count+'].name" id="users'+count+'.name" name="users['+count+'].name" type="text" maxlength="120" style="margin: 4px 5px 0 5px;"/></td><td><input path="users['+count+'].email" id="users'+count+'.email" name="users['+count+'].email" type="text" maxlength="120" style="margin: 4px 10px 0 0px;"/></td></tr> Delete</p>';
$('#addedRows').append(recRow);
}

Spring MVC passing ArrayList back to controller

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 :)

jsp in another jsp struts 2

1st problem : i have a jsp in which there are two tabs ,for the second tab i have included the struts include tag. When i open the second tab and performs the action the second tab form values are not passing to the action.
2nd problem : in the select tag i have populate the list but it is coming as prepopulated means all the options are coming as selected="selected"
main jsp in which tabs are made
<!-- admin first tab starts here -->
<form action="saveRolesAndPermissions" method="post">
<table align="center">
<tr>
<td ><s:text name="FTID" ></s:text></td>
<td ><s:textfield id="FTID" name="ft_id">
</s:textfield></td>
<td><input type="button" value="validate" onclick="userPresent()"></td>
</tr>
</table>
<table>
<tr>
<td>First Name</td>
<td><input id="first_name" type="text" readonly onkeydown="return false"></td>
<td>Last Name</td>
<td><input id="last_name"
type="text" readonly onkeydown="return false"></td>
<td>Email-Id</td>
<td><input id="mail_id" type="text"
readonly onkeydown="return false"></td>
</tr>
</table>
<table align="center">
<tr>
<td><s:text name="ROLES"></s:text></td>
<td></td>
<td></td>
</tr>
<tr>
<td><s:text name="Available Roles"></s:text></td>
<td></td>
<td><s:text name="Assigned Roles"></s:text></td>
</tr>
<tr>
<td><s:select id="roles" name="availableRole"
multiple="true" list="availableRole"></s:select></td>
<td><input type="button" value="&gt" onclick="move_list_items('roles','assignedroles');"/><input
type="button" value="&lt" onclick="move_list_items('assignedroles','roles');"/></td>
<td><s:select id="assignedroles" name="assignedRole" multiple="true"
list="{}" >
</s:select></td>
</tr>
</table>
<br /> <br /> <br />
<table>
<tr>
<td><s:text name="Permissions"></s:text></td>
<td></td>
<td> </td>
</tr>
<tr>
<td><s:text
name="Available Permissions"></s:text></td>
<td></td>
<td><s:text name="Assigned Permissions"></s:text></td>
</tr>
<tr>
<td><s:select id="permissions" multiple="true"
name="availablePermission" list="availablePermission" headerValue=""></s:select></td>
<td><input
type="button" value="&gt" onclick="move_list_items('permissions','assignedpermissions');"/><br /> <input type="button"
value="&lt" onclick="move_list_items('assignedpermissions','permissions');" /></td>
<td><s:select id="assignedpermissions" multiple="true" name="assignedPermission"
list="{}" ></s:select></td>
</tr>
</table>
<br /> <br />
<table align="center" style="width: 25%;">
<tr>
<td><s:if test="hasActionMessages()">
<div class="welcome" style="list-style:none">
<s:actionmessage />
</div>
</s:if></td>
</tr>
</table>
<table>
<tr>
<td><s:submit
value="save"onclick="saveRole();return false;"></s:submit></td>
<td> <input type="button" name="close"
value="close" /></td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</table>
</form>
<!-- second tab for modify roles -->
<div id="content02" class="content" >
<s:include value="../asp/aspAdminModify.jsp"></s:include>
</div>
<!-- /second tab ends here -->
included jsp
<form action="grantedRolesAndPermissions" method="post">
<table>
<tr>
<td><s:text name="FTID" ></s:text></td>
<td><s:textfield id="modify_FTID" name="modifyftid">
</s:textfield></td>
<td><s:submit
value="search" onclick="search();return false;"></s:submit>
</td>
</tr>
</table>
Bean for the first tab
private String ft_id;
private ArrayList<String> availableRole;
private ArrayList<String> availablePermission;
private ArrayList<String> assignedRole;
private ArrayList<String> assignedPermission;
//getters and setters
Action for the first tab
public class ASPadmin extends ActionSupport implements ServletRequestAware, ModelDriven<ASPAdminBean>{
/**
*
*/
private static final long serialVersionUID = 1L;
private AdminASPService aspService = new AdminASPService();
private HttpServletRequest request = ServletActionContext.getRequest();
private HttpServletResponse response = ServletActionContext.getResponse();
public CMTUser cmtuser;
private ASPAdminBean aspAdminBean = new ASPAdminBean();
//getters and setters
public String populateRolesAndPermissions() throws Exception
{
/*aspAdminBean=new ASPAdminBean();*/
aspAdminBean.setAvailableRole(aspService.getRolesList());
aspAdminBean.setAvailablePermission(aspService.getPermissionsList());
return SUCCESS;
}
public String saveRolesAndPermissions() throws Exception
{
User user = CMTUser.getUser(request);
String login = user.getUserId();
String[] temp = login.split("\\.");
String abcfinal = temp[0].substring(0, 1).toUpperCase()
+ temp[0].substring(1);
String deffinal = temp[1].substring(0, 1).toUpperCase()
+ temp[1].substring(1);
String name = abcfinal + " " + deffinal;
System.out.println("name ==============>" + name);
String id = aspService.saveRolesPermissions(aspAdminBean,name);
System.out.println("id=======>"+id);
if("Y".equals(id)){
addActionMessage("Record Inserted Successfully");
populateRolesAndPermissions();
return SUCCESS;
}
else{
return ERROR;
}
}
#Override
public String execute() throws Exception {
String url = request.getRequestURL().toString();
String[] actionArray = url.split("/");
String event = null;
String forward = SUCCESS;
for (int i = 0; i < actionArray.length; i++) {
event = actionArray[i];
}
System.err.println(event);
try {
if (event.equals("aspAdmin.action")) {
populateRolesAndPermissions();
}
}catch (Exception e) {
e.printStackTrace();
}
return SUCCESS;
}
#Override
public void setServletRequest(HttpServletRequest req) {
this.request = req;
}
#Override
public ASPAdminBean getModel() {
// TODO Auto-generated method stub
return aspAdminBean;
}
}
bean for second tab
public class ASPAdminModifyBean {
private String modifyftid;
private String aspmodifyassignedRole;
private String aspmodifyassignedPermission;
private String createddate;
private String createdby;
private String updateddate;
private String updatedby;
private String username;
private ArrayList<String> modifyavailableRole;
private ArrayList<String> modifyavailablePermission;
private ArrayList<String> modifyassignedRole;
private ArrayList<String> modifyassignedPermission;
//getters and setters
action for the second tab
public class ASPadminModify extends ActionSupport implements ServletRequestAware, ModelDriven<ASPAdminModifyBean> {
private AdminASPService aspService = new AdminASPService();
private GetRolePermissionListInt[] roleVO;
private HttpServletRequest request = ServletActionContext.getRequest();
private HttpServletResponse response = ServletActionContext.getResponse();
private ASPAdminModifyBean modify = new ASPAdminModifyBean();
GetRolePermissionListInt role;
//getters and setters for the above
public String grantRolesPermissions(){
System.out.println("enter the grant roles and permissions");
String tab2="modify";
boolean id;
HttpServletRequest request=getRequest();
HttpSession session=request.getSession();
session.setAttribute("tabs",tab2 );
role=aspService.getGrantedRolesAndPermissions(modify);
System.out.println("assigned roles===================>"+role.getAssignedRoles());
modify.setAspmodifyassignedRole(role.getAssignedRoles());
modify.setAspmodifyassignedPermission(role.getAssignedPermissions());
modify.setCreatedby(role.getCreatedBy());
modify.setCreateddate(role.getCreatedDate());
modify.setUpdatedby(role.getUpdatedBy());
modify.setUpdateddate(role.getUpdatedDate());
modify.setUsername(role.getUserName());
modify.setModifyftid(role.getFtid());
System.out.println("assigned permissions==============>"+role.getAssignedPermissions());
System.out.println("updated by=================>"+role.getUpdatedBy());
System.out.println("update date=================>"+role.getUpdatedDate());
System.out.println("created by===================>"+role.getCreatedBy());
System.out.println("created date=====================>"+role.getCreatedDate());
System.out.println("ftid=============================>"+role.getFtid());
System.out.println("user name===========================>"+role.getUserName());
System.out.println("ftid=============>"+role.getFtid());
if(role!=null){
return SUCCESS;
}else{
return ERROR;
}
}
#Override
public void setServletRequest(HttpServletRequest arg0) {
// TODO Auto-generated method stub
}
#Override
public ASPAdminModifyBean getModel() {
// TODO Auto-generated method stub
return modify;
}
}
Well, the code is grown again :)
Start by :
changing your ArrayList declarations to List (in both beans, and regenerate getters and setters);
changing the way you get the Request, the Response and the Session, by using Struts2 Aware s interfaces (and their setters, with code inside)
Then if it still doesn't work, describe a little better what do you mean by "values are not passing to Action", and consider posting your interceptor stack config.
Hope that helps...

Categories

Resources