Guía: Thymeleaf + Spring. Parte 2

Primera parte
Tercera parte

5 Mostrar datos de inicio de semillas



Lo primero que muestra nuestra página /WEB-INF/templates/seedstartermng.html es una lista con los datos de inicio iniciales que se guardan actualmente. Para hacer esto, necesitamos algunos mensajes externos, así como algunos trabajos de expresión para los atributos del modelo. Así:

<div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}"> <h2 th:text="#{title.list}">List of Seed Starters</h2> <table> <thead> <tr> <th th:text="#{seedstarter.datePlanted}">Date Planted</th> <th th:text="#{seedstarter.covered}">Covered</th> <th th:text="#{seedstarter.type}">Type</th> <th th:text="#{seedstarter.features}">Features</th> <th th:text="#{seedstarter.rows}">Rows</th> </tr> </thead> <tbody> <tr th:each="sb : ${allSeedStarters}"> <td th:text="${{sb.datePlanted}}">13/01/2011</td> <td th:text="#{|bool.${sb.covered}|}">yes</td> <td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td> <td th:text="${#strings.arrayJoin( #messages.arrayMsg( #strings.arrayPrepend(sb.features,'seedstarter.feature.')), ', ')}">Electric Heating, Turf</td> <td> <table> <tbody> <tr th:each="row,rowStat : ${sb.rows}"> <td th:text="${rowStat.count}">1</td> <td th:text="${row.variety.name}">Thymus Thymi</td> <td th:text="${row.seedsPerCell}">12</td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> 


Hay mucho que ver Miremos cada fragmento por separado.

En primer lugar, esta sección se mostrará solo si hay iniciadores de semillas. Logramos esto con el atributo th: never y la función # lists.isEmpty (...) .

 <div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}"> 


Tenga en cuenta que todos los objetos de utilidad, como #lists , están disponibles en las expresiones Spring EL de la misma manera que en las expresiones OGNL en el dialecto estándar.

Lo siguiente que debe ver es una gran cantidad de textos internacionalizados (externalizados), como:

<h2 th: text = "# {title.list}"> Lista de iniciadores de semillas

 <table> <thead> <tr> <th th:text="#{seedstarter.datePlanted}">Date Planted</th> <th th:text="#{seedstarter.covered}">Covered</th> <th th:text="#{seedstarter.type}">Type</th> <th th:text="#{seedstarter.features}">Features</th> <th th:text="#{seedstarter.rows}">Rows</th> ... 


Esta es una aplicación Spring MVC, ya hemos definido el bean MessageSource en nuestra configuración Spring (los objetos MessageSource son la forma estándar de controlar textos externos en Spring MVC):

 @Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("Messages"); return messageSource; } 


... y esta propiedad basename indica que tendremos archivos en nuestro classpath, como Messages_es.properties o Messages_en.properties . Veamos la versión en español:

 title.list=Lista de semilleros date.format=dd/MM/yyyy bool.true=sí bool.false=no seedstarter.datePlanted=Fecha de plantación seedstarter.covered=Cubierto seedstarter.type=Tipo seedstarter.features=Características seedstarter.rows=Filas seedstarter.type.WOOD=Madera seedstarter.type.PLASTIC=Plástico seedstarter.feature.SEEDSTARTER_SPECIFIC_SUBSTRATE=Sustrato específico para semilleros seedstarter.feature.FERTILIZER=Fertilizante seedstarter.feature.PH_CORRECTOR=Corrector de PH 


En la primera columna de la tabla mostramos la fecha en que se preparó el iniciador. Pero mostraremos que está formateado como lo definimos en nuestro DateFormatter . Para hacer esto, utilizaremos la sintaxis de doble paréntesis ( $ {{...}} ), que aplicará automáticamente el servicio de conversión Spring, incluido el DateFormatter, que registramos al configurar.

 <td th:text="${{sb.datePlanted}}">13/01/2011</td> 


A continuación se muestra si el contenedor de semillas iniciador de semillas está cubierto o no al convertir el valor de propiedad del contenedor cubierto booleano en un sí o no internacionalizado con una expresión de búsqueda literal:

 <td th:text="#{|bool.${sb.covered}|}">yes</td> 


Ahora necesitamos mostrar el tipo del contenedor inicial de inicio de semillas. El tipo es una enumeración de Java con dos valores ( WOOD y PLASTIC ) y, por lo tanto, definimos dos propiedades en nuestro archivo Messages con los nombres seedstarter.type.WOO D y seedstarter.type.PLASTIC .

Pero para obtener nombres de tipo internacionalizados, necesitamos agregar seedstarter.type. prefijo al valor de enum usando una expresión, cuyo resultado usaremos como clave de mensaje:

 <td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td> 


La parte más difícil de esta lista es la columna de características . En él, queremos mostrar todas las funciones de nuestro contenedor, que se presentan como una matriz de enumeraciones de características , separadas por comas. Como " Calefacción eléctrica, el césped ".

Tenga en cuenta que esto es especialmente difícil porque estos valores de enumeración también deben inferirse, como hicimos con los Tipos. La secuencia de salida es la siguiente:

  • Sustituya el prefijo apropiado con todos los elementos de la matriz de características .
  • Reciba mensajes externos que coincidan con todas las claves en el paso 1.
  • Adjunte todos los mensajes recibidos en el paso 2, utilizando una coma como separador.


Para hacer esto, creamos el siguiente código:

 <td th:text="${#strings.arrayJoin( #messages.arrayMsg( #strings.arrayPrepend(sb.features,'seedstarter.feature.')), ', ')}">Electric Heating, Turf</td> 


La última columna de nuestra lista en realidad será bastante simple. Incluso si tiene una tabla anidada para mostrar el contenido de cada fila en el contenedor:

 <td> <table> <tbody> <tr th:each="row,rowStat : ${sb.rows}"> <td th:text="${rowStat.count}">1</td> <td th:text="${row.variety.name}">Thymus Thymi</td> <td th:text="${row.seedsPerCell}">12</td> </tr> </tbody> </table> </td> 


6 Crear formularios



6.1 Procesando un objeto de comando



El objeto de comando es el nombre que Spring MVC otorga a los beans de soporte de formularios, es decir, objetos que modelan campos de formulario y proporcionan métodos de obtención y configuración que la plataforma utilizará para establecer y recuperar los valores ingresados ​​por el usuario en el navegador.

Thymeleaf requiere que especifique un objeto de comando utilizando el atributo th: object en su etiqueta <form> :

 <form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post"> ... </form> 


Esto es coherente con otro uso de th: object , pero de hecho, este escenario particular agrega algunas limitaciones para una integración adecuada con el marco Spring MVC:

  • Los valores de atributo th: object en las etiquetas de formulario deben ser expresiones variables ( $ {...} ), especificando solo el nombre del atributo del modelo, sin navegar por las propiedades. Esto significa que una expresión como $ {seedStarter} es válida, pero $ {seedStarter.data} no lo será.
  • Dentro de la etiqueta <form>, no se puede especificar otro atributo th: object . Esto es coherente con el hecho de que los formularios HTML no se pueden anidar.


6.2 Entradas



Veamos ahora cómo agregar entradas a nuestro formulario:

 <input type="text" th:field="*{datePlanted}" /> 


Como puede ver, estamos introduciendo un nuevo atributo: th: campo . Esta es una característica muy importante para la integración de Spring MVC, ya que hace todo el trabajo duro de vincular su entrada a una propiedad en el componente de soporte de formularios. Puede verlo como el equivalente de un atributo de ruta en una etiqueta de la biblioteca de etiquetas Spring MVC JSP.

El atributo th: field se comporta de manera diferente dependiendo de si está adjunto a la etiqueta <input>, <select> o <textarea> (y también según el tipo específico de la etiqueta <input>). En este caso (entrada [tipo = texto]) la línea de código anterior es similar a:

 <input type="text" id="datePlanted" name="datePlanted" th:value="*{datePlanted}" /> 


... Pero en realidad es un poco más, porque th: field también usará el servicio de transformación Spring registrado, incluido el DateFormatter , que vimos anteriormente (incluso si la expresión del campo no está entre corchetes). Debido a esto, la fecha se mostrará correctamente formateada.

Los valores para los atributos de campo th: deben ser expresiones selectas ( * {...} ), lo que tiene sentido dado el hecho de que se evaluarán en un componente que admita la forma, y ​​no en variables de contexto (o atributos de modelo en la jerga de Spring MVC). )

A diferencia de las expresiones en th: object , estas expresiones pueden incluir navegación de propiedades (de hecho, cualquier expresión permitida para el atributo de ruta de la etiqueta JSP <form: input> está permitida aquí).

Tenga en cuenta que th: field también comprende los nuevos tipos del elemento <input> introducido en HTML5, como <input type = "datetime" ... />, <input type = "color" ... />, etc., efectivamente agregando soporte HTML5 completo para Spring MVC.

6.3 Campos de casilla de verificación



th: el campo también le permite definir la entrada de la casilla de verificación para las banderas. Veamos un ejemplo de nuestra página HTML:

 <div> <label th:for="${#ids.next('covered')}" th:text="#{seedstarter.covered}">Covered</label> <input type="checkbox" th:field="*{covered}" /> </div> 


Tenga en cuenta que hay algo más además del indicador en sí, por ejemplo, una etiqueta externa, así como el uso de la función # ids.next ('cerrado') para obtener el valor que se aplicará al atributo id de la entrada al indicador.

¿Por qué necesitamos crear dinámicamente un atributo de identificación para este campo? Dado que los indicadores son potencialmente de múltiples valores y, por lo tanto, el sufijo del número de secuencia siempre se agregará a sus valores de identificador (utilizando internamente la función # ids.seq (...) ) para garantizar que cada uno de los indicadores de entrada de la misma propiedad tenga un valor de identificador diferente .

Será más fácil para nosotros ver esto si miramos una casilla de verificación con múltiples valores:

 <ul> <li th:each="feat : ${allFeatures}"> <input type="checkbox" th:field="*{features}" th:value="${feat}" /> <label th:for="${#ids.prev('features')}" th:text="#{${'seedstarter.feature.' + feat}}">Heating</label> </li> </ul> 


Tenga en cuenta que esta vez agregamos el atributo th: value , porque el campo de función no es lógico, como se describió anteriormente, pero es una matriz de valores.

Veamos el resultado HTML generado por este código:

 <ul> <li> <input id="features1" name="features" type="checkbox" value="SEEDSTARTER_SPECIFIC_SUBSTRATE" /> <input name="_features" type="hidden" value="on" /> <label for="features1">Seed starter-specific substrate</label> </li> <li> <input id="features2" name="features" type="checkbox" value="FERTILIZER" /> <input name="_features" type="hidden" value="on" /> <label for="features2">Fertilizer used</label> </li> <li> <input id="features3" name="features" type="checkbox" value="PH_CORRECTOR" /> <input name="_features" type="hidden" value="on" /> <label for="features3">PH Corrector used</label> </li> </ul> 


Aquí vemos cómo se agrega el sufijo de secuencia a cada atributo de entrada de id y cómo la función # ids.prev (...) nos permite extraer el último valor de secuencia generado para un identificador de entrada específico.

No se preocupe por estas entradas ocultas con name = "_ features" : se agregan automáticamente para evitar problemas con los navegadores que no envían valores de bandera no seleccionados al servidor cuando se envía el formulario.

También tenga en cuenta que si nuestra propiedad de características contenía algunos valores seleccionados en nuestro bean de respaldo de formulario, th: field se encargaría de esto y agregaría el atributo check = "check" a las etiquetas de entrada correspondientes.

6.4 Campos del botón de radio



Los campos de conmutación se configuran de manera similar a los indicadores no booleanos (de valores múltiples), excepto, por supuesto, que no tienen valores múltiples:

 <ul> <li th:each="ty : ${allTypes}"> <input type="radio" th:field="*{type}" th:value="${ty}" /> <label th:for="${#ids.prev('type')}" th:text="#{${'seedstarter.type.' + ty}}">Wireframe</label> </li> </ul> 


6.5 Lista desplegable / Selectores de lista



Los campos de selección constan de dos partes: la etiqueta <select> y sus etiquetas <option> anidadas. Al crear un campo de este tipo, solo la etiqueta <select> debe incluir el atributo th: field , pero los atributos th: value en las etiquetas <option> anidadas serán muy importantes porque brindarán la oportunidad de descubrir cuál es la opción seleccionada actualmente (similar a las banderas y botones de radio no booleanos) )

Vamos a reconstruir un campo de tipo desplegable:

 <select th:field="*{type}"> <option th:each="type : ${allTypes}" th:value="${type}" th:text="#{${'seedstarter.type.' + type}}">Wireframe</option> </select> 


En este punto, entender este fragmento de código es bastante fácil. Solo observe cómo la prioridad del atributo nos permite establecer el th: cada atributo en la etiqueta <option> en sí.

6.6 Campos dinámicos



Gracias a las capacidades avanzadas de vincular campos de formulario en Spring MVC, podemos usar expresiones Spring EL complejas para vincular campos de formulario dinámicos a nuestro bean de respaldo de formulario. Esto nos permitirá crear nuevos objetos de fila en nuestro componente SeedStarter y agregar los campos de estas líneas a nuestro formulario a solicitud del usuario.

Para hacer esto, necesitamos un par de nuevos métodos mapeados en nuestro controlador que agreguen o eliminen una línea de nuestro SeedStarter dependiendo de la disponibilidad de ciertos parámetros de solicitud:

 @RequestMapping(value="/seedstartermng", params={"addRow"}) public String addRow(final SeedStarter seedStarter, final BindingResult bindingResult) { seedStarter.getRows().add(new Row()); return "seedstartermng"; } @RequestMapping(value="/seedstartermng", params={"removeRow"}) public String removeRow( final SeedStarter seedStarter, final BindingResult bindingResult, final HttpServletRequest req) { final Integer rowId = Integer.valueOf(req.getParameter("removeRow")); seedStarter.getRows().remove(rowId.intValue()); return "seedstartermng"; } 


Y ahora podemos agregar una tabla dinámica a nuestro formulario:

 <table> <thead> <tr> <th th:text="#{seedstarter.rows.head.rownum}">Row</th> <th th:text="#{seedstarter.rows.head.variety}">Variety</th> <th th:text="#{seedstarter.rows.head.seedsPerCell}">Seeds per cell</th> <th> <button type="submit" name="addRow" th:text="#{seedstarter.row.add}">Add row</button> </th> </tr> </thead> <tbody> <tr th:each="row,rowStat : *{rows}"> <td th:text="${rowStat.count}">1</td> <td> <select th:field="*{rows[__${rowStat.index}__].variety}"> <option th:each="var : ${allVarieties}" th:value="${var.id}" th:text="${var.name}">Thymus Thymi</option> </select> </td> <td> <input type="text" th:field="*{rows[__${rowStat.index}__].seedsPerCell}" /> </td> <td> <button type="submit" name="removeRow" th:value="${rowStat.index}" th:text="#{seedstarter.row.remove}">Remove row</button> </td> </tr> </tbody> </table> 


Aquí hay suficientes cosas, pero no tantas como para no entender ... excepto por una cosa extraña :

 <select th:field="*{rows[__${rowStat.index}__].variety}"> ... </select> 


Si recuerda del tutorial " Uso de Thymeleaf ", la sintaxis __ $ {...} __ es una expresión de preprocesamiento, que es una expresión interna que se evalúa antes de la evaluación real de toda la expresión. Pero, ¿por qué esta forma de especificar el índice de fila? ¿No sería eso suficiente con:

 <select th:field="*{rows[rowStat.index].variety}"> ... </select> 


... en realidad no. El problema es que Spring EL no evalúa las variables entre paréntesis del índice de matriz, por lo que al ejecutar la expresión anterior, recibimos un error que nos dice que filas [rowStat.index] (en lugar de filas [0] , filas [1] , etc. ) posición no válida en la colección de filas. Es por eso que aquí se necesita un procesamiento previo.

Veamos un fragmento del código HTML resultante después de hacer clic en "Agregar fila" varias veces:

 <tbody> <tr> <td>1</td> <td> <select id="rows0.variety" name="rows[0].variety"> <option selected="selected" value="1">Thymus vulgaris</option> <option value="2">Thymus x citriodorus</option> <option value="3">Thymus herba-barona</option> <option value="4">Thymus pseudolaginosus</option> <option value="5">Thymus serpyllum</option> </select> </td> <td> <input id="rows0.seedsPerCell" name="rows[0].seedsPerCell" type="text" value="" /> </td> <td> <button name="removeRow" type="submit" value="0">Remove row</button> </td> </tr> <tr> <td>2</td> <td> <select id="rows1.variety" name="rows[1].variety"> <option selected="selected" value="1">Thymus vulgaris</option> <option value="2">Thymus x citriodorus</option> <option value="3">Thymus herba-barona</option> <option value="4">Thymus pseudolaginosus</option> <option value="5">Thymus serpyllum</option> </select> </td> <td> <input id="rows1.seedsPerCell" name="rows[1].seedsPerCell" type="text" value="" /> </td> <td> <button name="removeRow" type="submit" value="1">Remove row</button> </td> </tr> </tbody> 

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


All Articles