Erster TeilDritter Teil5 Seed Starter-Daten anzeigen
Das erste, was unsere Seite
/WEB-INF/templates/seedstartermng.html anzeigt, ist eine Liste mit den anfänglichen Startdaten, die derzeit gespeichert sind. Dazu benötigen wir einige externe Nachrichten sowie einige Ausdrucksarbeiten für die Modellattribute. Wie ist es:
<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>
Es gibt viel zu sehen. Schauen wir uns jedes Fragment einzeln an.
Zunächst wird dieser Abschnitt nur angezeigt, wenn Samenstarter vorhanden sind. Dies erreichen wir mit dem Attribut
th: never und der Funktion
# lists.isEmpty (...) .
<div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}">
Beachten Sie, dass alle Dienstprogrammobjekte, wie z. B.
#Lists , in Spring EL-Ausdrücken auf dieselbe Weise verfügbar sind wie in OGNL-Ausdrücken im Standarddialekt.
Das nächste, was zu sehen ist, sind viele internationalisierte (externalisierte) Texte, wie zum Beispiel:
<h2 th: text = "# {title.list}"> Liste der Seed Starter
<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> ...
Dies ist eine Spring MVC-Anwendung. Wir haben die
MessageSource-Bean bereits in unserer Spring-Konfiguration definiert (
MessageSource- Objekte sind die Standardmethode zum Steuern externer Texte in Spring MVC):
@Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("Messages"); return messageSource; }
... und diese Eigenschaft
basename gibt an, dass sich Dateien in unserem Klassenpfad befinden, z. B.
Messages_es.properties oder
Messages_en.properties . Schauen wir uns die spanische Version an:
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
In der ersten Spalte der Tabelle zeigen wir das Datum, an dem der Starter vorbereitet wurde.
Wir werden jedoch zeigen, dass es so formatiert ist, wie wir es in unserem
DateFormatter definiert
haben . Zu diesem Zweck verwenden wir die Doppelklammer-Syntax (
$ {{...}} ), die automatisch den Spring-Konvertierungsdienst anwendet, einschließlich des DateFormatter, den wir beim Einrichten registriert haben.
<td th:text="${{sb.datePlanted}}">13/01/2011</td>
Das Folgende zeigt, ob der Seed-Starter-Seed-Container abgedeckt ist oder nicht, indem der Eigenschaftswert des booleschen abgedeckten Bin in ein internationalisiertes Ja oder Nein mit einem wörtlichen Suchausdruck konvertiert wird:
<td th:text="#{|bool.${sb.covered}|}">yes</td>
Jetzt müssen wir den Typ des anfänglichen Samenstarterbehälters anzeigen. Der Typ ist eine Java-Aufzählung mit zwei Werten (
WOOD und
PLASTIC ). Daher haben wir in unserer
Nachrichtendatei zwei Eigenschaften mit den Namen
seedstarter.type.WOO D und
seedstarter.type.PLASTIC definiert .
Um jedoch internationalisierte
Typnamen zu erhalten, müssen wir
seedstarter.type hinzufügen
. Präfix zum Wert von enum mit einem Ausdruck, dessen Ergebnis wir dann als Nachrichtenschlüssel verwenden:
<td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>
Der schwierigste Teil dieser Liste ist die
Feature- Spalte. Darin möchten wir alle Funktionen unseres Containers anzeigen, die als Array von
Feature- Aufzählungen dargestellt werden, die durch Kommas getrennt sind. Wie "
Elektrische Heizung, der Rasen ."
Beachten Sie, dass dies besonders schwierig ist, da diese Aufzählungswerte ebenfalls abgeleitet werden müssen, wie wir es bei Typen getan haben. Der Ausgabestream ist wie folgt:
- Ersetzen Sie das entsprechende Präfix durch alle Elemente des Features- Arrays.
- Empfangen Sie externe Nachrichten, die mit allen Schlüsseln in Schritt 1 übereinstimmen.
- Fügen Sie alle in Schritt 2 empfangenen Nachrichten mit einem Komma als Trennzeichen hinzu.
Dazu erstellen wir folgenden Code:
<td th:text="${#strings.arrayJoin( #messages.arrayMsg( #strings.arrayPrepend(sb.features,'seedstarter.feature.')), ', ')}">Electric Heating, Turf</td>
Die letzte Spalte unserer Liste wird eigentlich ganz einfach sein. Auch wenn es eine verschachtelte Tabelle gibt, um den Inhalt jeder Zeile im Container anzuzeigen:
<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 Formulare erstellen
6.1 Befehlsobjekt verarbeiten
Das Befehlsobjekt ist der Name, den Spring MVC Form Support Beans gibt, dh Objekte, die Formularfelder modellieren und Methoden zum Abrufen und Festlegen bereitstellen, mit denen die Plattform die vom Benutzer im Browser eingegebenen Werte festlegt und abruft.
Für Thymeleaf müssen Sie ein Befehlsobjekt mit dem Attribut
th: object in Ihrem
<form> -Tag angeben:
<form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post"> ... </form>
Dies steht im Einklang mit einer anderen Verwendung von
th: object . In diesem speziellen Szenario werden jedoch einige Einschränkungen für die ordnungsgemäße Integration in das Spring MVC-Framework hinzugefügt:
- Die th: object- Attributwerte in den Formular-Tags müssen variable Ausdrücke ( $ {...} ) sein, die nur den Modellattributnamen angeben, ohne durch die Eigenschaften zu navigieren. Dies bedeutet, dass ein Ausdruck wie $ {seedStarter} gültig ist, $ {seedStarter.data} jedoch nicht.
- Innerhalb des <form> -Tags kann kein anderes th: object- Attribut angegeben werden. Dies steht im Einklang mit der Tatsache, dass HTML-Formulare nicht verschachtelt werden können.
6.2 Eingänge
Lassen Sie uns nun sehen, wie Sie unserem Formular Eingaben hinzufügen:
<input type="text" th:field="*{datePlanted}" />
Wie Sie sehen können, führen wir ein neues Attribut ein:
th: field . Dies ist eine sehr wichtige Funktion für die Spring MVC-Integration, da sie die harte Arbeit des Bindens Ihrer
Eingabe an eine Eigenschaft in der Formularunterstützungskomponente erledigt. Sie können es als Äquivalent eines Pfadattributs in einem Tag aus der Spring MVC JSP-Tag-Bibliothek anzeigen.
Das Attribut th: field verhält sich unterschiedlich, je nachdem, ob es an das Tag <input>, <select> oder <textarea> angehängt ist (und auch abhängig vom spezifischen Typ des <input> -Tags). In diesem Fall (Eingabe [Typ = Text]) ähnelt die obige Codezeile:
<input type="text" id="datePlanted" name="datePlanted" th:value="*{datePlanted}" />
... Aber eigentlich ist es ein bisschen mehr, weil das
Feld th: auch den registrierten Spring-Transformationsdienst verwendet, einschließlich des
DateFormatter , den wir zuvor gesehen haben (auch wenn der
Feldausdruck nicht in eckigen Klammern eingeschlossen ist). Aus diesem Grund wird das Datum korrekt formatiert angezeigt.
Die Werte für die Attribute
th: field sollten ausgewählte Ausdrücke (
* {...} ) sein. Dies ist sinnvoll, da sie für eine Komponente ausgewertet werden, die das Formular unterstützt, und nicht für Kontextvariablen (oder Modellattribute im Spring MVC-Jargon). )
Im Gegensatz zu Ausdrücken in
th: object können diese Ausdrücke die Eigenschaftsnavigation enthalten (tatsächlich ist hier jeder Ausdruck zulässig, der für das Pfadattribut des JSP-Tags <form: input> zulässig ist).
Beachten Sie, dass das Feld th: auch die neuen Typen des in HTML5 eingeführten <input> -Elements wie <input type = "datetime" ... />, <input type = "color" ... /> usw. effektiv versteht Hinzufügen der vollständigen HTML5-Unterstützung für Spring MVC.
6.3 Kontrollkästchenfelder
Mit dem Feld th: können Sie auch die Eingabe von Kontrollkästchen für Flags definieren. Sehen wir uns ein Beispiel von unserer HTML-Seite an:
<div> <label th:for="${#ids.next('covered')}" th:text="#{seedstarter.covered}">Covered</label> <input type="checkbox" th:field="*{covered}" /> </div>
Beachten Sie, dass es neben dem Flag selbst noch etwas anderes gibt, z. B. eine externe Bezeichnung, sowie die Verwendung der Funktion
# ids.next ('geschlossen') , um den Wert
abzurufen , der auf das
ID- Attribut der Eingabe in das Flag angewendet wird.
Warum müssen wir dynamisch ein
ID- Attribut für dieses Feld erstellen? Da die Flags möglicherweise mehrwertig sind und daher das Suffix der Sequenznummer immer zu ihren Bezeichnerwerten hinzugefügt wird (intern mithilfe der Funktion
# ids.seq (...) ), um sicherzustellen, dass jedes der Eingabeflags derselben Eigenschaft einen anderen Bezeichnerwert hat .
Es wird für uns einfacher, dies zu erkennen, wenn wir uns ein solches mehrwertiges Kontrollkästchen ansehen:
<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>
Bitte beachten Sie, dass wir diesmal das Attribut
th: value hinzugefügt
haben , da das Funktionsfeld nicht wie oben beschrieben logisch ist, sondern ein Array von Werten.
Sehen wir uns die HTML-Ausgabe an, die von diesem Code generiert wird:
<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>
Hier sehen wir, wie das Sequenzsuffix zu jedem
ID- Eingabeattribut hinzugefügt wird und wie die Funktion
# ids.prev (...) es uns ermöglicht, den zuletzt für eine bestimmte Eingabe-
ID generierten Sequenzwert zu extrahieren.
Machen Sie sich keine Sorgen über diese versteckten Eingaben mit
name = "_ features" : Sie werden automatisch hinzugefügt, um Probleme mit Browsern zu vermeiden, die beim Senden des Formulars keine nicht ausgewählten Flag-Werte an den Server senden.
Beachten Sie außerdem, dass, wenn unsere
Features- Eigenschaft einige ausgewählte Werte in unserer Form-Backing-Bean enthält, das
Feld th: dies erledigt und das Attribut
"Checked =" Checked "" zu den entsprechenden Eingabe-Tags hinzufügt.
6.4 Optionsfeldfelder
Die Schalterfelder werden ähnlich wie nicht-boolesche (mehrwertige) Flags gesetzt, außer natürlich, dass sie nicht mehrwertig sind:
<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 Dropdown- / Listenselektoren
Die Auswahlfelder bestehen aus zwei Teilen: dem <select> -Tag und seinen verschachtelten <option> -Tags. Wenn Sie ein Feld dieses Typs erstellen, sollte nur das <select> -Tag das Attribut
th: field enthalten. Die Attribute
th: value in den verschachtelten <option> -Tags sind jedoch sehr wichtig, da sie die Möglichkeit bieten, die aktuell ausgewählte Option herauszufinden (ähnlich wie bei nicht-booleschen Flags und Optionsfeldern) )
Lassen Sie uns ein Dropdown-Feld neu erstellen:
<select th:field="*{type}"> <option th:each="type : ${allTypes}" th:value="${type}" th:text="#{${'seedstarter.type.' + type}}">Wireframe</option> </select>
Zu diesem Zeitpunkt ist es ziemlich einfach, diesen Code zu verstehen. Beachten Sie nur, wie die Attributpriorität es uns ermöglicht, das
th: jedes Attribut im <option> -Tag selbst festzulegen.
6.6 Dynamische Felder
Dank der erweiterten Funktionen zum Binden von Formularfeldern in Spring MVC können wir komplexe
Spring EL- Ausdrücke verwenden, um dynamische Formularfelder an unsere Form-Backing-Bean zu binden. Auf diese Weise können wir neue
Zeilenobjekte in unserer
SeedStarter- Komponente
erstellen und die Felder dieser Zeilen auf Wunsch des Benutzers zu unserem Formular hinzufügen.
Dazu benötigen wir einige neue zugeordnete Methoden in unserem Controller, die abhängig von der Verfügbarkeit bestimmter Anforderungsparameter eine Zeile zu unserem
SeedStarter hinzufügen oder daraus entfernen:
@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"; }
Und jetzt können wir unserem Formular eine dynamische Tabelle hinzufügen:
<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>
Es gibt genug Dinge hier, aber nicht so viele, dass sie nicht verstanden werden ... bis auf eine
seltsame Sache:
<select th:field="*{rows[__${rowStat.index}__].variety}"> ... </select>
Wenn Sie sich an das
Tutorial „
Verwenden von Thymeleaf “ erinnern, ist die Syntax
__ $ {...} __ ein Vorverarbeitungsausdruck, ein interner Ausdruck, der vor der eigentlichen Auswertung des gesamten Ausdrucks ausgewertet wird. Aber warum diese Art der Angabe des Zeilenindex? Wäre das nicht genug mit:
<select th:field="*{rows[rowStat.index].variety}"> ... </select>
... eigentlich nicht. Das Problem ist, dass Spring EL die Variablen in den Klammern des Array-Index nicht auswertet. Wenn Sie also den obigen Ausdruck ausführen, erhalten Sie eine Fehlermeldung, dass
Zeilen [rowStat.index] (anstelle von
Zeilen [0] ,
Zeilen [1] usw. ) ungültige Position in der Zeilensammlung. Deshalb ist hier eine Vorverarbeitung erforderlich.
Schauen wir uns einen Ausschnitt des resultierenden HTML-Codes an, nachdem wir mehrmals auf "Zeile hinzufügen" geklickt haben:
<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>