Spring MVC and form binding: how to remove an item from a list?

I have a Person model attribute containing an Email s list.
I created Javascript code that removes elements from a list of html email addresses. This is pure Javascript client code, without an AJAX call.

After the presentation, I don’t understand why I get all the letters in the corresponding @Controller method, even those that were deleted in html.

Can someone explain?

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> $(document).ready(function() { $('.trash').click(function() { $(this.parentNode).remove(); }); }); </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 id="emails"> <c:forEach items="${person.emails}" varStatus="status"> <li><form:hidden path="emails[${status.index}].order" class="emailsDisplayOrder"></form:hidden><form:input path="emails[${status.index}].label"></form:input><form:input type="email" path="emails[${status.index}].value"></form:input><input type="image" src="/resume/images/trash.png" class="trash" value="${status.index}"></input></li> </c:forEach> </ol> </td> </tr> </table> </form:form> </body> </html> 

Controller:

 @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/save") public String save(@ModelAttribute(value="person") Person p, BindingResult result, SessionStatus status) { new PersonValidator().validate(p, result); Collections.sort(p.getEmails()); //this collection still contains client-side dropped objects this.resumeManager.savePerson(p); return PERSON_VIEW_NAME; } } 
+6
source share
2 answers

Description

When you load a page with <form:form modelAttribute="person" ...> , there are two cases:

  • case 1: if person does not exist, it creates an empty person
  • case 2: if person already exists, he uses it

In all cases when the page loads, an existing person exists.
When you submit the form, Spring MVC only updates this existing person with the information provided.

So, in case 1, if you send emails 1, 2, 3 and 4, Spring MVC will add 4 letters to the empty person . No problem for you in this case.

But in case 2 (for example, when editing an existing person in a session), if you send emails 1 and 2, but the person already has 4 letters, then Spring MVC will simply replace email 1 and 2. Email 3 and 4 are still exist.


Possible Solution

Probably not the best, but it should work.

Add remove boolean to the Email class:

 ... public class Email { ... private boolean remove; // Set this flag to true to indicate that // you want to remove the person. ... } 

In the save method of your controller, delete messages for which remove set to true.

Finally, in your JSP add this hidden field:

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

And tell your Javascript to set the input value to true when the user clicks to delete the letter.

+6
source

Alternative Solution for Jerome Dalber

Jerome’s solution should work (thanks again for the clear answer and solution), but I didn’t want to change the business model.
So, here is how I found out: mark the HTML elements to be deleted using a java script and actually delete it using ajax calls in the form of submit (first I avoided ajax to keep the model unchanged until the user sends, but these solutions retain this requirement)

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> 

Controller:

 @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++; } } } 
0
source

Source: https://habr.com/ru/post/927571/


All Articles