Guia: Thymeleaf + Spring. Parte 2

Primeira parte
Terceira parte

5 Exibir dados iniciais de sementes



A primeira coisa que nossa página /WEB-INF/templates/seedstartermng.html mostra é uma lista com os dados de início iniciais atualmente salvos. Para fazer isso, precisamos de algumas mensagens externas, bem como alguma expressão de trabalho para os atributos do modelo. Assim:

<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> 


Há muito o que ver. Vamos olhar para cada fragmento separadamente.

Antes de tudo, esta seção será exibida apenas se houver iniciadores de sementes. Conseguimos isso com o atributo th: never e a função # lists.isEmpty (...) .

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


Observe que todos os objetos utilitários, como #lists , estão disponíveis nas expressões Spring EL da mesma maneira que nas expressões OGNL no dialeto padrão.

A próxima coisa a ver são muitos textos internacionalizados (externalizados), como:

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

 <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> ... 


Este é um aplicativo Spring MVC, já definimos o bean MessageSource em nossa configuração do Spring (os objetos MessageSource são a maneira padrão de controlar textos externos no Spring MVC):

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


... e essa propriedade de nome de base indica que teremos arquivos em nosso caminho de classe, como Messages_es.properties ou Messages_en.properties . Vejamos a versão em espanhol:

 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 


Na primeira coluna da tabela, mostramos a data em que a partida foi preparada. Mas mostraremos que ele está formatado conforme definido em nosso DateFormatter . Para fazer isso, usaremos a sintaxe de colchete duplo ( $ {{...}} ), que aplicará automaticamente o serviço de conversão do Spring, incluindo o DateFormatter, que registramos ao configurar.

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


A seguir, mostra se o contêiner de sementes do iniciador de sementes está coberto ou não, convertendo o valor da propriedade do compartimento coberto por booleanos em um sim ou não internacionalizado com uma expressão de pesquisa literal:

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


Agora, precisamos mostrar o tipo do contêiner inicial de iniciador de sementes. O tipo é uma enumeração java com dois valores ( WOOD e PLASTIC ) e, portanto, definimos duas propriedades em nosso arquivo de Mensagens com os nomes seedstarter.type.WOO D e seedstarter.type.PLASTIC .

Mas, para obter nomes de tipo internacionalizados, precisamos adicionar seedstarter.type. prefixo ao valor de enum usando uma expressão, cujo resultado usaremos como chave de mensagem:

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


A parte mais difícil desta lista é a coluna de recursos . Nele, queremos exibir todas as funções do nosso contêiner, apresentadas como uma matriz de enumerações de recursos , separadas por vírgulas. Como " Aquecimento elétrico, o gramado ".

Observe que isso é especialmente difícil porque esses valores de enum também devem ser inferidos, como fizemos com Types. O fluxo de saída é o seguinte:

  • Substitua o prefixo apropriado por todos os elementos da matriz de recursos .
  • Receba mensagens externas que correspondem a todas as chaves na etapa 1.
  • Anexe todas as mensagens recebidas na etapa 2, usando uma vírgula como separador.


Para fazer isso, criamos o seguinte código:

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


A última coluna da nossa lista será realmente bastante simples. Mesmo se houver uma tabela aninhada para exibir o conteúdo de cada linha no contêiner:

 <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 Criando formulários



6.1 Processando um objeto de comando



O objeto de comando é o nome que o Spring MVC fornece aos beans de suporte ao formulário, ou seja, objetos que modelam campos de formulário e fornecem métodos get e set que a plataforma usará para definir e recuperar os valores inseridos pelo usuário no navegador.

O Thymeleaf requer que você especifique um objeto de comando usando o atributo th: object na sua tag <form> :

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


Isso é consistente com outro uso do objeto th:, mas, de fato, esse cenário específico adiciona algumas limitações para a integração adequada com a estrutura do Spring MVC:

  • Os valores do atributo th: object nas tags do formulário devem ser expressões variáveis ​​( $ {...} ), especificando apenas o nome do atributo do modelo, sem navegar pelas propriedades. Isso significa que uma expressão como $ {seedStarter} é válida, mas $ {seedStarter.data} não.
  • Dentro da tag <form>, outro atributo th: object não pode ser especificado. Isso é consistente com o fato de que os formulários HTML não podem ser aninhados.


6.2 Entradas



Vamos agora ver como adicionar entrada ao nosso formulário:

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


Como você pode ver, estamos introduzindo um novo atributo: th: field . Esse é um recurso muito importante para a integração do Spring MVC, pois faz todo o trabalho duro de vincular sua entrada a uma propriedade no componente de suporte ao formulário. Você pode vê-lo como o equivalente a um atributo de caminho em uma tag da biblioteca de tags Spring MVC JSP.

O atributo th: field se comporta de maneira diferente, dependendo de estar anexado à tag <input>, <select> ou <textarea> (e também dependendo do tipo específico da tag <input>). Nesse caso (entrada [type = text]), a linha de código acima é semelhante a:

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


... Mas, na verdade, é um pouco mais, porque o campo th: também usará o serviço de transformação Spring registrado, incluindo o DateFormatter , que vimos anteriormente (mesmo que a expressão do campo não esteja entre colchetes). Por esse motivo, a data será exibida corretamente formatada.

Os valores para os atributos th: field devem ser expressões de seleção ( * {...} ), o que faz sentido, pois elas serão avaliadas em um componente que suporta o formulário e não em variáveis ​​de contexto (ou atributos de modelo no jargão do Spring MVC). )

Diferentemente das expressões no objeto th:, essas expressões podem incluir navegação de propriedade (na verdade, qualquer expressão permitida para o atributo path da tag JSP <form: input> é permitida aqui).

Observe que o campo th: também entende os novos tipos do elemento <input> introduzidos no HTML5, como <input type = "datetime" ... />, <input type = "color" ... /> etc., efetivamente adicionando suporte HTML5 completo ao Spring MVC.

6.3 Campos da caixa de seleção



O campo th: também permite definir a entrada da caixa de seleção para sinalizadores. Vamos ver um exemplo da nossa página HTML:

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


Observe que há algo mais além do próprio sinalizador, por exemplo, um rótulo externo, além de usar a função # ids.next ('closed') para obter o valor que será aplicado ao atributo id da entrada no sinalizador.

Por que precisamos criar dinamicamente um atributo de ID para esse campo? Como os sinalizadores são potencialmente com vários valores e, portanto, o sufixo do número de sequência sempre será adicionado aos seus valores de identificador (internamente usando a função # ids.seq (...) ) para garantir que cada um dos sinalizadores de entrada da mesma propriedade tenha um valor de identificador diferente .

Será mais fácil ver isso se observarmos uma caixa de seleção com vários 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> 


Observe que desta vez adicionamos o atributo th: value , porque o campo de função não é lógico, como descrito acima, mas é uma matriz de valores.

Vamos ver a saída HTML gerada 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> 


Aqui vemos como o sufixo da sequência é adicionado a cada atributo de entrada de id e como a função # ids.prev (...) nos permite extrair o último valor de sequência gerado para um identificador de entrada específico.

Não se preocupe com essas entradas ocultas com name = "_ features" : elas são adicionadas automaticamente para evitar problemas com navegadores que não enviam valores de sinalizadores não selecionados para o servidor quando o formulário é enviado.

Observe também que, se nossa propriedade features contivesse alguns dos valores selecionados em nosso bean de backup de formulários, o campo th: cuidaria disso e adicionaria o atributo selected = "selected" às tags de entrada correspondentes.

6.4 Campos dos botões de opção



Os campos de opção são definidos de forma semelhante aos sinalizadores não booleanos (com valores múltiplos), exceto, é claro, que eles não são com valores múltiplos:

 <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 Seletores suspensos / lista



Os campos de seleção consistem em duas partes: a tag <select> e suas tags <option> aninhadas. Ao criar um campo desse tipo, apenas a tag <select> deve incluir o atributo th: field , mas os atributos th: value nas tags <option> aninhadas serão muito importantes porque fornecerão a oportunidade de descobrir qual é a opção atualmente selecionada (semelhante a sinalizadores e botões de opção não booleanos) )

Vamos reconstruir um campo do tipo suspenso:

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


Neste ponto, entender esse pedaço de código é bastante fácil. Observe como a prioridade do atributo nos permite definir o atributo th: each na própria tag <option>.

6.6 Campos dinâmicos



Graças aos recursos avançados de ligação de campos de formulário no Spring MVC, podemos usar expressões complexas do Spring EL para vincular campos de formulário dinâmicos ao nosso bean de backup de formulário. Isso nos permitirá criar novos objetos Row em nosso componente SeedStarter e adicionar os campos dessas linhas ao nosso formulário, a pedido do usuário.

Para fazer isso, precisamos de alguns novos métodos mapeados em nosso controlador que adicionem ou removem uma linha do SeedStarter, dependendo da disponibilidade de certos parâmetros de solicitação:

 @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"; } 


E agora podemos adicionar uma tabela dinâmica ao nosso formulário:

 <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> 


Há coisas suficientes aqui, mas não tantas que não entendo ... exceto por uma coisa estranha :

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


Se você se lembra do tutorial “ Usando o Thymeleaf, a sintaxe __ $ {...} __ é uma expressão de pré-processamento, que é uma expressão interna avaliada antes da avaliação real de toda a expressão. Mas por que essa maneira de especificar o índice de linha? Isso não seria suficiente com:

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


... na verdade não. O problema é que o Spring EL não avalia as variáveis ​​entre colchetes do índice da matriz, portanto, ao executar a expressão acima, obtemos um erro informando que as linhas [rowStat.index] (em vez das linhas [0] , linhas [1] etc.) ) posição inválida na coleção de linhas. É por isso que o pré-processamento é necessário aqui.

Vejamos um trecho do código HTML resultante depois que clicamos em "Adicionar linha" várias vezes:

 <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/pt435080/


All Articles