Spring MVC binding a List of nested Custom types t

2019-04-29 20:29发布

The Case: I've an Organization Object. It has a list of Department Objects, and each Department has a list of Employee Objects.

In JSP, I have a checkbox list that binds a check box to an employee object (deep down 2 hierarchies. That is Organization->Department->Employee).

<input type="checkbox" name="adminDepartmentList[${status.index}].employeeList" value="${employee.firstName}"> <c:out value="${employee.firstName}" /><br>

As you can see:

adminDepartmentList[0].employeeList --> John
adminDepartmentList[2].employeeList --> Rose

The binding is good. After form is submitted, in the controller, I can loop over admin departmentList and find all departments created and find the employees that were created due to the checkbox selection.

The Issue :( The departments are created with null names and non-null employeeList. I cannot find the names of department to which the employeeList belongs :( So how can I pass some department-name so the name gets injected to the department (as it is being created) just like how the ".employeeList" is getting injected to the department.

The Details: To give you the details of my work:

An Organization class has two lists of Departments. A Department class has a list of Employees. Employee has first and last name and hourToWork.

public class Organization{
  private long id;
  private String name;
  private List<Department> adminDepartmentList;   //n admin departments
  private List<Department> employeeDepartmentList //m employee departments
// default constructor and all getters and setters
}

public class Department{
  private long id;
  private String name;
  private List<Employee> employeeList; //k employees
  //default constructor and all getters and setters
}

public class Employee{
  private long id;
  private String firstName;
  private String lastName;
  private int hoursToWork;  // to be filled from Spring MVC form
  //default contructor and all getters and setters
}

The list of Departments comes from an API. And all Employees of that department comes from another API.

I am writing a client that enables users to create "customized organizations" by first selecting the departments they are interested in and then for each department that was selected, the user selects a subset of employees from all employees related to that department.

So I have 3 JSP forms:

Organization form (organization.jsp): input field for the name of the organization and a check box list of all departments. User can select a set of Department for the new organization that's being created.

<form:form name='fs' action="department.htm"  method='POST' commandName="organization">
            Organization Name:
            <input type="text" name="name" >
            <!-- ============================================================== -->
            Departments:<br> Select admin-departments you want.
            <div class="checkbox-list">
            <%-- Size :<c:out value="${organization.adminDepartmentList.size}"/> --%>
                <c:forEach var="i" varStatus="status" items="${organization.adminDepartmentList}">
              <input type="checkbox" name="adminDepartmentList" value="${i.name}"> <c:out value="${i.name}" /><br>
                  </c:forEach>
            </div>
            <!-- ============================================================== -->
            Departments:<br> Select employee-departments you want.
            <div class="checkbox-list">
                <c:forEach var="i" varStatus="status" items="${organization.employeeDepartmentList}">            
                      <input type="checkbox" name="employeeDepartmentList" value="${i.name}"> <c:out value="${i.name}" /><br>
                  </c:forEach>
            </div>
            <button type="submit" class="btn btn-lg btn-primary btn-block">Next Step</button>
  </form:form>

Department form (department.jsp): For each of the departments that were selected, this show a check box list of employees to be selected for the department.

<form:form name='f' action="employee.htm"  method='POST' commandName="organization">
            Organization Name: <c:out value="${organization.name}" /><br>
            Select Employees you want for your new Departments.
            Admin Departments:<br>
                <c:forEach var="department" varStatus="status" items="${organization.adminDepartmentList}">
                ______Dept: <c:out value="${department.name}" /><br>
                <div class="checkbox-list">
                    <c:forEach var="employee" varStatus="status" items="${department.employeeList}">
                          <input type="checkbox" name="adminDepartmentList[${status.index}].employeeList" value="${employee.firstName}"> <c:out value="${employee.firstName}" /><br>
                    </c:forEach>
                </div>
                </c:forEach>

              Employee Departments:<br>
                <c:forEach var="department" varStatus="status" items="${organization.employeeDepartmentList}">
                ______Dept: <c:out value="${department.name}" /><br>
                <div class="checkbox-list">
                    <c:forEach var="employee" varStatus="status" items="${department.employeeList}">
                          <input type="checkbox" name="employeeDepartmentList[${status.index}].employeeList" value="${employee.firstName}"> <c:out value="${employee.firstName}" /><br>
                    </c:forEach>
                </div>
                </c:forEach>  

                  <button type="submit" class="btn btn-lg btn-primary btn-block">Next Step</button>
  </form:form>

Employee form (employee.jsp): For each of the employee that was selected in Department Form, this shows the employee name and a field to enter the hours that will be assigned to that employee.

The Employee Controller (the one that's getting null department-name)

@RequestMapping(value = "/employee", method = RequestMethod.POST)
  public String product_post(@ModelAttribute("organization") Organization organization, HttpServletRequest request,
            HttpServletResponse response, BindingResult result, ModelMap model) {
      System.out.println("=========== POST Employee CONTROLLER===============");
      //STEP 1. show me which employees are selected for each admin department.
      List<Department> adminDepartments = organization.getAdminDepartmentList();

      for(Department dept: adminDepartments){
        System.out.println("Admin Dept name::: " + dept.getName());  //<-----------Name Comes as null :(
        List<Employee> employeeList = dept.getEmployeeList();
        for(Employee emp: employeeList){
          System.out.println("Employee::"+ emp.getFirstName());
        }
      }

      //STEP 2. show me which employees are selected for each employee department.
      List<Department> employeeDepartments = organization.getEmployeeDepartmentList();
      for(Department dept: employeeDepartments){
        System.out.println("Employee Dept name::: " + dept.getName()); //<----------Name Comes as null :(
        List<Employee> employeeList = dept.getEmployeeList();
        for(Employee emp: employeeList){
          System.out.println("Employee::"+ emp.getFirstName());
        }
      }
      model.addAttribute("organization", organization);
    return "employee";
  }

The Known: I understand why department.name is not being injected, but I'm not sure how to solve it. How do I inject the department name from the outer loop to the inner loop that runs over the employee list in the below c:foreach? Or how to somehow bind it to each employee. I'm suspecting it's here:

Admin Departments:<br>
                    <c:forEach var="department" varStatus="status" items="${organization.adminDepartmentList}">
                    ______Dept: <c:out value="${department.name}" /><br>
                    <div class="checkbox-list">
                        <c:forEach var="employee" varStatus="status" items="${department.employeeList}">
                              <input type="checkbox" name="adminDepartmentList[${status.index}].employeeList" value="${employee.firstName}"> <c:out value="${employee.firstName}" /><br>
                        </c:forEach>
                    </div>
                    </c:forEach>

I tried hidden input to help create named departments but it didn't help.

The Credits:

Thanks so much for ur cooperation :)

For simplicity, assume FirstName for employee is unique. And department.name is also unique. Thanks.

1条回答
乱世女痞
2楼-- · 2019-04-29 20:40

The News: Oh wow, Holy Moly Cow, I solved it.

The Missing Part: hidden input that tells what department to create and what name to inject to it.

<form:hidden path="adminDepartmentList[${statusDepartment.index}].name"  value="${department.name}" />

Injecting department name to each of the department that was being created by spring.

The Whole Solution: department.jsp

<form:form name='f' action="employee.htm"  method='POST' commandName="organization">
              Organization Name: <c:out value="${organization.name}" /><br>
              Select Employees you want for your new Departments.
              Admin Departments:<br>
                  <c:forEach var="department" varStatus="statusDepartment" items="${organization.adminDepartmentList}">
                      ______Dept: <c:out value="${department.name}" /><br>
                      <div class="checkbox-list">
                            <form:hidden path="adminDepartmentList[${statusDepartment.index}].name"  value="${department.name}" />
                            <c:forEach var="employee" varStatus="statusEmployee" items="${department.employeeList}">
                                <form:checkbox path="adminDepartmentList[${statusDepartment.index}].employeeList" value="${employee.firstName}"/> <c:out value="${employee.firstName}" /><br>
                            </c:forEach>
                      </div>
                  </c:forEach>

                Employee Departments:<br>
                  <c:forEach var="department" varStatus="statusDepartment" items="${organization.employeeDepartmentList}">
                      ______Dept: <c:out value="${department.name}" /><br>
                      <div class="checkbox-list">
                            <form:hidden path="employeeDepartmentList[${statusDepartment.index}].name" value="${department.name}" />
                            <c:forEach var="employee" varStatus="statusEmployee" items="${department.employeeList}">
                                <form:checkbox path="employeeDepartmentList[${statusDepartment.index}].employeeList" value="${employee.firstName}"/> <c:out value="${employee.firstName}" /><br>
                            </c:forEach>
                      </div>
                  </c:forEach>  

                    <button type="submit" class="btn btn-lg btn-primary btn-block">Next Step</button>
    </form:form>
查看更多
登录 后发表回答