Is there anything like AutoPopulatingList
but for Set
?
The data that I want to show is an association that uses Set
.
public class Employer implements java.io.Serializable {
private Set<Employee> employees = new HashSet();
}
I've tried using AutoPopulatingList
but in that case I have to use List
in hibernate which needs me to specify list-index
using Employee.employeeId
and whenever I retrieve the employees
through Employee
later the list would have spaces between element (null
elements) depending on the Employee.employeeId
.
I need to auto-populate the collection because I need to generate the employees
dynamically while creating the Employer
.
I got the following when I use plain Set
:
org.springframework.beans.InvalidPropertyException: Invalid property 'employees[0]' of bean class [model.Employer]: Cannot get element with index 0 from Set of size 0, accessed using property path 'employees[0]'
Is there any other solution?
Edit
I'm trying to implement dynamic form
You can not use Set
as a binding target in MVC because it is not possible to create property path for its items.
You should use Map<Integer, YourType>
when building dynamic forms. What we have implemented many times (so I know it is working) is this:
1, 3, 4, ...
)maxIndex + 1
(always increasing sequence)Map
implementation MUST BE instance of LinkedHashMap
so that the iteration order is preserved (Spring is creating this implementation by default if a Map
field needs to be autopopulated)Map
must be part of some parent form object (i.e. you can not have Map
as the top form object) so that Spring is able to infer the generic types from the property getterThere are many ways how you can work with this. For example we have a special template subform, which is used when we need to dynamically add another subform. This approach is probably a bit more complex to follow:
<form:form action="${formUrl}" method="post" modelAttribute="organizationUsersForm">
<%-- ... other fields ... --%>
<div id="userSubforms">
<c:forEach items="${organizationUsersForm.users.entrySet()}" var="subformEntry">
<div data-subform-key="${subformEntry.key}">
<spring:nestedPath path="users['${subformEntry.key}']">
<%@ include file="user-subform.jspf" %>
</spring:nestedPath>
</div>
</c:forEach>
</div>
<button onclick="addSubform(jQuery('#userSubforms'), 'users', 'user', 'userTemplate');">ADD ANOTHER USER</button>
<%-- other form fields, submit, etc. --%>
</form:form>
<div class="hide" data-subform-template="user">
<spring:nestedPath path="userTemplate">
<%@ include file="user-subform.jspf" %>
</spring:nestedPath>
</div>
<script>
function addSubform(subformContainer, subformPath, templateName, templatePath) {
// Find the sequence number for the new subform
var existingSubforms = subformContainer.find("[data-subform-key]");
var subformIndex = (existingSubforms.length != 0) ?
parseInt(existingSubforms.last().attr("data-subform-key"), 10) + 1 : 0;
// Create new subform based on the template
var subform = jQuery('<div data-subform-key="' + subformIndex + '" />').
append(jQuery("[data-subform-template=" + templateName + "]").children().clone(true));
// Don't forget to update field names, identifiers and label targets
subform.find("[name]").each(function(node) {
this.name = subformPath + "["+ subformIndex +"]." + this.name;
});
subform.find("[for^=" + templatePath + "]").each(function(node) {
this.htmlFor = this.htmlFor.replace(templatePath + ".", subformPath + "["+ subformIndex +"].");
});
subform.find("[id^=" + templatePath + "]").each(function(node) {
this.id = this.id.replace(templatePath + ".", subformPath + "["+ subformIndex +"].");
});
// Add the new subform to the form
subformContainer.append(subform);
}
</script>
Now you can ask "How can user delete a subform"? This is pretty easy if the subform JSPF contains:
<button onclick="jQuery(this).parents('[data-subform-key]').remove();">DELETE USER</button>