Infuriating empty list in Spring form / controller - java

I'm trying to create a list of objects from form inputs. The objects are the same but their values may differ, it's essentially a menu.
I'm still getting to grips with Spring/Thymeleaf which is adding some level of complexity to what feels like a simple task.
I've a class for the menu, a simple POJO, there is then a list of these defined as a data member in the bean itself:
private ArrayList<GuestMenuOptions> guestMenus;
I've read many posts, tried many things and am on the verge of softly resting my head against the table.
I've had several errors, most of which either tell me that the list cannot be found or that the list is empty - it's currently in stable condition where the list, no matter what I try, will not be populated, even when I load in default values...unfortunately my debugger has died which is not helping.
Any help is appreciated. thank you
EntryController:
#RequestMapping(method=RequestMethod.GET, value="/")
public String indexPage(Model model) {
model.addAttribute("childMenuOptions", generateChildMenus());
//not sure if this is neccesary...
ArrayList<GuestMenuOptions>guestMenus = new ArrayList<>();
GuestMenuOptions ad1 = new GuestMenuOptions();
GuestMenuOptions ad2 = new GuestMenuOptions();
guestMenus.add(ad1);
guestMenus.add(ad2);
GuestContactBean ctb = new GuestContactBean();
ctb.setGuestMenus(guestMenus);
model.addAttribute("guestContactBean", ctb);
model.addAttribute("formBackingBean", new FormBackingBean());
return "index";
}
Form:
<form modelAttribute="guestBean" class="contact_form" name="rsvp" role="form" th:object="${formBackingBean}" th:action="#{/sendRsvp}" method="post">
<div class="row">
<div class="form-group">
<select name="ad1Starter" id="starterMealAdult1">
<option value="!!!">-Starter-</option>
<option th:field="${guestContactBean.guestMenus[0].starter}" th:each="entry : ${adultMenuOptions.get('starter').entrySet()}" th:value="${entry.key}" th:text="${entry.value}">
</option>
</select>
</div>
<input type="submit"guest name="submit" class="btn default-btn btn-block" value="Send RSVP">
RequestController:
#RequestMapping(value = "/sendRsvp", method = RequestMethod.POST)
public String sendRsvp(#ModelAttribute("guestContactBean") GuestContactBean guestContactBean,
#ModelAttribute("guestMenus") ArrayList<GuestMenuOptions>menus,
BindingResult result) throws MessagingException {
smtpMailSender.send(guestContactBean);
return "thanksMessage";
}
Beans:
FormBacking is POJO with no reference to the menus at all.
GuestMenuOptions is the same with just starter, desert members
guestContactbean has not much more going on, basic fields with the addition of the list of GuestMenuOptions
private String numberOfAdults;
private String eventAttending;
private ArrayList<GuestMenuOptions> guestMenus;
public ArrayList<GuestMenuOptions> getGuestMenus() {
return guestMenus;
}
EDIT:
The field that populates the drop downs in working fine, it's declared as private Map<String, Map<String, String>> adultMenuOptions;
private Map<String, Map<String, String>> childMenuOptions;
they are then built in the controller so that each may have several options under 'starter', 'main' and desert' for example:
starter.put("salmon", "Smoked Salmon");
starter.put("pate", "Chicken Liver Pate");
this is then populating both the value and text of the dropdown.
If I could save the state of this Map and pass it back to the controller instead, that would also be fine but I wasn't able to why then spawned the creation of the there wrapper list.

Please revisit http://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#dropdownlist-selectors. It should be as simple as
class Animal {
int id;
String name;
}
then in your template:
<select th:field="*{animalId}">
<option th:each="animal : ${animals}"
th:value="${animal.id}"
th:text="${animal.name}">Wireframe</option>
</select>
I think your code is all over the place and you're mixing up menu selection with menu item types.

Related

Input in array Thymeleaf

I need choose values from one array and assign it to other array. Using Spring Thymeleaf. No idea how retrieve these choosed values.
My classes:
#Entity
public class Collaborator {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size (min=3, max=32)
private String name;
#NotNull
#ManyToOne (cascade = CascadeType.ALL)
private Role role;
public Collaborator() {}...
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 3, max = 99)
private String name;
public Role() {}....
My controllers:
#RequestMapping("/project_collaborators/{projectId}")
public String projectCollaborators(#PathVariable Long projectId, Model model) {
Project project = mProjectService.findById(projectId);
List<Collaborator> allCollaborators = mCollaboratorService.findAll();
List<Collaborator> assignments = new ArrayList<>();
if (project.getRolesNeeded()!=null) {
for (int i=0;i<project.getRolesNeeded().size();i++) {
assignments.add(new Collaborator("Unassigned", project.getRolesNeeded().get(i)));
assignments.get(i).setId((long) 0);
}
}
model.addAttribute("assignments", assignments);
model.addAttribute("allCollaborators", allCollaborators);
model.addAttribute("project", project);
return "project_collaborators";
}
#RequestMapping(value = "/project_collaborators/{projectId}", method = RequestMethod.POST)
public String projectCollaboratorsPost(#ModelAttribute Project project, #PathVariable Long projectId, Model model) {
Project p = mProjectService.findById(projectId);
//mProjectService.save(project);
return "redirect:/project_detail/{projectId}";
}
And template:
<form th:action="#{'/project_collaborators/' + ${project.id}}" method="post" th:object="${project}">
<label th:text="'Edit Collaborators: ' + ${project.name}">Edit Collaborators: Website Project</label>
<ul class="checkbox-list">
<li th:each="a : ${assignments}">
<span th:text="${a.role.name}" class="primary">Developer</span>
<div class="custom-select">
<span class="dropdown-arrow"></span>
<select th:field="${a.id}">
<option th:each="collaborator : ${allCollaborators}" th:value="${collaborator.id}" th:text="${collaborator.name}">Michael Pemulis</option>
</select>
</div>
</li>
</ul>
<div class="actions">
<input type="submit" value="Save" class="button"/>
Cancel
</div>
</form>
As you can see I want to let user choose for each role (roleNeeded) any collaborator from (allCollaborators) and keep that assigns in List (assignments).
And I get error message:
ava.lang.IllegalStateException: Neither BindingResult nor plain target
object for bean name 'a' available as request attribute
So question is: how to solve it, assign values from one array to another in template and retrieve that values in my controller.
The cause of the exception
The IllegalStateException you are getting is because th:field="${a.id}" in your select element must be related to the form th:object="${project}" element; the th:field attribute must refer to an actual field in the project instance (also you need to write th:field="*{fieldName}"). That should fix the exception you are getting, but will not solve your entire problem as the second part of it is related to how to make the values get into your controller, which I will explain next.
Sending the values to your controller
To get the values into your controller you will need to make a few changes. As I don't really know the code of your Project class, I will change a few things so you will be able to figure it out how to adapt this simple example to your particular case.
First, I understand you want to make a relation like the following in your form:
Role1 => CollaboratorA
Role2 => CollaboratorB
Your controller needs to receive a list and in order to receive this information we need two classes:
The class which will be storing the individual element data, mapping a role id with the collaborator id:
public class RoleCollaborator {
private Long roleId;
private Long collaboratorId;
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getCollaboratorId() {
return collaboratorId;
}
public void setCollaboratorId(Long collaboratorId) {
this.collaboratorId = collaboratorId;
}
}
A wrapper class to store a list of individual mappings:
public class RolesCollaborators {
private List<RoleCollaborator> rolesCollaborators;
public List<RoleCollaborator> getRolesCollaborators() {
return rolesCollaborators;
}
public void setRolesCollaborators(List<RoleCollaborator> rolesCollaborators) {
this.rolesCollaborators = rolesCollaborators;
}
}
The next thing to do is change your controllers where you have two methods, one that handles GET requests and another one which handles the POST requests and so, receives your form data.
In the GET one:
public String projectCollaborators(#PathVariable Long projectId, Model model) {
(... your code ...)
model.addAttribute("project", project);
// Add the next line to add the "rolesCollaborators" instance
model.addAttribute("rolesCollaborators", new RolesCollaborators());
return "project_collaborators";
}
As you can see, we added a line that will be used by the thymeleaf template. Right now is a wrapper of an empty list of roles and collaborators, but you can add values if you need to edit existing mappings instead of adding new ones.
In the POST one:
// We changed the #ModelAttribute from Project to RolesCollaborators
public String projectCollaboratorsPost(#ModelAttribute RolesCollaborators rolesCollaborators, #PathVariable Long projectId, Model model) {
(... your code ...)
}
At this point, your controller is prepared to receive the information sent from your form, that we also need to modify.
<form th:action="#{'/project_collaborators/' + ${project.id}}" method="post" th:object="${rolesCollaborators}">
<label th:text="'Edit Collaborators: ' + ${project.name}">Edit Collaborators: Website Project</label>
<ul class="checkbox-list">
<li th:each="a, stat : ${assignments}">
<span th:text="${a.role.name}" class="primary">Developer</span>
<div class="custom-select">
<input type="hidden" th:id="rolesCollaborators[__${stat.index}__].roleId" th:name="rolesCollaborators[__${stat.index}__].roleId" th:value="${a.role.id}" />
<span class="dropdown-arrow"></span>
<select th:field="*{rolesCollaborators[__${stat.index}__].collaboratorId}">
<option th:each="collaborator : ${allCollaborators}" th:value="${collaborator.id}" th:text="${collaborator.name}">Michael Pemulis</option>
</select>
</div>
</li>
</ul>
<div class="actions">
<input type="submit" value="Save" class="button"/>
Cancel
</div>
</form>
Here there are a few changes:
As I pointed out in The cause of the exception you need to change th:object="${project}" to th:object="${rolesCollaborators}", as rolesCollaborator is the instance name from where you will receive the values from your GET controller method and where you will be sending the values to your POST controller method.
I added a hidden input; this input will store the role id that will be send in association to the collaborator id the user picks from the interface using the select element. Take a look at the syntax used.
I changed the th:field value of your select element to refer to a field in the rollesCollaborators object we use in th:object="${rolesCollaborators}" form attribute. This will set the value of the collaborator in the RoleCollaborator element of the RolesCollaborators wrapped list.
With these changes your code will work. Of course, you can improve it with some other modifications, but I tried to not introduce more modifications to focus on your problem.

Spring MVC / JSP - How to pass nested list of object to controller from a select form

I am using Spring MVC with JSP.
I have a User entity which contains a list of Group entities. The relationship is Many-to-Many (a Group entity contains a list of User entities).
In my user controller I have a method for returning the add user page with an empty User entity and a list of available Group entities.
#RequestMapping(value = "/add", method = RequestMethod.GET)
public ModelAndView loadUserAdd() {
ModelAndView mv = new ModelAndView("user/userAdd");
mv.addObject("user", new User());
try {
mv.addObject("groups", gr.listGroups());
} catch (TestException e) {
mv.addObject("error", e.getMessage());
e.printStackTrace();
}
return mv;
}
On the userAdd page, I want to select the group(s) the user will have from the list of the available groups.
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6">
<select id="availableGroups" class="form-control" multiple onclick="shuttle('availableGroups', 'selectedGroups')">
<c:forEach items="${groups}" var="group">
<option value="${group.id}">${group.id}: ${group.name}</option>
</c:forEach>
</select>
</div>
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6">
<select id="selectedGroups" class="form-control" multiple onclick="shuttle('selectedGroups', 'availableGroups')">
<c:forEach var="group" items="${user.groups}" varStatus="status">
<option value="${group.id}">${group.id}: ${group.name}</option>
</c:forEach>
</select>
</div>
</div>
Note, the 'shuttle' function moves a group from one select element to the other. E.g. from the available groups to the selected groups or vice versa. This works.
On submit, I want to have the selected groups set in the user entity so that it will arrive in the addUser method.
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ModelAndView addUser(#ModelAttribute("user") User user) {
Instead on submit, the user entity contains a null list of groups. I'm sure my JSP is wrong so it would be great if someone could point me in the right direction. Any advice on improvements would be good as I'm doing this as a learning exercise. Thanks.
A college suggested a way to solve this question. The solution offered uses a single select field rather than two select fields with a shuttle moving values from available to selected and vice versa.
In the JSP, I've replaced both select fields with just one:
<div class="row">
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-3">
Groups
</div>
<div class="col-xs-9 col-sm-9 col-md-9 col-lg-9">
<form:select
path="groups"
items="${availableGroups}"
multiple="true"
itemValue="id"
itemLabel="name"
class="form-control" />
</div>
</div>
This form:select iterates over the available groups and creates a option element for each. The select attributes are:
path - uses the value 'groups' to map to the getGroups getter method on the user entity in the view model (modelAndView.addObject("users", users);).
items - is the list of all available groups in the system. This is set in the model from the controller. See note about this below.
itemValue - is the value which will become the select's option's value. The 'id' maps to the getId getter method of the current group.
itemLabel - is the value which will become the select's option's visible label. The 'name' maps to the getName getter method of the current group.
The form:select also marks options as selected if the user has them set.
Here is an example output where the groups are 1,2,3,4 (both in id and name) and the user has group 1,2.
<div class="col-xs-9 col-sm-9 col-md-9 col-lg-9">
<select id="groups" name="groups" class="form-control" multiple="multiple">
<option value="1" selected="selected">1</option>
<option value="2" selected="selected">2</option>
<option value="3">3</option>
<option value="5">4</option>
</select>
<input type="hidden" name="_groups" value="1"/>
</div>
To make 'availableGroups' available I've used:
#ModelAttribute("availableGroups")
public List<Group> initializeGroups() {
return us.listGroups();
}
This makes the list of available-groups available to each view of the controller.
On submit, to update the user with the selected groups (either adding or removing groups as this works on edit too), I've used a Converter (org.springframework.core.convert.converter.Converter).
#Component
public class GroupConverter implements Converter<String, Group> {
#Autowired
GroupService groupService;
public User convert(String element) {
User user = null;
if(element != null) {
int id = Integer.parseInt(element);
user = userService.fetchUser(id);
}
return user;
}
}
The Converter takes the id of the group and gets it from the data source then magically sets it in the user entity before the controller is called.
Converters are set on the WebMvcConfigurationSupport...
#Autowired
GroupConverter groupConverter;
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(groupConverter);
}

Spring 3 MVC capability for JSTL jsp option group label

I am a newbie to Spring and I want to create "option group select" But I am unable to do this.
I want an output as following but in HTML type saying
<select name="..." value"...">
<optgroup label="Category 1">
<option ... />
<option ... />
</optgroup>
<optgroup label="Category 2">
<option ... />
<option ... />
</optgroup>
</select>
General
movies
hobbies
Games
football
basketball
Images
officePics
familyPics
PresntationPics
RingTones
pop
classical
jazz
jsp code
Edited : Correct one
<form:select multiple="single" path="servicemodule" id="servicemodule">
<form:option value="None" label="--Select--" />
<c:forEach var="service" items="${servicemodule}">
<optgroup label="${service.key}">
<form:options items="${service.value}"/>
</optgroup>
</c:forEach>
</form:select>
Controller code :
There are 4 main categories and under each of these there can be many subCategories. These can be retrieved from getServiceModuleList method. But I am not getting idea where to implement the loop to store different subcategories under their respective main category.
#Autowired
private ServiceModule servicemodule;
Edited: Correct #ModelAttribute
#ModelAttribute("servicemodule")
public Map<String,List<String>> populateService() {
String[][] mainCategory = new String[7][2];
mainCategory[0][0]= "General"; mainCategory[0][1]= "general1234";
mainCategory[1][0]= "Games"; mainCategory[1][1]= "games1234";
mainCategory[2][0]= "Images"; mainCategory[2][1]= "images1234";
mainCategory[3][0]= "Ringtones"; mainCategory[3][1]= "ringtone1234";
Map<String,List<String>> serviceModule=
new LinkedHashMap<String,List<String>>();
List<String> subCategory=new ArrayList<String>();
List<ServicesPojo> services=
servicemodule.getServiceModuleList("1",mainCategory[0][1],"0");
for(ServicesPojo serviceName: services)
{
subCategory.add(serviceName.getServiceName().trim());
}
serviceModule.put(scats[0][0],subService);
return serviceModule;
}
Edited: Got the Answer for Loop
for(int i=0;i<mainCategory.length;i=i+2){
List<String> subCategory=new ArrayList<String>();
List<ServicesPojo> services=
servicemodule.getServiceModuleList("1",mainCategory[0][i],"0");
for(ServicesPojo serviceName: services)
{
subCategory.add(serviceName.getServiceName().trim());
}
serviceModule.put(mainCategory[i][0],subCategory);
}
Model
This has the main error whether I should keep only String or List Confused!!
Edited: Now Corrected one
private List<String> servicemodule;
public List<String> getServicemodule() {
return servicemodule;
}
public void setServicemodule(List<String> servicemodule) {
this.servicemodule = servicemodule;
}
Error Description
org.springframework.beans.NotReadablePropertyException:
Invalid property 'serviceModule' of bean class
[springx.practise.model.SiteModel]: Bean property 'serviceModule'
is not readable or has an invalid getter method:
Does the return type of the getter match the parameter type of the setter?
Solved!!
Watch you case: servicemodule != serviceModule.
The <c:foreEach> loop isn't correct either: it uses itemGroup both for var and varStatus, and itemGroup is never used inside the loop. Instead, serviceModule is used, but is not defined anywhere.
And I have a hard time understanding your code, one of the reasons being that you use the same name for very different things and don't pluralize attributes of type List.
private ServiceModule servicemodule;
...
Map<String,List<String>> serviceModule
...
private List<String> servicemodule;
...
<form:select multiple="single" path="serviceModule" id="serviceModule">
...
<c:forEach var="itemGroup" items="${servicesModule}" varStatus="itemGroup">
No wonder you lost yourself.

Bind Collection to Form - why is it not working?

Could someone please help me find out why my attempt to bind Collection to the form in Spring MVC is not working?
Here is how my object looks like -
public class TestObj {
private Integer testNumber;
private String home;
private String destination;
}
Here is my form object that contains list of above object -
public class TestForm {
private List<TestObj> testList;
//contains getter and setter for testList
}
In my controller, I have implemented formBackingObject method -
public class MyController extends SimpleFormController {
public MyController() {
setCommandClass(TestForm.class);
setCommandName("testForm");
}
protected Object formBackingObject(HttpServletRequest request) throws Exception {
if (isFormSubmission(request)) {
testForm = (TestForm) super.formBackingObject(request);
//THIS ALWAYS RETURNS NULL ON FORM SUBMISSION
List<TestObj> testList = testForm.getTestList();
} else {
//load initial data using hibernate. TestObj is hibernate domain object.
List<TestObj> testList = myService.findTestList();
testForm = new TestForm(testList);
}
return testForm;
}
Here is my JSP snippet -
<form:form commandName="testForm" method="post">
<c:forEach items="${testForm.testList}" var="testvar" varStatus="testRow">
<tr>
<td>
<form:hidden path="testList[${testRow.index}].home" />
<c:out value="${testvar.home}" />
</td>
<td>
<form:input path="testList[${testRow.index}].destination" />
</td>
</tr>
</c:forEach>
<tr><td><input type="submit"></td></tr>
</form:form>
While the first time I load the data shows up fine on the form, when I press submit button the control goes to the formBackingObject method and the isFormSubmission returns true. However, when I get the command object using super.formBackingObject(request), it returns the form object with the testList value as null. I am unable to figure out why this simple case is not working?
I will really appreciate any help in getting this to work.
Are you using Spring 3? If so, you should take a look at this post.
With respect to list processing and object binding, take a look at this post.
Try using the following code. May be that can solve your problem.
private List<TestObj> operationParameterses = LazyList.decorate(new ArrayList<TestObj>(), FactoryUtils.instantiateFactory(TestObj.class));
It won't return you all null list.
Hope that helps you.
Cheers.
I guess my understanding of formBackingObject method must be wrong. I removed that method from the implementation, used referenceData for initial form load and onSubmit to process it on submit. This works fine and does get collection in the form back as expected.
Thanks all for the help though.

Inner object id save in Spring 3

I want to save Village object through hibernate where already persisted District id need to be saved. I populated district object in dropdown. I coded similar work in spring 2, but in spring 3 it doesn't work.
here, if I log village.getDistrict() in POST, id is set perfectly as I set to dropdown but other value of district object is null.
#SessionAttributes({"village"})
#Controller
public class VillageController{
#Autowired(required=true)
private AddressService addressService;
#RequestMapping(value="/cp/village.html", method = RequestMethod.GET)
public String setForm(ModelMap model) {
Village village = new Village();
village.setDistrict(new District());
model.addAttribute("village", village);
return "/cp/village";
}
#ModelAttribute("districtList")
public List<District> populateDistrictList() {
return addressService.getDistrictList();
}
#RequestMapping(value="/cp/village.html", method = RequestMethod.POST)
public String getForm(#ModelAttribute("village") Village village,
BindingResult result,
SessionStatus status) {
log.debug("============================="+village.getDistrict());
addressService.saveVillage(village);
status.setComplete();
return "redirect:/cp/village.html123";
}
}
In JSP:
<form:form commandName ="village" action="village.html" >
<div>
<label><fmt:message key="location.district"/></label>
<form:select path="district.id">
<form:options items="${districtList}" itemValue="id" itemLabel="districtName"/>
</form:select>
</div>
<div>
<label><fmt:message key="location.village"/></label>
<form:input path = "villageName" />
</div>
<div>
<label><fmt:message key="prompt.remarks"/></label>
<form:textarea path = "remarks" rows="2" cols="50"/>
</div>
<div class = "button-area">
<input type = "submit" value="Save" class="submit-button" />
<input type = "button" value="Cancel" onclick="window.location='commonComplaintList.html'" class="submit-button" />
</div>
<br/>
</form:form>
You do not set any value in the District object, so it is empty.
If you expected that the district has the values of objects from List<District> populateDistrictList(), then I have to say: it does not work out of the box.
One way to do it, would be implementing a Converter, that converts a string (the id) to the District object, by loading it from the database.
I think you need to implement a Converter for the District entity as well. Since the Converter for your Village entity class is likely not called at all during the POST (at least not for new Villages, since they lack an id). Also, even if it was called the District would still need a Converter in order to fetch the correct District from Hibernate. Otherwise, Spring will just give you a fresh instance of the District with the Id set to the value in the select.
However, if you are only going to save the Village I am unsure whether you would actually need to fetch the whole District object from Hibernate. I think (not 100% sure on this since it was a while since I used Hibernate and Spring) that Hibernate will associate the village you create in your POST with the correct district even though you only have a District object with an Id. You might need to specify some annotation stuff about cascading for it to not overwrite the District objects other properties.
EDIT:
Looked it up a bit and I think you should annotate the district property of the Village class with something like:
#Column(name = "district", insertable = false)
That way Hibernate will know that no new districts should be added when storing a new Village.
Again not sure about this last part, but worth a quick try :)

Categories