Java Spring 3 MVC:动态表单中的一对多(在创建/更新时添加/删除)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9671640/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 05:56:52  来源:igfitidea点击:

Spring 3 MVC: one-to-many within a dynamic form (add/remove on create/update)

javahibernatespring-mvcone-to-manydynamicform

提问by sp00m

I'm looking for a solution to manage a one-to-many relation within an HTML formusing jQuery. I'm developing with Spring, Spring MVCand Hibernate. I found many tracks on the web, but not any working full-example.

我正在寻找一种解决方案来使用jQuery管理 HTML 表单中的一对多关系。我正在使用SpringSpring MVCHibernate 进行开发。我在网上找到了很多曲目,但没有找到任何有效的完整示例。

The background

背景

I've three JPA entities:

我有三个 JPA 实体:

Consult.java(1)

Consult.java(1)

@Entity
@Table(name = "consult")
public class Consult

    private Integer id;
    private String label;
    private Set<ConsultTechno> consultTechnos;

    /* getters & setters */

}

ConsultTechno.java(2)

ConsultTechno.java(2)

@Entity
@Table(name = "consult_techno")
public class ConsultTechno {

    private Integer id;
    private Techno techno;
    private Consult consult;
    private String level;

    /* getters & setters */

}

Techno.java(3)

Techno.java(3)

@Entity
@Table(name="techno")
public class Techno {

    private Integer id;
    private String label;
    private Set<ConsultTechno> consultTechnos;

    /* getters & setters */

}

As shown, a Consult (1) contains nConsultTechnos (2), which are caracterized by a leveland a Techno (3).

如图所示,一个Consult (1) 包含n 个ConsultTechnos (2),它们由一个级别和一个Techno (3)表征。

The needs

需求

Using an HTML form, I would like to have a Add a technobutton which dynamically adds two fields in the DOM:

使用 HTML 表单,我想要一个Add a techno按钮,可以在 DOM 中动态添加两个字段:

<input type="text" name="consult.consultTechnos[].techno.id" />
<input type="text" name="consult.consultTechnos[].level" />

Of course, each time the user clicks on the button, those two fields should be re-added, etc. I chose input type="text"for the example, but at the end, the fields will be two select.

当然,每次用户点击按钮时,应该重新添加这两个字段,等等。我选择input type="text"了示例,但最后,字段将是两个select

Four kinds of operation should be covered:

应涵盖四种操作:

  1. Adda child entity when creatinga new master entity
  2. Removea child entity when creatinga new master entity
  3. Adda child entity when updatinga new master entity
  4. Removea child entity when updatinga new master entity
  1. 创建新的主实体时添加子实体
  2. 创建新的主实体时删除子实体
  3. 更新新的主实体时添加子实体
  4. 更新新的主实体时删除子实体

The problem

问题

That layout partalready works, but when posting the form, I can't manage to bind the dynamically added fields to my @ModelAttribute consult.

布局部分已经有效,但是在发布表单时,我无法将动态添加的字段绑定到我的@ModelAttribute consult.

Do you have any idea of how to do that kind of jobs? I hope I've been clear enough...

你知道如何做这种工作吗?我希望我已经足够清楚了...

Thanks in advance :)

提前致谢 :)

采纳答案by sp00m

This point is still quite confusing and unclear on the web, so here is the way I solved my problem. This solution is probably not the most optimized one, but it works when creating and updatinga master entity.

这一点在网上还是比较混乱和不清楚的,所以这里是我解决问题的方法。此解决方案可能不是最优化的解决方案,但它在创建和更新主实体时有效。

Theory

理论

  1. Use a Listinstead of a Setfor your one-to-many relations which should be dynamically managed.

  2. Initialize your Listas an AutoPopulatingList. It's a lazy list which allows to adddynamically elements.

  3. Add an attribute removeof intto your child entity. This will play the part of a boolean flag and will be usefull when removingdynamically an element.

  4. When posting the form, persist only the elements that have the flag removeon 0(i.e. false).

  1. 对应该动态管理的一对多关系使用 aList而不是 a Set

  2. 将您的初始化ListAutoPopulatingList. 这是一个惰性列表,允许动态添加元素。

  3. 添加一个属性removeint,以你的子实体。这将起到布尔标志的作用,并且在动态删除元素时会很有用。

  4. 在发布形式,坚持只有具有该标志的元素remove0(即false)。

Practice

实践

A working full-example: an employer has many employees, an employee has one employer.

一个工作完整的例子:一个雇主有很多雇员,一个雇员有一个雇主。

Entities:

实体:

Employer.java

Employer.java

@Entity
@Table(name = "employer")
public class Employer

    private Integer id;

    private String firstname;
    private String lastname;
    private String company;

    private List<Employee> employees; // one-to-many

    /* getters & setters */

}

Employee.java

Employee.java

@Entity
@Table(name = "employee")
public class Employee {

    private Integer id;

    @Transient // means "not a DB field"
    private Integer remove; // boolean flag

    private String firstname;
    private String lastname;

    private Employer employer; // many-to-one

    /* getters & setters */

}

Controller:

控制器:

EmployerController.java

EmployerController.java

@Controller
@RequestMapping("employer")
public class EmployerController {

    // Manage dynamically added or removed employees
    private List<Employee> manageEmployees(Employer employer) {
        // Store the employees which shouldn't be persisted
        List<Employee> employees2remove = new ArrayList<Employee>();
        if (employer.getEmployees() != null) {
            for (Iterator<Employee> i = employer.getEmployees().iterator(); i.hasNext();) {
                Employee employee = i.next();
                // If the remove flag is true, remove the employee from the list
                if (employee.getRemove() == 1) {
                    employees2remove.add(employee);
                    i.remove();
                // Otherwise, perform the links
                } else {
                    employee.setEmployer(employer);
                }
            }
        }
        return employees2remove;
    }

    // -- Creating a new employer ----------

    @RequestMapping(value = "create", method = RequestMethod.GET)
    public String create(@ModelAttribute Employer employer, Model model) {
        // Should init the AutoPopulatingList
        return create(employer, model, true);
    }

    private String create(Employer employer, Model model, boolean init) {
        if (init) {
            // Init the AutoPopulatingList
            employer.setEmployees(new AutoPopulatingList<Employee>(Employee.class));
        }
        model.addAttribute("type", "create");
        return "employer/edit";
    }

    @RequestMapping(value = "create", method = RequestMethod.POST)
    public String create(@Valid @ModelAttribute Employer employer, BindingResult bindingResult, Model model) {
        if (bindingResult.hasErrors()) {
            // Should not re-init the AutoPopulatingList
            return create(employer, model, false);
        }
        // Call the private method
        manageEmployees(employer);
        // Persist the employer
        employerService.save(employer);
        return "redirect:employer/show/" + employer.getId();
    }

    // -- Updating an existing employer ----------

    @RequestMapping(value = "update/{pk}", method = RequestMethod.GET)
    public String update(@PathVariable Integer pk, @ModelAttribute Employer employer, Model model) {
        // Add your own getEmployerById(pk)
        model.addAttribute("type", "update");
        return "employer/edit";
    }

    @RequestMapping(value = "update/{pk}", method = RequestMethod.POST)
    public String update(@PathVariable Integer pk, @Valid @ModelAttribute Employer employer, BindingResult bindingResult, Model model) {
        // Add your own getEmployerById(pk)
        if (bindingResult.hasErrors()) {
            return update(pk, employer, model);
        }
        List<Employee> employees2remove = manageEmployees(employer);
        // First, save the employer
        employerService.update(employer);
        // Then, delete the previously linked employees which should be now removed
        for (Employee employee : employees2remove) {
            if (employee.getId() != null) {
                employeeService.delete(employee);
            }
        }
        return "redirect:employer/show/" + employer.getId();
    }

    // -- Show an existing employer ----------

    @RequestMapping(value = "show/{pk}", method = RequestMethod.GET)
    public String show(@PathVariable Integer pk, @ModelAttribute Employer employer) {
        // Add your own getEmployerById(pk)
        return "employer/show";
    }

}

View:

看法:

employer/edit.jsp

employer/edit.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%><%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"
%><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"
%><%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
%><!DOCTYPE HTML>
<html>
<head>

    <title>Edit</title>
    <style type="text/css">.hidden {display: none;}</style>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
    <script type="text/javascript">
    $(function() {

        // Start indexing at the size of the current list
        var index = ${fn:length(employer.employees)};

        // Add a new Employee
        $("#add").off("click").on("click", function() {
            $(this).before(function() {
                var html = '<div id="employees' + index + '.wrapper" class="hidden">';                    
                html += '<input type="text" id="employees' + index + '.firstname" name="employees[' + index + '].firstname" />';
                html += '<input type="text" id="employees' + index + '.lastname" name="employees[' + index + '].lastname" />';
                html += '<input type="hidden" id="employees' + index + '.remove" name="employees[' + index + '].remove" value="0" />';
                html += '<a href="#" class="employees.remove" data-index="' + index + '">remove</a>';                    
                html += "</div>";
                return html;
            });
            $("#employees" + index + "\.wrapper").show();
            index++;
            return false;
        });

        // Remove an Employee
        $("a.employees\.remove").off("click").on("click", function() {
            var index2remove = $(this).data("index");
            $("#employees" + index2remove + "\.wrapper").hide();
            $("#employees" + index2remove + "\.remove").val("1");
            return false;
        });

    });
    </script>

</head>
<body>

    <c:choose>
        <c:when test="${type eq 'create'}"><c:set var="actionUrl" value="employer/create" /></c:when>
        <c:otherwise><c:set var="actionUrl" value="employer/update/${employer.id}" /></c:otherwise>
    </c:choose>

    <form:form action="${actionUrl}" modelAttribute="employer" method="POST" name="employer">
        <form:hidden path="id" />
        <table>
            <tr>
                <td><form:label path="firstname">Firstname</form:label></td>
                <td><form:input path="firstname" /><form:errors path="firstname" /></td>
            </tr>
            <tr>
                <td><form:label path="lastname">Lastname</form:label></td>
                <td><form:input path="lastname" /><form:errors path="lastname" /></td>
            </tr>
            <tr>
                <td><form:label path="company">company</form:label></td>
                <td><form:input path="company" /><form:errors path="company" /></td>
            </tr>
            <tr>
                <td>Employees</td>
                <td>
                    <c:forEach items="${employer.employees}" varStatus="loop">
                        <!-- Add a wrapping div -->
                        <c:choose>
                            <c:when test="${employer.employees[loop.index].remove eq 1}">
                                <div id="employees${loop.index}.wrapper" class="hidden">
                            </c:when>
                            <c:otherwise>
                                <div id="employees${loop.index}.wrapper">
                            </c:otherwise>
                        </c:choose>
                            <!-- Generate the fields -->
                            <form:input path="employees[${loop.index}].firstname" />
                            <form:input path="employees[${loop.index}].lastname" />
                            <!-- Add the remove flag -->
                            <c:choose>
                                <c:when test="${employees[loop.index].remove eq 1}"><c:set var="hiddenValue" value="1" /></c:when>
                                <c:otherwise><c:set var="hiddenValue" value="0" /></c:otherwise>
                            </c:choose>
                            <form:hidden path="employees[${loop.index}].remove" value="${hiddenValue}" />
                            <!-- Add a link to remove the Employee -->
                            <a href="#" class="employees.remove" data-index="${loop.index}">remove</a>
                        </div>
                    </c:forEach>
                    <button id="add" type="button">add</button>
                </td>
            </tr>
        </table>
        <button type="submit">OK</button>
    </form:form>

</body>
</html>

Hope that could help :)

希望能有所帮助 :)

回答by pradeep

why u are using HTML input tag instead of spring taglib forms

为什么你使用 HTML 输入标签而不是 spring taglib 表单

   <form:form action="yourURL.htm" command="employeeDto">
    <form:input type="text" name="consult.consultTechnos[].techno.id" />
    <form:input type="text" name="consult.consultTechnos[].level" /> 
   <form:form>

and create one EmployeeDto class like, and add the modelMap.addAttribute("employeeDto", new Employee);in your controller

并创建一个 EmployeeDto 类,并将其添加到modelMap.addAttribute("employeeDto", new Employee);您的控制器中

回答by chakputth

Well i just got to the problem, the html view source will not show the dynamic html added. If you will inspect the html elements the DOM tree will show you all the dynamic elements added but on the Form Submit if you see all the elements will be submitted to the server including the dynamic created elements too.

好吧,我刚遇到问题,html 视图源不会显示添加的动态 html。如果您将检查 html 元素,DOM 树将向您显示添加的所有动态元素,但在表单提交上,如果您看到所有元素都将提交到服务器,包括动态创建的元素。

One way to reproduce is ob form submit call the javascript method and put a debug point in the javascript method and check for the form submit values in the document>form elements by inspecting the DOM tree

重现的一种方法是 ob 表单提交调用 javascript 方法并在 javascript 方法中放置一个调试点,并通过检查 DOM 树来检查文档>表单元素中的表单提交值