Java SpringMVC和表单绑定:如何从列表中删除项?

Java SpringMVC和表单绑定:如何从列表中删除项?,java,javascript,jquery,spring,spring-mvc,Java,Javascript,Jquery,Spring,Spring Mvc,我有一个Personmodel属性,其中包含电子邮件列表 我创建了一些JavaScript代码,可以从电子邮件的HTML列表中删除元素。这是纯JavaScript客户端代码,没有AJAX调用 提交后,我不明白为什么我会在相应的@Controller方法中获得所有的电子邮件,甚至是那些在HTML中被删除的电子邮件 谁能解释一下吗 JSP 解释 加载带有的页面时,有两种情况: 案例1:如果person不存在,它将创建一个空的person 案例2:如果person已经存在,它将使用它 在所有情况

我有一个
Person
model属性,其中包含电子邮件列表

我创建了一些JavaScript代码,可以从电子邮件的HTML列表中删除元素。这是纯JavaScript客户端代码,没有AJAX调用

提交后,我不明白为什么我会在相应的
@Controller
方法中获得所有的电子邮件,甚至是那些在HTML中被删除的电子邮件

谁能解释一下吗


JSP 解释 加载带有
的页面时,有两种情况:

  • 案例1:如果
    person
    不存在,它将创建一个空的
    person
  • 案例2:如果
    person
    已经存在,它将使用它
在所有情况下,加载页面时,都会有一个现有的
人员

当您提交表单时,Spring MVC仅使用提交的信息更新现有的
人员

因此,在案例1中,如果您提交电子邮件1、2、3和4,Spring MVC将向空的
人员添加4封电子邮件。在这种情况下你没问题

但在案例2中(例如,当您在会话中编辑现有的
人员时),如果您提交了电子邮件1和2,但该人员已经有4封电子邮件,则Spring MVC将仅替换电子邮件1和2。电子邮件3和4仍然存在


可能的解决办法 也许不是最好的,但它应该能起作用

remove
布尔值添加到
Email
类:

...
public class Email {

    ...

    private boolean remove; // Set this flag to true to indicate that 
                            // you want to remove the person.

    ...

}
在控制器的
save
方法中,删除
remove
设置为true的电子邮件

最后,在JSP中添加以下隐藏字段:

<form:hidden path="emails[${status.index}].remove" />


当用户单击删除电子邮件时,告诉Javascript将输入值设置为true。

Jerome Dalbert one的替代解决方案

Jerome的解决方案应该有效(再次感谢您提供了清晰的答案和解决方案),但我不想修改业务模型。
我发现的方法是:使用java脚本标记要删除的HTML元素,并在表单提交时使用ajax调用实际删除它(我最初避免使用ajax在用户提交之前保持模型不变,但此解决方案保留了该要求)

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" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>
    <link rel="stylesheet" href="<c:url value="/styles/resume.css"/>" type="text/css"></link>
    <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"></link>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
    <script src="/resume/js/jquery.editable-1.0.1.js"></script>
    <title>Resumes manager</title>

    <script>
        $('.sortable').sortable({
            update: function(event,ui) {
                var liElements = this.getElementsByTagName('li');
                $(liElements).each(function(i, liElement) {
                    var orderElements = liElement.getElementsByClassName('order');
                    $(orderElements).each(function (j, orderElement) {
                        orderElement.value = i;
                    });
                });
            }
        });

        $('.trashable').click(function() {
            $(this.parentNode.childNodes).each(function(index, element) {
                if(element.src.match(/trash.png/) != null) {
                    element.src = '/resume/images/back.png';
                    this.parentNode.className = 'trashed';
                } else if(element.src.match(/back.png/) != null) {
                    element.src = '/resume/images/trash.png';
                    this.parentNode.className = '';
                } else {
                    element.disabled = !element.disabled;
                }
            });
        });

        function trash(element) {
            var sfx = element.alt;
            var lnk = ('/resume/person/edit/').concat(sfx);
            $.ajax({
                url: lnk
            });
        }

        $('#personForm').submit(function() {
            var trashed = $(this).find('.trashed');
            $(trashed).each(function(index, element) {
                var img = $(element).find('.trashable');
                var tmp = $(img)[0];
                trash(tmp);
            });
        });
    });

    </script>
</head>

<body>
    <h1>Personal data</h1>
    <form:form modelAttribute="person" action="/resume/person/edit/save" id="personForm" method="post" >
        <table>
            <tr>
                <td>Email addresses:</td>
                <td colspan="4">
                    <ol class="sortable">
                        <c:forEach items="${person.emails}" varStatus="status">
                            <li><form:hidden path="emails[${status.index}].order" class="order"></form:hidden><form:input path="emails[${status.index}].label"></form:input><form:input type="email" path="emails[${status.index}].value"></form:input><img src="/resume/images/trash.png" class="trashable" alt="dropEmail/${person.emails[status.index].id}"></img></li>
                        </c:forEach>
                    </ol>
                </td>
            </tr>
            <tr><td colspan="5"><form:button name="save" value="${person.id}">${person.id == 0 ? 'save' : 'update'}</form:button></td></tr>
        </table>
    </form:form>
</body>

</html> 

你的意思是删除
  • 和元素?也许有些代码会更清晰,我的意思是带有所有子树的“li”节点(因此,是的,包含的“input”元素也会被删除)。我刚刚编辑添加了JSP代码,这是您所期望的吗?是的,谢谢,您的问题现在更清楚了,这回答了我的问题,但引出了另一个问题:我应该如何删除列表项?@JeromeDalbert我正在尝试实现您建议的技巧。但是,在删除隐藏输入并发布表单之前,将其值更改为false并不起作用。我的意思是,提交后该值不会更改。。你对此有什么想法吗?
    <form:hidden path="emails[${status.index}].remove" />
    
    <%@ 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" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>
        <link rel="stylesheet" href="<c:url value="/styles/resume.css"/>" type="text/css"></link>
        <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"></link>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
        <script src="/resume/js/jquery.editable-1.0.1.js"></script>
        <title>Resumes manager</title>
    
        <script>
            $('.sortable').sortable({
                update: function(event,ui) {
                    var liElements = this.getElementsByTagName('li');
                    $(liElements).each(function(i, liElement) {
                        var orderElements = liElement.getElementsByClassName('order');
                        $(orderElements).each(function (j, orderElement) {
                            orderElement.value = i;
                        });
                    });
                }
            });
    
            $('.trashable').click(function() {
                $(this.parentNode.childNodes).each(function(index, element) {
                    if(element.src.match(/trash.png/) != null) {
                        element.src = '/resume/images/back.png';
                        this.parentNode.className = 'trashed';
                    } else if(element.src.match(/back.png/) != null) {
                        element.src = '/resume/images/trash.png';
                        this.parentNode.className = '';
                    } else {
                        element.disabled = !element.disabled;
                    }
                });
            });
    
            function trash(element) {
                var sfx = element.alt;
                var lnk = ('/resume/person/edit/').concat(sfx);
                $.ajax({
                    url: lnk
                });
            }
    
            $('#personForm').submit(function() {
                var trashed = $(this).find('.trashed');
                $(trashed).each(function(index, element) {
                    var img = $(element).find('.trashable');
                    var tmp = $(img)[0];
                    trash(tmp);
                });
            });
        });
    
        </script>
    </head>
    
    <body>
        <h1>Personal data</h1>
        <form:form modelAttribute="person" action="/resume/person/edit/save" id="personForm" method="post" >
            <table>
                <tr>
                    <td>Email addresses:</td>
                    <td colspan="4">
                        <ol class="sortable">
                            <c:forEach items="${person.emails}" varStatus="status">
                                <li><form:hidden path="emails[${status.index}].order" class="order"></form:hidden><form:input path="emails[${status.index}].label"></form:input><form:input type="email" path="emails[${status.index}].value"></form:input><img src="/resume/images/trash.png" class="trashable" alt="dropEmail/${person.emails[status.index].id}"></img></li>
                            </c:forEach>
                        </ol>
                    </td>
                </tr>
                <tr><td colspan="5"><form:button name="save" value="${person.id}">${person.id == 0 ? 'save' : 'update'}</form:button></td></tr>
            </table>
        </form:form>
    </body>
    
    </html> 
    
    @Controller
    @SessionAttributes(types={Person.class}, value={"person"})
    public class PersonController {
    
        private final static String PERSON_VIEW_NAME = "person-form";
    
        private ResumeManager resumeManager;
    
        @Autowired()
        public PersonController(ResumeManager resume) {
            this.resumeManager = resume;
        }
    
        @InitBinder
        public void initBinder(WebDataBinder dataBinder) {
            dataBinder.setDisallowedFields("id");
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            dateFormat.setLenient(false);
            dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
        }
    
        @RequestMapping(value="/person/edit/{id}")
        public String edit(@PathVariable("id") long personId, Model model) {
            Person p = this.resumeManager.getPersonById(personId);
    
            if(p != null) {
                model.addAttribute("person", p);
    
                return PERSON_VIEW_NAME;
    
            } else {
                return "redirect:/";
            }
        }
    
        @RequestMapping(value="/person/edit/save")
        public String save(@ModelAttribute(value="person") Person p, BindingResult result, SessionStatus status) {
            new PersonValidator().validate(p, result);
            Collections.sort(p.getEmails());
    
            this.resumeManager.savePerson(p);
    
            return PERSON_VIEW_NAME;
        }
    
        @RequestMapping(value="/person/edit/dropEmail/{id}")
        @ResponseBody
        public void dropEmail(@ModelAttribute(value="person") Person p, @PathVariable("id") long id) {
            int i = 0;
            for(Email e : p.getEmails()) {
                if(e.getId() == id) {
                    p.getEmails().remove(i);
                    break;
                }
                i++;
            }
        }
    }