El aspecto del formulario en JSP/JSTL es el siguiente:
<form:form modelAttribute="combinacion" action="/" method="post"> <fmt:message key="home.label.minimum"/> <form:input path="min" type="number" /> <form:errors path="min" /><br> <fmt:message key="home.label.maximum"/> <form:input path="max" type="number" /><form:errors path="max" /><br> <fmt:message key="home.label.quantity"/> <form:input path="n" type="number" /> <form:errors path="n" /><br> <button type="submit"> <fmt:message key="home.button.submit"/> </button> </form:form>
Las etiquetas <form:errors ...> que vienen de <%@taglib prefix = "form" uri = "http://www.springframework.org/tags/form" %> ; mostrarán los errores en los campos.
Primero tendremos la clase en sí (genera una combinación de números n numeros aleatorios entre min y max):
import java.util.Random; import java.util.Set; import java.util.TreeSet; import javax.validation.constraints.Min; @CombGenConstraint public class GeneradorCombinacion { private Set<Integer> numbers; private int min; private int max; @Min(value=0, message="{combinacion.nError}") private int n; GeneradorCombinacion() { this(6, 1, 49); } GeneradorCombinacion (int n, int min, int max) { this.min=min; this.max=max; this.n=n; this.calcular(); } public void calcular() { int lmax=getMax(); int lmin=getMin(); int ln=getN(); if (lmax>lmin && lmax-lmin+1>=ln) { Random r=new Random(); numbers=new TreeSet<>(); while (numbers.size()<ln) { int number=r.nextInt(lmax-lmin+1)+lmin; if (!numbers.contains(number)) numbers.add(number); } } } public Set<Integer>getNumbers() { return numbers; } public int getMin() { return min; } public int getMax() { return max; } public int getN() { return n; } public void setMin(int min) { this.min=min; } public void setMax(int max) { this.max=max; } public void setN (int n) { this.n=n; } }
En el ejemplo anterior se anotaron validaciones:
- @Min(value=0, message="{combinacion.nError}") --> validación predefinida que podemos usar cuando queramos (hay muchas más).
- @CombGenConstraint --> validación creada para este caso concreto.
import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy; import javax.validation.Constraint; import javax.validation.Payload; @Documented @Constraint(validatedBy = CombGenValidator.class) @Target( { ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface CombGenConstraint { String message() default "{CombGenConstraint.NotValid}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
La parte de "message()..." define el mensaje asociado al error (que se mostrará al usuario), cuando se produzca este error. Si está entre llaves es porque puede ir en un archivo tipo ".properties".
La parte de "@Constraint(validatedBy = CombGenValidator.class)" indica que esto es una restricción y que es validada por la clase CombGenValidator.
La parte de @Target( { ElementType.TYPE }) indica que se puede aplicar a clases (sin fuera para atributos hay que poner ElementType.FIELD, sino recuerdo mal).
Y ahora la clase que hace la validación:
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class CombGenValidator implements ConstraintValidator<CombGenConstraint, GeneradorCombinacion> { @Override public boolean isValid(GeneradorCombinacion c, ConstraintValidatorContext context) { boolean b=c.getMax()>c.getMin() && c.getMax()-c.getMin()+1>=c.getN(); return b; } }
Aquí es mportante implementar la clase ConstraintValidator, poniendo la anotación asociada (CombGenConstraint en este caso), y la clase (superclase si fuese necesario) a la que se aplica (GeneradorCombinacion en este caso).
Los mensajes de validación de la restricción pueden ir en el archivo ValidationMessages.properties por ejemplo:
combinacion.nError=El valor máximo para la cantidad es 0.
CombGenConstraint.NotValid=Los datos insertados no permiten generar ninguna combinación.
También es lógico añadir los siguientes siguientes mensajes:
typeMismatch.min = Debe proporcionarse un valor válido para el mínimo.
typeMismatch.max = Debe proporcionarse un valor válido para el máximo.
typeMismatch.n = Debe proporcionarse un valor válido para n
Estos pueden ir por ejemplo en el archivo messages.properties, y están destinados a sobrescribir los mensajes de error por defecto producidos cuando no se pueda procesar la entrada recibido por el formulario.
El controlador podría ser así ahora (método homePost):
import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import javax.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller @RequestMapping("/") public class HomeController { @GetMapping public String homeGet (Model model, @RequestParam(defaultValue="6") int n, @RequestParam(defaultValue="1") int min, @RequestParam(defaultValue="49") int max) { model.addAttribute("combinacion", new GeneradorCombinacion(n, min, max)); return "home"; } @PostMapping public String homePost (@Valid @ModelAttribute("combinacion") GeneradorCombinacion combinacion, BindingResult result, Model model) { List<String> problemasGlobales=new ArrayList<>(); String problemasCampos=null; if (result.hasFieldErrors()) { List<FieldError> lfe=result.getFieldErrors(); problemasCampos=lfe.stream().map(fe->fe.getField()).collect(Collectors.joining(", ")); } for (ObjectError oe:result.getGlobalErrors()) problemasGlobales.add(oe.getDefaultMessage()); if (problemasGlobales.size()==0 && problemasCampos==null) { combinacion.calcular(); } else { model.addAttribute("problemasGlobales",problemasGlobales); model.addAttribute("problemasCampos",problemasCampos); } return "home"; } }
Y por último, el archivo JSP/JSTL podría ser así:
<%@taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %> <%@taglib prefix = "fmt" uri = "http://java.sun.com/jstl/fmt"%> <%@taglib prefix = "form" uri = "http://www.springframework.org/tags/form" %> <html> <head> <link rel="stylesheet" type="text/css" href="main.css"> <meta charset="UTF-8"> </head> <body> <H1><fmt:message key="home.listOfNumbers"/></H1> <c:if test="${not empty problemasGlobales or not empty problemasCampos}"> <ul> <c:if test="${not empty problemasGlobales}"> <c:forEach items="${problemasGlobales}" var="mensaje"> <li> <span class="error"> ${mensaje} </span> </li> </c:forEach> </c:if> <c:if test="${not empty problemasCampos}"> <fmt:message key="home.error.fields"> <fmt:param>${problemasCampos}</fmt:param> </fmt:message> </c:if> </ul> </c:if> <c:if test="${empty problemasGlobales and empty problemasCampos}"> <fmt:message key="home.range"> <fmt:param>${combinacion.getN()}</fmt:param> <fmt:param>${combinacion.getMin()}</fmt:param> <fmt:param>${combinacion.getMax()}</fmt:param> </fmt:message> <ul> <c:forEach items="${combinacion.getNumbers()}" var="numero"> <li> ${numero} </li> </c:forEach> </ul> </c:if> <form:form modelAttribute="combinacion" action="/" method="post"> <fmt:message key="home.label.minimum"/> <form:input path="min" type="number" /> <form:errors path="min" /><br> <fmt:message key="home.label.maximum"/> <form:input path="max" type="number" /><form:errors path="max" /><br> <fmt:message key="home.label.quantity"/> <form:input path="n" type="number" /> <form:errors path="n" /><br> <button type="submit"> <fmt:message key="home.button.submit"/> </button> </form:form> </body> </html>
Aquí la combinación solo se muestra si no hay errores en los campos.
Y esto es todo.
No hay comentarios :
Publicar un comentario