21.11.18

Spring: ejemplo de uso de data binding en formularios

Podríamos tener una clase que contendría la estructura de los datos a recibir (Combinacion en este caso), y un método para manejar la petición POST (homePost en este caso):

import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
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  {

    public static class Combinacion {
        private Set<Integer> numbers;                        
        private int min;
        private int max;
        private int n;
        
        Combinacion()
        {
            this(6, 1, 49);
        }

        Combinacion (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();
            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 Math.min(min,max);
        }

        public int getMax() {            
            return Math.max(Math.max(min,max),getMin()+getN());
        }

        public int getN() {
            return n>0?n:0;
        }

        public void setMin(int min) {
            this.min=min;            
        }

        public void setMax(int max) {
            this.max=max;            
        }

        public void setN (int n) {
            this.n=n;            
        }
    }    
    
    @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 Combinacion(n, min, max));        
        return "home";
    }    

    @PostMapping
    public String homePost (@ModelAttribute("combinacion") Combinacion combinacion, BindingResult result, Model model)
    {
       if (result.hasErrors())
        {
            List<FieldError> lfe=result.getFieldErrors();
            String listaParam=lfe.stream().map(fe->fe.getField()).collect(Collectors.joining(", "));
            model.addAttribute("mensaje", "Error en los parámetros: "+listaParam);
        }
        combinacion.calcular();
       
       model.addAttribute("combinacion", combinacion);        
       return "home";
    }
    
}

La clase Combinacion sería a la que se mapearía la entrada (gracias a @ModelAttribute), y después, el BindingResult contiene información de si se pudo o no hacer el binding (y errores de la validación si la hubiera... que sería lo mejor).

Después en el JSP/JSTL pues simplemente se crea un formulario para enviar los datos vía POST a ese manejador anterior usando las etiquetas http://www.springframework.org/tags/form:

<%@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 mensaje}">
            <P class="error">
                ${mensaje}
            </P>
        </c:if>
        <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>     
                
        <form:form modelAttribute="combinacion" action="/" method="post">
            Mínimo: <form:input path="min" type="number" /><br>
            Máximo: <form:input path="max" type="number" /><br>
            Numeros: <form:input path="n" type="number" /><br>
            <button type="submit">
                Enviar!
            </button>
        </form:form>
    </body>
</html>

En el formulario "<form:form...>" el action no es necesario cuando el controlador no es otro... pero no está de más. En ese formulario es importante el atributo modelAttribute (que debe coincidir con el @ModelAttribute del método que maneja la petición POST) y los atributos path de los <form:input ...>, que deben coincidir con el campo del objeto destino (en este caso el objeto Combinacion).

Y esto es todo.


No hay comentarios :