Première partieTroisième partie5 Afficher les données du démarreur de semences
La première chose que notre page
/WEB-INF/templates/seedstartermng.html montre est une liste avec les données de démarrage initiales actuellement enregistrées. Pour ce faire, nous avons besoin de certains messages externes, ainsi que d'un travail d'expression pour les attributs du modèle. Comme ça:
<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>
Il y a beaucoup à voir. Examinons chaque fragment séparément.
Tout d'abord, cette section ne sera affichée que s'il y a des démarreurs de semences. Nous y
parvenons avec l'attribut
th: never et la fonction
# lists.isEmpty (...) .
<div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}">
Notez que tous les objets utilitaires, tels que
#lists , sont disponibles dans les expressions Spring EL de la même manière que dans les expressions OGNL dans un dialecte standard.
La prochaine chose à voir est un grand nombre de textes internationalisés (externalisés), tels que:
<h2 th: text = "# {title.list}"> Liste des démarreurs de semences
<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> ...
Il s'agit d'une application Spring MVC, nous avons déjà défini le
bean MessageSource dans notre configuration Spring (les objets
MessageSource sont le moyen standard de contrôler les textes externes dans Spring MVC):
@Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("Messages"); return messageSource; }
... et cette propriété de nom de base indique que nous aurons des fichiers dans notre
chemin de classe , tels que
Messages_es.properties ou
Messages_en.properties . Regardons la version espagnole:
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
Dans la première colonne du tableau, nous indiquons la date de préparation du démarreur. Mais
nous montrerons qu'il est formaté comme nous l'avons défini dans notre
DateFormatter . Pour ce faire, nous utiliserons la syntaxe à double parenthèse (
$ {{...}} ), qui appliquera automatiquement le service de conversion Spring, y compris le DateFormatter, que nous avons enregistré lors de la configuration.
<td th:text="${{sb.datePlanted}}">13/01/2011</td>
Ce qui suit montre si le conteneur de semences de démarrage de semences est couvert ou non en convertissant la valeur de propriété du bac couvert booléen en oui ou non internationalisé avec une expression de recherche littérale:
<td th:text="#{|bool.${sb.covered}|}">yes</td>
Maintenant, nous devons montrer le type du conteneur de démarrage de semences initial. Le type est une énumération java avec deux valeurs (
WOOD et
PLASTIC ), et donc nous avons défini deux propriétés dans notre fichier
Messages avec les noms
seedstarter.type.WOO D et
seedstarter.type.PLASTIC .
Mais pour obtenir des noms de type internationalisés, nous devons ajouter
seedstarter.type. préfixe de la valeur de enum en utilisant une expression, dont le résultat sera ensuite utilisé comme clé de message:
<td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>
La partie la plus difficile de cette liste est la colonne des
fonctionnalités . Dans ce document, nous voulons afficher toutes les fonctions de notre conteneur, qui sont présentées sous la forme d'un tableau d'énumérations de
fonctionnalités , séparées par des virgules. Comme "
Chauffage électrique, la pelouse ."
Notez que cela est particulièrement difficile car ces valeurs d'énumération doivent également être déduites, comme nous l'avons fait avec les types. Le flux de sortie est le suivant:
- Remplacez le préfixe approprié par tous les éléments du tableau d' entités .
- Recevez des messages externes correspondant à toutes les clés de l'étape 1.
- Joignez tous les messages reçus à l'étape 2, en utilisant une virgule comme séparateur.
Pour ce faire, nous créons le code suivant:
<td th:text="${#strings.arrayJoin( #messages.arrayMsg( #strings.arrayPrepend(sb.features,'seedstarter.feature.')), ', ')}">Electric Heating, Turf</td>
La dernière colonne de notre liste sera en fait assez simple. Même s'il a une table imbriquée pour afficher le contenu de chaque ligne du conteneur:
<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 Création de formulaires
6.1 Traitement d'un objet de commande
L'objet de commande est le nom que Spring MVC donne aux beans de support de formulaire, c'est-à-dire les objets qui modélisent les champs de formulaire et fournissent des méthodes get et set que la plateforme utilisera pour définir et récupérer les valeurs entrées par l'utilisateur dans le navigateur.
Thymeleaf nécessite que vous spécifiez un objet de commande à l'aide de l'attribut
th: object dans votre
balise <form> :
<form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post"> ... </form>
Ceci est cohérent avec une autre utilisation de
th: object , mais en fait, ce scénario particulier ajoute quelques limitations pour une bonne intégration avec le framework Spring MVC:
- Les valeurs d'attribut th: object dans les balises de formulaire doivent être des expressions variables ( $ {...} ), spécifiant uniquement le nom d'attribut du modèle, sans naviguer dans les propriétés. Cela signifie qu'une expression comme $ {seedStarter} est valide, mais $ {seedStarter.data} ne le sera pas.
- À l'intérieur de la balise <form>, un autre attribut th: object ne peut pas être spécifié. Cela est cohérent avec le fait que les formulaires HTML ne peuvent pas être imbriqués.
6.2 Entrées
Voyons maintenant comment ajouter une entrée à notre formulaire:
<input type="text" th:field="*{datePlanted}" />
Comme vous pouvez le voir, nous introduisons un nouvel attribut:
th: field . Il s'agit d'une fonctionnalité très importante pour l'intégration de Spring MVC, car elle fait tout le travail difficile de lier votre
entrée à une propriété du composant de support de formulaire. Vous pouvez le voir comme l'équivalent d'un attribut de chemin dans une balise de la bibliothèque de balises Spring MVC JSP.
L'attribut th: field se comporte différemment selon qu'il est attaché à la balise <input>, <select> ou <textarea> (et également en fonction du type spécifique de la balise <input>). Dans ce cas (entrée [type = texte]), la ligne de code ci-dessus est similaire à:
<input type="text" id="datePlanted" name="datePlanted" th:value="*{datePlanted}" />
... Mais en fait, c'est un peu plus, car
th: field utilisera également le service de transformation Spring enregistré, y compris le
DateFormatter , que nous avons vu plus tôt (même si l'expression de champ n'est pas placée entre crochets). Pour cette raison, la date sera affichée correctement formatée.
Les valeurs des attributs
th: field doivent être des expressions select (
* {...} ), ce qui est logique étant donné qu'elles seront évaluées sur un composant qui prend en charge le formulaire, et non sur des variables de contexte (ou des attributs de modèle dans le jargon Spring MVC). )
Contrairement aux expressions dans
th: object , ces expressions peuvent inclure la navigation dans les propriétés (en fait, toute expression autorisée pour l'attribut path de la balise JSP <form: input> est autorisée ici).
Notez que th: field comprend également les nouveaux types de l'élément <input> introduits dans HTML5, tels que <input type = "datetime" ... />, <input type = "color" ... />, etc., efficacement ajout de la prise en charge HTML5 complète pour Spring MVC.
6.3 Champs de case à cocher
Le champ th: permet également de définir une entrée de case à cocher pour les indicateurs. Voyons un exemple de notre page HTML:
<div> <label th:for="${#ids.next('covered')}" th:text="#{seedstarter.covered}">Covered</label> <input type="checkbox" th:field="*{covered}" /> </div>
Notez qu'il existe autre chose que l'indicateur lui-même, par exemple, une étiquette externe, ainsi que l'utilisation de la fonction
# ids.next ('fermée') pour obtenir la valeur qui sera appliquée à l'attribut
id de l'entrée de l'indicateur.
Pourquoi avons-nous besoin de créer dynamiquement un attribut
id pour ce champ? Étant donné que les indicateurs sont potentiellement à valeurs multiples, et par conséquent, le suffixe du numéro de séquence sera toujours ajouté à leurs valeurs d'identification (en utilisant la fonction
# ids.seq (...) en interne) pour garantir que chacun des indicateurs d'entrée de la même propriété a une valeur d'identification différente .
Il sera plus facile pour nous de voir cela si nous regardons une telle case à cocher à plusieurs valeurs:
<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>
Veuillez noter que cette fois, nous avons ajouté l'attribut
th: value , car le champ de fonction n'est pas logique, comme décrit ci-dessus, mais est un tableau de valeurs.
Voyons la sortie HTML générée par ce code:
<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>
Nous voyons ici comment le suffixe de séquence est ajouté à chaque attribut d'entrée
id et comment la fonction
# ids.prev (...) nous permet d'extraire la dernière valeur de séquence générée pour un identificateur d'entrée spécifique.
Ne vous inquiétez pas de ces entrées cachées avec
name = "_ features" : elles sont automatiquement ajoutées pour éviter les problèmes avec les navigateurs qui n'envoient pas de valeurs d'indicateur non sélectionnées au serveur lorsque le formulaire est soumis.
Notez également que si notre propriété
features contenait certaines valeurs sélectionnées dans notre bean de sauvegarde de formulaire, alors
le champ th: s'en occuperait et ajouterait l'attribut
vérifié = «vérifié» aux balises d'entrée correspondantes.
6.4 Champs des boutons radio
Les champs de commutation sont définis de manière similaire aux indicateurs non booléens (à valeurs multiples), sauf, bien sûr, qu'ils ne sont pas à valeurs multiples:
<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 Sélecteurs de liste déroulante / liste
Les champs de sélection se composent de deux parties: la balise <select> et ses balises <option> imbriquées. Lors de la création d'un champ de ce type, seule la balise <select> doit inclure l'attribut
th: field , mais les attributs
th: value dans les balises <option> imbriquées seront très importants car ils permettront de découvrir quelle est l'option actuellement sélectionnée (similaire aux drapeaux non booléens et aux boutons radio )
Reconstruisons un champ de type déroulant:
<select th:field="*{type}"> <option th:each="type : ${allTypes}" th:value="${type}" th:text="#{${'seedstarter.type.' + type}}">Wireframe</option> </select>
À ce stade, la compréhension de ce morceau de code est assez facile. Remarquez simplement comment la priorité d'attribut nous permet de définir l'attribut
th: each dans la balise <option> elle-même.
6.6 Champs dynamiques
Grâce aux capacités avancées de liaison des champs de formulaire dans Spring MVC, nous pouvons utiliser des expressions
Spring EL complexes pour lier des champs de formulaire dynamiques à notre bean de support de formulaire. Cela nous permettra de créer de nouveaux objets
Row dans notre composant
SeedStarter et d'ajouter les champs de ces lignes à notre formulaire à la demande de l'utilisateur.
Pour ce faire, nous avons besoin de quelques nouvelles méthodes mappées dans notre contrôleur qui ajoutent ou suppriment une ligne de notre
SeedStarter en fonction de la disponibilité de certains paramètres de demande:
@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"; }
Et maintenant, nous pouvons ajouter une table dynamique à notre formulaire:
<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>
Il y a assez de choses ici, mais pas assez pour ne pas comprendre ... sauf pour une chose
étrange :
<select th:field="*{rows[__${rowStat.index}__].variety}"> ... </select>
Si vous vous souvenez du didacticiel «
Utilisation de Thymeleaf », la syntaxe
__ $ {...} __ est une expression de prétraitement, qui est une expression interne qui est évaluée avant l'évaluation réelle de l'expression entière. Mais pourquoi cette façon de spécifier l'index des lignes? Cela ne suffirait-il pas avec:
<select th:field="*{rows[rowStat.index].variety}"> ... </select>
... en fait non. Le problème est que Spring EL n'évalue pas les variables entre les crochets de l'index du tableau, donc lors de l'exécution de l'expression ci-dessus, nous obtenons une erreur nous indiquant que les
lignes [rowStat.index] (au lieu des
lignes [0] ,
lignes [1] , etc. ) position invalide dans la collection de lignes. C'est pourquoi le prétraitement est nécessaire ici.
Regardons un extrait du code HTML résultant après avoir cliqué plusieurs fois sur «Ajouter une ligne»:
<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>