Je cherche une solution pour gérer une relation un-à-plusieurs dans un formulaire HTML en utilisant jQuery. Je développe avec Spring, Spring MVC et Hibernate. J'ai trouvé de nombreuses pistes sur le Web, mais aucun exemple complet ne fonctionnait.
J'ai trois entités 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 */
}
Comme indiqué, un Consult (1) contient n ConsultTechnos (2), qui sont caractérisés par un niveau et une Techno (3).
En utilisant un formulaire HTML, j'aimerais avoir un Add a techno
bouton qui ajoute dynamiquement deux champs dans le DOM:
<input type="text" name="consult.consultTechnos[].techno.id" />
<input type="text" name="consult.consultTechnos[].level" />
Bien sûr, chaque fois que l'utilisateur clique sur le bouton, ces deux champs doivent être rajoutés, etc. J'ai choisi input type="text"
pour l'exemple, mais à la fin, les champs seront deux select
.
Quatre types d'opérations devraient être couverts:
Cette partie de mise en page fonctionne déjà, mais lors de la publication du formulaire, je n'arrive pas à lier les champs ajoutés dynamiquement à mon @ModelAttribute consult
.
Avez-vous une idée de comment faire ce genre de travail? J'espère avoir été assez clair ...
Merci d'avance :)
Ce point est encore assez confus et peu clair sur le Web, voici donc la façon dont j'ai résolu mon problème. Cette solution n'est probablement pas la plus optimisée, mais elle fonctionne lorsque crée et met à jour une entité maître.
Utilisez un List
au lieu d'un Set
pour vos relations un-à-plusieurs qui doivent être gérées dynamiquement.
Initialisez votre List
sous la forme d'un AutoPopulatingList
. C'est une liste paresseuse qui permet d'ajouter dynamiquement des éléments.
Ajoutez un attribut remove
de int
à votre entité enfant. Cela jouera le rôle d'un drapeau booléen et sera utile lorsque supprimera dynamiquement un élément.
Lors de la publication du formulaire, ne conservez que les éléments qui ont le drapeau remove
sur 0
(c'est-à-dire false
).
Un exemple complet: un employeur a de nombreux employés, un employé a un employeur.
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";
}
}
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>
J'espère que cela pourrait aider :)
pourquoi vous utilisez la balise d'entrée HTML au lieu des formulaires taglib de printemps
<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>
et créez une classe EmployeeDto comme, et ajoutez la modelMap.addAttribute("employeeDto", new Employee);
dans votre contrôleur
Eh bien, je viens de résoudre le problème, la source de vue html n'affichera pas le HTML dynamique ajouté. Si vous inspectez les éléments html, l'arborescence DOM vous montrera tous les éléments dynamiques ajoutés mais sur le formulaire Soumettre si vous voyez que tous les éléments seront soumis au serveur, y compris les éléments créés dynamiques.
Une façon de se reproduire est ob form submit, appelez la méthode javascript et placez un point de débogage dans la méthode javascript et vérifiez les valeurs de formulaire submit dans le document> éléments de formulaire en inspectant l'arborescence DOM