java - requestmapping - spring mvc
Spring 3 MVC: uno-a-muchos dentro de una forma dinĂ¡mica(agregar/eliminar en crear/actualizar) (3)
Estoy buscando una solución para administrar una relación de uno a muchos dentro de un formulario HTML usando jQuery . Estoy desarrollando Spring , Spring MVC e Hibernate . Encontré muchas pistas en la web, pero no hay ningún ejemplo completo que funcione.
El fondo
Tengo tres entidades 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 */
}
Como se muestra, un Consult (1) contiene n ConsultTechnos (2), que se caracterizan por un nivel y un Techno (3).
Las necesidades
Usando un formulario HTML, me gustaría tener un botón Add a techno
que agrega dinámicamente dos campos en el DOM:
<input type="text" name="consult.consultTechnos[].techno.id" />
<input type="text" name="consult.consultTechnos[].level" />
Por supuesto, cada vez que el usuario haga clic en el botón, esos dos campos se deben volver a agregar, etc. Elegí input type="text"
para el ejemplo, pero al final, los campos serán dos select
.
Se deben cubrir cuatro tipos de operaciones:
- Agregue una entidad secundaria al crear una nueva entidad maestra
- Eliminar una entidad secundaria al crear una nueva entidad maestra
- Agregue una entidad secundaria al actualizar una nueva entidad maestra
- Eliminar una entidad hijo al actualizar una nueva entidad maestra
El problema
Esa parte de diseño ya funciona, pero al publicar el formulario, no puedo vincular los campos agregados dinámicamente a mi @ModelAttribute consult
.
¿Tienes alguna idea de cómo hacer ese tipo de trabajos? Espero haber sido lo suficientemente claro ...
Gracias de antemano :)
¿Por qué está usando la etiqueta de entrada HTML en lugar de las formas de taglib de primavera?
<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>
y crear una clase EmployeeDto como, y agregar el modelMap.addAttribute("employeeDto", new Employee);
en tu controlador
Bueno, acabo de llegar al problema, la fuente de vista html no mostrará el html dinámico agregado. Si va a inspeccionar los elementos html, el árbol DOM le mostrará todos los elementos dinámicos agregados, pero en el formulario Enviar, si ve todos los elementos se enviarán al servidor, incluidos los elementos dinámicos creados también.
Una forma de reproducir es ob enviar llamar al método javascript y poner un punto de depuración en el método javascript y verificar los valores de envío de formulario en el documento> elementos de formulario inspeccionando el árbol DOM
Este punto todavía es bastante confuso y poco claro en la web, así que aquí está la manera en que resolví mi problema. Esta solución probablemente no sea la más optimizada, pero funciona al crear y actualizar una entidad maestra.
Teoría
Utilice una
List
lugar de unSet
para sus relaciones de uno a muchos, que deben administrarse dinámicamente.Inicialice su
List
comoAutoPopulatingList
. Es una lista perezosa que permite agregar elementos dinámicamente.Agregue un atributo
remove
ofint
a su entidad hijo. Esto jugará la parte de una bandera booleana y será útil al eliminar dinámicamente un elemento.Al publicar el formulario, persista solo los elementos que tienen el indicador
remove
en0
(es decir,false
).
Práctica
Un ejemplo completo de trabajo: un empleador tiene muchos empleados, un empleado tiene un solo empleador.
Entidades:
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 */
}
Controlador:
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";
}
}
Ver:
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>
Espero que pueda ayudar :)