Spring 3.1 Form and List Binding - spring

I have not been able to find a solution on this yet after searching and searching.
Using Spring 3.1
I have a form where I would like the user to be able to add multiple point of contacts (poc). In my JSP I have a submit button for adding a poc. It goes to a Spring Controller, gets the current list of pocs from the domain object (which will be empty at first), adds the poc to the list and then puts the domain object back into the model before returning to the same view. Then the user will ultimately submit the entire page which will go to the controller and save the entire object to persistence.
I have tried various attempts and come up with different results from only being able to add one poc with a new one overwriting the existing, to not being able to get the entered poc displayed on the form. I will put the code I currently have.
JSP:
<form:form method="post" commandName="request">
...
<h2>POC:</h2>
<input type="text" name="newPoc"/>
<input type="submit" name="addPoc" value="Add POC"/>
<table>
...
<c:forEach items="${request.pointOfContacts}" var="poc" varStatus="vs">
<tr>
<td><form:label path="pointOfContacts[${vs.index}].name/></td>
.....
</c:forEach>
......
</table>
</form:form>
Spring Controller:
#RequestMapping(value="/request", method=RequestMethod.POST, param="addPoc")
public Sring addPoc(#RequestParam String newPoc, MyRequest req, Model model) {
PointOfContact poc = new PointOfContact();
poc.setName(newPoc);
List<PointOfContact> pocs = req.getPointOfContacts();
pocs.add(poc);
req.setPointOfContacts(pocs);
model.addAttribute("request", req);
return "requestForm";
}
Domain Objects
#Entity
public class MyRequest {
...
#OneToMany(Cascade=CascadeType.ALL)
private List<PointOfContact> pointOfContacts = new ArrayList<PointOfContact>();
.....
}
#Entity
public class PointOfContact {
...
private String name;
....
}
Does anybody have any solutions? I have seen various posts about AutoPopulatingList, is this a solution? If so how would I use it in this example.

Yes you should use AutoPopulatingList, example. This will require change in MyRequest entity.

Related

Spring framework child object in object gets lost after clicking on the website

I started to learn spring some weeks ago and it’s going pretty nice so far. But there’s some nasty workaround that bothers me since some days.
In short: I retrieve an object (from one database table library) with a child object which is a list (coming from another table book). The library and list of books is loaded correctly when it is called - my Library class looks like this.
#Data
#Entity
public class Library implements Serializable {
…
#OneToMany(mappedBy = „library“)
#JsonManagedReference
private List<Book> books;
…
}
I created a website with html and thymeleaf to show all the data in a html form.
Somewhere inside the form I've put a select control, as shown here:
<select id="selectReader" name="selectReader" th:object="${bookstore.selectedReader}" th:field="*{id}" class="form-control form-control-sm" required="true" onchange="submit()">
<option th:value="-1">Add Reader</option>
<option th:each="item : ${readers}" th:value="${item.id}" th:text="${item.name}"/>
</select>
But, when I click the select control the page is reloaded and the list of books is gone.
#PostMapping("/updateBookstore/{id}")
public String updateBookstore(#PathVariable int id, #ModelAttribute Bookstore bookstore, Model model) {
// bookstore.books is null
// also checking the model for the attribute "bookstore.books" is null
}
My current workaround is to store my library object in a private field of the RestController, then the list of books is still there. But that seems like bad practice to me.
How can I setup the code so that the library is always loaded with the list of books without this workaround? Please tell me if you have an idea.
I forgot to create a reference for thymeleaf in the HTML. By adding it I can see the list of books in the code.
<tr th:each="book, stat : *{books}">
<td>Borrowed by</td>
<td>
<input type="text" class="form-control form-control-sm" th:field="*{books[__${stat.index}__].borrowedBy}"/>
</td>
</tr>

Spring relationships between tables in view

Well, I started a new project using Spring MVC (I'm beginner in technology) and soon I had a basic question which I am unable to find on the internet, maybe the reason that I'm doing wrong or implementing the wrong question.
I have a form in which the data will be persisted are in two different tables.
What better way to do this?
I created two related tables, one called "Agency" and another called "Login". An "Agency" may contain one or more "Login" (# OneToMany), but the problem takes the view creation time, because data from both tables will compose a single form. With some research I noticed that I can not have two modelAttribute in my form.
I apologize for the mistakes in English.
Best regards!
if the mapping is correct and Agency contain one or many login, what you have to do is render the Agency in the model and view and in your form iterate the logins
<form:form id="foo"
method="post"
action="url"
modelAttribute="agency">
<form:input type="hidden" path="id"/>
<c:forEach var="login" items="${agency.logins}"
varStatus="login_index">
<form:input type="hidden" path="login.id" />
</c:foreach>
</form:form>
thanks for the reply.
But is not it :(
I have a form in which the data will be persisted are in two different tables.
<form class="form-signin" method="post" action="addAgency">
<div class="input-group">
<span class="input-group-addon entypo-user"></span>
//Table Agency
<spring:bind path="tenant.firstName"/>
<input class="form-control" placeholder="Nome"/>
//Table Login
<spring:bind path="login.email"/>
<input class="form-control" placeholder="Nome"/>
</div>
//Rest of my form...
</form>
In my view I have the annotation "bind" Spring, searching in the internet I found this way to make the connection between the controller and the view for persist two tables.
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(#ModelAttribute("tenant") Agency tenant, #ModelAttribute("login") Login login, ModelMap map) {
Agency agency = dashboardFacade.getAgency();
map.addAttribute("agency", agency);
if (tenantResolver.isMasterTenant()) {
//Here is the problem!!
// Add an attribute in my view of type login and agency, but i don't kwon if it is correct.
map.addAttribute("tenant", tenant);
map.addAttribute("login", login);
return "landing/index";
} else {
return "dashboard/home";
}
}
The method below to save an agency and login.
// Add a new agency
#RequestMapping(value = "/addAgency", method = RequestMethod.POST)
public String addAgency(#ModelAttribute("tenant") Agency agency, #ModelAttribute("login") Login login, Model model, final RedirectAttributes redirectAttributes) {
agency = dashboardFacade.addAgency(agency);
login = dashboardFacade.addLogin(login);
return "redirect:" + getAgencyFullUrl(agency);
}
What better way to do this?
Thank you

Simple JSP-Spring 3 dropdown list not working

I know this should be pretty easy but I'm stuck after trying several things.
I'm only trying to display in my jsp a basic dropdown list. Spring version is 3 so I want everything to work with annotations.
JSP form with dropdown list:
<form:form method="post" commandName="countryForm">
<table>
<tr>
<td>Country :</td>
<td><form:select path="country">
<form:option value="Select" label="Select" />
</form:select>
</td>
<tr>
<td colspan="3"><input type="submit" /></td>
</tr>
</table>
</form:form>
CountryForm.java is a plain object with a single String attribute "country", with its getters and setters.
Controller who deals with the GET request is the following:
#Controller
public class CountryFormController {
#RequestMapping(value = "MainView", method = RequestMethod.GET)
public String showForm(Map model) {
CountryForm cform = new CountryForm();
model.put("countryForm", cform);
return "MainView";
}
}
However, when I redirect to the JSP "MainView" I get the typical error:
org.apache.jasper.JasperException: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'countryForm' available as request attribute
org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:502)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:424)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
What am I doing wrong?
The select tag in the Spring TagLib needs to be provided with a collection, map or array of options. I'm not sure what you would like these to be so I will make some assumptions.
You need to include a collection, map or array of objects in your controller. Ideally you would have a Country class and create new instances for a set of countries. For the example to work with your code, I just created a static list of countries. Add the list to your model and then modify the select tag, setting the options to ${countries}. Assuming country is a field of type String on CountryForm with appropriate get/set methods, the country should data-bind to field when the form is submitted.
Controller
#Controller
public class CountryFormController {
#RequestMapping(value = "MainView", method = RequestMethod.GET)
public String showForm(Map model) {
List<CountryForm> cfs = new ArrayList<CountryForm>();
cfs.add("United States");
cfs.add("Canada");
model.put("countries", cfs);
model.put("countryForm", cform);
return "MainView";
}
}
JSP
<form:select path="countryForm.country" options="${countries}"/>
I have sample code at GitHub, try it an let me know. Look at landing.jsp and UserController
<form:select path="users[${status.index}].type" >
<form:option value="NONE" label="--- Select ---"/>
<form:options itemValue="name" itemLabel="description" />
</form:select>
HTH

Handling Multiple forms of the same type - impossible?

Consider the following scenario:
My form model
public class PersonForm {
#NotNull
private String name;
/*usual getters and setters*/
}
My controller:
#Controller
#SessionAttribute(types={ PersonForm.class })
public class MyController {
#RequestAttribute(...)
public String render(final ModelMap map) {
/* get list of info and for each info
* create a PersonForm and put it in the modelmap
* under key p0, p1, p2, ..., pn
*/
}
public String submit(final ModelMap map,
#Valid final PersonForm form,
final BindingResult result) {
if (result.hasErrors()) {
// return to page
} else {
// do necessary logic and proceed to next page
}
}
}
And finally my JSP view
...
<c:forEach ...>
<form:form commandName="p${counter}">
... other form:elements and submit button goes here
</form:form>
</c:forEach>
...
As you can see I am trying to handle multiple forms of the same class type. The submit works -- it gets me to the submit(...) method just fine, and so does the validation. However re-rendering the page does not show me the expected error messages!
Even worse -- I checked what is being passed in the submit header and there is no indication whatsoever which form submitted, so there is no way to discriminate between one form on another. This led me to believe multiple forms of the same class type is not possible ...
Is there any other way I could do this (apart from Ajax) ?
Many thanks.
I managed to get this 'hack' to work. It is as what jelies has recommended so the credit goes all to him.
In simple terms, the concept is to pre-fill your view using the traditional <c:forEach> construct. The tricky part is whenever the 'Submit' button of that respective row is pressed, all of the information must be injected into a hidden form and force-submitted to the Controller. If the screen is rendered again with some errors, the script must be responsible of injecting the values back to the respective rows including the errors.
1) My model
public class PersonForm {
private String id;
#NotNull
private String name;
/*usual getters and setters*/
}
2) My controller
#Controller
#SessionAttribute(/* the hidden form name, the person list */)
public class MyController {
#RequestAttribute(...)
public String render(final ModelMap map) {
/* get list of info and for each info
* create a PersonForm and put it in the modelmap
* under key p0, p1, p2, ..., pn
*/
}
public String submit(final ModelMap map,
#Valid final PersonForm form,
final BindingResult result) {
if (result.hasErrors()) {
// return to page
} else {
// do necessary logic and proceed to next page
}
}
}
3) My view
...
<form:form commandName="personForm" cssStyle="display: none;">
<form:hidden path="id"/>
<form:hidden path="name" />
<form:errors path="name" cssStyle="display: none;" />
</form:form>
...
<c:forEach var="p" items="${pList}">
<input type="text" id="${ p.id }Name" value="${ p.name }" />
<!-- to be filled in IF the hidden form returns an error for 'name' -->
<span id="${ p.id }nameErrorSpan"></span>
<button type="button" value="Submit" onclick="injectValuesAndForceSubmit('${ p.id }');" />
</c:forEach>
...
<script type="text/javascript">
injectValuesAndForceSubmit = function(id) {
$('#id').val( id ); // fill in the hidden form's id
$('#name').val( $('#'+id+'name').val() ); //fill in the hidden form's name
$('#personForm').submit(); //submit!
}
$(document).ready(function() {
var id = $('#id').val();
if (id.trim().length == 0) {
//Empty. Nothing to do here as this is a simple render.
} else {
//The page seems to be returning from some sort of error ... pre-fill the respective row!
$('#'+id+'name').val($('#name').val());
var hiddenNameErrorSpan = $('#name.errors');
if (hiddenNameErrorSpan) {
$('#'+id+'nameErrorSpan').text(hiddenNameErrorSpan.html());
}
} //else
}
</script>
As you can see the view has the hairiest parts -- hopefully it will still proves to be useful for anyone who (unfortunately) comes across the same situation as mine. Cheers!
IMHO having multiple forms makes things overcomplicated (or at least with spring). Also, you are using multiple forms but only one is going to be submitted.
So, I suggest that the easiest way to manage this is using a unique hidden external form with person properties. When one of the buttons is pressed, fill accordingly the person properties of form and submit it. With this you are achieving the tipical spring form submit/validation.
Maybe this solution requires a bit work with JavaScript, but I don't know how to handle spring-mvc with multiple forms, I always tried to avoid it, due to previous unsuccessful attemps.

Using Beans to populate/retrieve view

I'm new to Spring and little confused of how to use beans for populating and retrieving values to/from the view.
Here is what I'm doing now.
In the controller I'm initializing two beans xxxMain.java and xxxView.java. I'm using xxxMain.java to retrieve values FROM the view and xxxView.java for pre-populating the view.
Here is my controller
#RequestMapping(value = "accounting", method = RequestMethod.GET)
public String showPage( Model model) {
XXXMain xxxMain = new XXXMain();
XXXView xxxView = new XXXView();
service.loadXXXForm(xxxMain, xxxView);
model.addAttribute("xxxMain", xxxMain);
model.addAttribute("xxxView", xxxView);
return "admin/xxx";
}
So as I'm using the xxxMain.java for retrieving I'm coding the jsp like this.
<form:form modelAttribute="XXXMain" method="post" action="/app/home/save">
</form:form>
also I'm using Spring tags, like
<form:input path="name" size="15"/>
Now, when the fields in the view are empty, all is fine, but when I have to pre-populate the fields, I'm not sure what approach to take as
<form:input path="name" size="15"/>
does not has a value attribute to populate the field. So what I have done is populate the XXXMain.java class along with the XXXView.java class with the default values, as you can see in the controller code snippet. That way values are pre-populated when view is first loaded. But I'm not sure if I'm doing the right thing by populating the xxxMain.java file which in fact should only contain the user entered values.
How can I improve this design?
Thanks a lot.
Ravi
Here is an example I wrote which might help steer you right.

Resources