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

2023-11-23

我正在寻找解决方案管理 HTML 表单中的一对多关系 using jQuery。我正在开发Spring, 春季MVC and 休眠。我在网上找到了很多曲目,但没有任何有效的完整示例。

的背景

我有三个 JPA 实体:

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)

@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)

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

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

    /* getters & setters */

}

如图所示,咨询 (1) 包含n请参阅 Technos (2),其特点是level和一首 Techno (3)。

需求

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

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

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

应涵盖四种操作:

  1. Add子实体,当creating一个新的主实体
  2. Remove子实体,当creating一个新的主实体
  3. Add子实体,当updating一个新的主实体
  4. Remove子实体,当updating一个新的主实体

问题

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

您知道如何做此类工作吗?我希望我已经说得够清楚了...

提前致谢:)


这一点在网上仍然很混乱和不清楚,所以这是我解决问题的方法。该解决方案可能不是最优化的解决方案,但它在以下情况下有效:创建和更新一个主实体。

Theory

  1. Use a List代替Set对于应该动态管理的一对多关系。

  2. 初始化你的List as an AutoPopulatingList。这是一个惰性列表,允许add动态元素。

  3. 添加属性remove of int到您的子实体。这将起到布尔标志的作用,并且在以下情况下很有用:removing动态地一个元素。

  4. 发布表单时,仅保留具有该标志的元素remove on 0 (i.e. false).

Practice

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

实体:

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

@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 */

}

控制器:

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

<%@ 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>

希望能有所帮助:)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 如何使用 C 的 mmap() 更改文本文件中的字符?

    假设我将标准的 Hello World n 保存到名为 hello txt 的文本文件中 如果我想将 H 更改为 R 或其他内容 我可以使用 mmap 来实现吗 mmap标准 C99 或 C11 规范中不存在 它是在 POSIX 中定义的
  • 正则表达式 - 匹配不带空格的字符串

    构建一个正则表达式 该表达式将拒绝包含空格的输入字符串 我有以下表达式 但它不起作用 a zA Z0 9 S 有效案例 String123 test string without space 无效案例 String123 test cont
  • 我们如何存储到 NSDictionary 中? NSDictionary 和 NSMutableDictionary 有什么区别?

    我正在开发一个应用程序 我想在其中使用NSDictionary 任何人都可以给我发送一个示例代码来解释如何使用NSDictionary用一个完美的例子来存储数据 The NS词典 and NSMutableDictionary文档可能是您最
  • 您可以以多快的速度自动增加 Firebase 实时数据库上的值?

    火力战士在这里 当我最近tweeted关于新的increment Firebase实时数据库中的操作员 有队友问有多快increment is 我一直想知道同样的问题 你能以多快的速度增加一个值increment 1 与此相比如何使用事务来
  • C++11:按值传递是否涉及移动语义?

    我有一个如下所示的 API void WriteDefaultFileOutput std wostream str std wstring target Some code that modifies target before prin
  • 撰写布局:相对于居中项目对齐(例如 toRightOf)

    Compose 中有没有一种方法可以在不使用的情况下将可组合项与居中项目对齐ConstraintLayout I want to achieve this I could use a Spacer and Weights like this
  • 这些自执行匿名函数(又名 IIFE)实现有什么区别

    在很多书中 博客文章自调用匿名函数模式是这样写的 function var foo bar 然而运行JSLint对此给出了这个错误 将调用移至包含该函数的括号中 例如将其更改为这样的作品 function var foo bar 问题 为什
  • 如何使用 vbscript 获取正在运行的 Excel 实例的工作簿名称?

    Dim objXL strMessage On Error Resume Next Set objXl GetObject Excel Application If Not TypeName objXL Empty then strMess
  • Ember 表与 Ember 模型/Ember 数据集成

    我试图将 ember models 链接到 ember table 以从服务器提取分页记录 并在向下滚动时将它们添加到表中 我可以通过只请求我的 api url 和页码来让它工作 就像 ajax 示例中那样http addepar gith
  • 方法与函数,以及其他问题

    对于JS来说 两者有什么区别呢 我知道方法与对象相关联 但我很困惑函数的目的是什么 它们各自的语法有何不同 另外 这两种语法有什么区别 var myFirstFunc function param Do something and func
  • CsvHelper ConvertUsing 不更改输出

    我正在尝试使用ConvertUsing的方法CSV助手库 v 2 4 0 我已阅读有关的文档转换使用但无法让它发挥作用 我正在使用一个简单的类 public class Test public long Id get set public
  • Python Pandas

    我正在尝试对连续的相同信息进行分组和计数 Functions def postal saude global df lista solic List of solicitantes in Postal Saude list sol list
  • 如何从Firebase配置中获取measurementId?

    和玩谷歌分析对于 Firebase 上基于 Web 的项目 我们需要使用 Firebase 保留 URL 或按照文档所述复制 更新 Firebase 配置对象here 我正在使用以下命令来打印该配置信息 firebase setup web
  • VS Code 根据文件中的单词自动完成

    我刚刚开始使用 VS Code 目前我对此非常满意 我来自 Notepad 对于我正在做的事情 我没有找到任何处于同一 级别 的 IDE 直到现在 我真的很喜欢 VS Code 所做的事情以及所有现代集成技术如何帮助我 但我怀念 NPP 所
  • 如何增加 Symfony 2 表单上文件的上传限制?

    我在 Symfony 中有一个表单 用户可以将文件上传到其中 这些文件的大小最大可达 50Mb 但是 当我尝试上传大约 10Mb 的文件 在此之前 文件不大于 7 2Mb 时 表单会重新加载并出现以下错误 上传的文件太大 请尝试上传较小的文
  • ImageButton 不显示特定的可绘制对象

    这是我遇到过的一个相当有趣的问题 我的表格布局有 9 个图像按钮 每行 3 个 每个 ImageButton 都有一个与之关联的不同图像 我已将图像按钮的背景设置为透明 00000000 现在有趣的事情发生了 其中一张图像没有显示在模拟器
  • 如何处理 Win32 错误消息的占位符?

    我想在我的程序遇到 Win32 错误时显示有意义的错误消息 我打电话GetLastError 进而FormatMessage 但某些错误消息包含占位符 例如 ERROR BAD EXE FORMAT有文字 1 不是有效的 Win32 应用程
  • 如何在Python中使用cv2和多重处理并行从视频中获取帧

    我一直在 python 中使用 cv2 和多处理 我终于有了一个工作脚本 一旦各个帧已经在输入队列中 它就会对它们进行处理 但是 我想首先通过使用多个核心来加快将帧放入队列的速度 因此我尝试使用相同的多处理方法将每个图像读入队列 但我似乎无
  • python 3.5 上的 PyHook

    我正在尝试在 python 上编写一个基本的键盘记录程序 我需要安装 pywin32 和 pyhook 模块 我已经成功安装了 pywin32 但似乎无法 pyhook 工作 我已经读到它可以在更高版本的 python 上工作 但似乎无法弄
  • Spring 3 MVC:动态表单中的一对多(在创建/更新时添加/删除)

    我正在寻找解决方案管理 HTML 表单中的一对多关系 using jQuery 我正在开发Spring 春季MVC and 休眠 我在网上找到了很多曲目 但没有任何有效的完整示例 的背景 我有三个 JPA 实体 Consult java 1