Skip to content

5. Thymeleaf-Ansichten

Kehren wir zur Architektur einer Spring MVC-Anwendung zurück.

In den beiden vorangegangenen Kapiteln wurden verschiedene Aspekte von Block [1], den Aktionen, beschrieben. Wir werden nun Folgendes besprechen:

  • Block [2] der V-Ansichten;
  • das [3]-Modell (M), das von diesen Ansichten dargestellt wird;

Seit der Einführung von Spring MVC ist JSP (Java Server Pages) die Technologie, die zur Generierung von HTML-Seiten verwendet wird, die an Client-Browser gesendet werden. In den letzten Jahren ist auch die [Thymeleaf] [http://www.thymeleaf.org/]-Technologie verfügbar geworden. Wir werden diese Technologie nun vorstellen.

5.1. Das STS-Projekt

Wir erstellen ein neues Projekt:

  • Geben Sie in [3] an, dass das Projekt die [Thymeleaf]-Abhängigkeiten benötigt. Dadurch werden die [Thymeleaf]-Framework-Abhängigkeiten [5] zu den [Spring MVC]-Abhängigkeiten aus dem vorherigen Projekt hinzugefügt;

Lassen Sie uns dieses Projekt nun wie folgt weiterentwickeln:

  

Wir orientieren uns am vorherigen Projekt:

  • [istia.st.springmvc.controllers] enthält die Controller;
  • [istia.st.springmvc.models] enthält die Aktions- und Ansichtsmodelle;
  • [istia.st.springmvc.main] ist das Paket für die ausführbare Spring-Boot-Klasse;
  • [templates] enthält die Thymeleaf-Ansichten;
  • [i18n] enthält die internationalisierten Meldungen, die von den Views angezeigt werden;

Die [Application]-Klasse sieht wie folgt aus:


package istia.st.springmvc.main;
 
import org.springframework.boot.SpringApplication;
 
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Config.class, args);
    }
}

Die Klasse [Config] sieht wie folgt aus:


package istia.st.springmvc.main;
 
import java.util.Locale;
 
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
 
@Configuration
@ComponentScan({ "istia.st.springmvc.controllers", "istia.st.springmvc.models" })
@EnableAutoConfiguration
public class Config extends WebMvcConfigurerAdapter {
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("i18n/messages");
        return messageSource;
    }
 
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        return localeChangeInterceptor;
    }
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
 
    @Bean
    public CookieLocaleResolver localeResolver() {
        CookieLocaleResolver localeResolver = new CookieLocaleResolver();
        localeResolver.setCookieName("lang");
        localeResolver.setDefaultLocale(new Locale("fr"));
        return localeResolver;
    }
}

Diese Konfiguration aktiviert derzeit die Locale-Verwaltung.

Der [ViewController] sieht wie folgt aus:


package istia.st.springmvc.actions;
 
import org.springframework.stereotype.Controller;
 
@Controller
public class ViewsController {
 
}
  • Zeile 5: Die Annotation [@Controller] hat die Annotation [@RestController] ersetzt, da die Aktionen von nun an keine Antwort an den Client generieren. Stattdessen werden sie:
    • ein Modell M erstellen
    • einen Typ [String] zurückgeben, der den Namen der [Thymeleaf]-Ansicht angibt, die für die Anzeige dieses Modells zuständig ist. Es ist die Kombination aus dieser Ansicht V und diesem Modell M, die den an den Client gesendeten HTML-Stream generiert;

Die Datei [messages.properties] ist derzeit leer.

5.2. [/v01]: Thymeleaf-Grundlagen

Wir sehen uns die nächste Aktion in [ViewsController] an:


    // thymeleaf basics - 1
    @RequestMapping(value = "/v01", method = RequestMethod.GET)
    public String v01() {
        return "v01";
}
  • Zeile 3: Die Aktion gibt einen Typ [String] zurück. Dies ist der Name der Aktion;
  • Zeile 4: Diese Ansicht lautet [v01]. Standardmäßig muss sie sich im Ordner [templates] befinden und den Namen [v01.html] tragen;

Die Ansicht [v01.html] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="'Les vues'">Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <h2 th:text="'Les vues dans Spring MVC'">Spring 4 MVC</h2>
</body>
</html>

Dies ist eine HTML-Datei. Die Verwendung von Thymeleaf ist offensichtlich:

  • im [th]-Namensraum in Zeile 2;
  • in den [th:text]-Attributen in den Zeilen 4 und 8;

Dies ist eine gültige HTML-Datei, die angezeigt werden kann. Wir speichern sie im Ordner [static] [2] unter dem Namen [vue-01.html] und rufen sie direkt über einen Browser auf:

Wenn wir den Quellcode der Seite in [2] untersuchen, sehen wir, dass die [th:text]-Attribute vom Server gesendet und vom Browser ignoriert wurden. Wenn eine Ansicht das Ergebnis einer Aktion ist, greift Thymeleaf ein und interpretiert die [th]-Attribute, bevor die Antwort an den Client gesendet wird.

Der HTML-Tag:


<title th:text="'Les vues'">Spring 4 MVC</title>

wird von Thymeleaf wie folgt verarbeitet:

  • th:text hat die Syntax th:text="Ausdruck", wobei Ausdruck ein auszuwertender Ausdruck ist. Wenn dieser Ausdruck eine Zeichenkette ist, wie in diesem Fall, muss er in einfache Anführungszeichen gesetzt werden;
  • der Wert von [Ausdruck] ersetzt den Text des HTML-Tags, in diesem Fall den Text des [title]-Tags;

Nach der Verarbeitung wird das obige Tag zu:


<title>Les vues</title>

Nennen wir die Aktion [/v01]:

  • In [2] sehen wir die von Thymeleaf durchgeführte Ersetzung;

Rufen wir nun die URL [http://localhost:8080/v01.html] auf:

 

Wie ist das zu interpretieren? Wurde die Ansicht [templates/v01.html] direkt ausgeliefert, ohne dass eine Aktion durchlaufen wurde? Um Klarheit zu schaffen, erstellen wir die folgende Aktion [/v02]:


    // thymeleaf basics - 2
    @RequestMapping(value = "/v02", method = RequestMethod.GET)
    public String v02() {
        System.out.println("action v02");
        return "vue-02";
}

Die Ansicht [vue-02.html] ist eine Kopie von [v01.html]:

  

Rufen wir nun die URL [http://localhost:8080/vue-02.html] auf:

 

Die URL wurde nicht gefunden. Versuchen wir es nun mit der URL [http://localhost:8080/v02.html]

  • In den Konsolenprotokollen unter [1] sehen wir, dass die Aktion [/v02] aufgerufen wurde, wodurch die Ansicht [vue-02.html] unter [2] angezeigt wurde;

Nun wissen wir, dass die URL [http://localhost:8080/v02.html] auch auf eine Datei [/v02.html] im Ordner [static] verweisen kann. Was passiert, wenn diese Datei existiert? Probieren wir es aus. Wir erstellen die folgende Datei [v02.html] im Ordner [static]:

  

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <h2>Spring 4 MVC</h2>
</body>
</html>

Dann rufen wir die URL [http://localhost:8080/v02.html] auf:

[1] und [2] zeigen, dass die Aktion [/v02] aufgerufen wurde. Daraus lässt sich schließen, dass Spring / Thymeleaf, wenn die angeforderte URL die Form [/x.html] hat,

  • die Aktion [/x] ausführt, sofern sie existiert;
  • die Seite [/static/x.html] bereitstellt, sofern sie existiert;
  • andernfalls eine 404 Not Found-Ausnahme auslöst;

Um Verwechslungen zu vermeiden, werden Aktionen und Ansichten von nun an nicht mehr denselben Namen tragen.

5.3. [/v03]: Internationalisierung von Ansichten

Durch die Spring/Thymeleaf-Integration kann Thymeleaf Spring-Nachrichtendateien verwenden. Betrachten Sie die folgende neue Aktion [/v03]:


    // internationalization of views
    @RequestMapping(value = "/v03", method = RequestMethod.GET)
    public String v03() {
        return "vue-03";
}

Es wird die folgende Ansicht [vue-03.html] angezeigt:

  

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{title}">Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <h2 th:text="#{title}">Spring 4 MVC</h2>
</body>
</html>

In den Zeilen 4 und 8 lautet der Ausdruck für das Attribut [th:text] #{title}, dessen Wert die Schlüsselmeldung [title] ist. Wir erstellen die folgenden Dateien [messages_fr.properties] und [messages_en.properties]:

[messages_fr.properties]


title=Les vues dans Spring MVC

[messages_en.properties]


title=Views in Spring MVC

Rufen wir die URLs [http://localhost:8080/v03.html?lang=fr] und [http://localhost:8080/v03.html?lang=en] auf:

Beachten Sie, dass wir das, was wir kürzlich gelernt haben, angewendet haben. Anstatt die Aktion [v03] als [/v03] zu bezeichnen, haben wir sie als [/v03.html] bezeichnet.

5.4. [/v04]: Erstellen der M-Vorlage für eine V-Ansicht

Betrachten Sie die folgende neue Aktion [/v04]:


    // creation of the M model of a V view
    @RequestMapping(value = "/v04", method = RequestMethod.GET)
    public String v04(Model model) {
        model.addAttribute("personne", new Personne(7, "martin", 17));
        System.out.println(String.format("Modèle=%s", model));
        return "vue-04";
}
  • Zeile 4: Das View-Modell wird in die Aktionsparameter eingefügt. Standardmäßig ist dieses anfängliche Modell leer. Wir werden sehen, dass es möglich ist, es vorab zu füllen;
  • Zeile 4: Ein Modell vom Typ [Model] ist eine Art Wörterbuch mit Elementen vom Typ <String, Object>. In Zeile 4 fügen wir diesem Wörterbuch einen Eintrag mit dem Schlüssel [person] hinzu, der mit einem Wert vom Typ [Person] verknüpft ist;
  • Zeile 5: Wir zeigen das Modell auf der Konsole an, um zu sehen, wie es aussieht;
  • Zeile 6: Wir zeigen die Ansicht [vue-04.html] an;

Die Klasse [Person] ist diejenige, die im vorherigen Kapitel verwendet wurde:

  

package istia.st.springmvc.models;
 
public class Personne {
 
    // identifier
    private Integer id;
    // name
    private String nom;
    // age
    private int age;
 
    // manufacturers
    public Personne() {
 
    }
 
    public Personne(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }
 
    public Personne(Integer id, String nom, int age) {
        this(nom, age);
        this.id = id;
    }
 
    @Override
    public String toString() {
        return String.format("[id=%s, nom=%s,  age=%d]", id, nom, age);
    }

    // getters and setters
...
}

Die Ansicht [vue-04.html] sieht wie folgt aus:

  

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <p>
            <span th:text="#{personne.nom}">Nom :</span>
            <span th:text="${personne.nom}">Bill</span>
        </p>
        <p>
            <span th:text="#{personne.age}">Age :</span>
            <span th:text="${personne.age}">56</span>
        </p>
    </body>
</html>
  • Zeile 10 führt einen neuen Thymeleaf-Ausdruckstyp ein, ${var}, wobei var ein Schlüssel aus dem M-Modell der Ansicht ist. Erinnern Sie sich daran, dass die Aktion [/v04] dem Modell einen Schlüssel [person] hinzugefügt hat, der mit dem Typ Person[id, name, age] verknüpft ist;
  • Zeile 10: zeigt den Namen der Person im Modell an;
  • Zeile 14: zeigt ihr Alter an;

Die Nachrichtendateien werden geändert, um die Schlüssel [person.name] und [person.age] aus den Zeilen 9 und 13 hinzuzufügen. Das Ergebnis lautet wie folgt:

und die Eigenschaften des Modells M sind in den Konsolenprotokollen [2] zu finden.

Man könnte sich fragen, warum wir die Ansicht [view-04] nicht wie folgt schreiben:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}"></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <p>
            <span th:text="#{personne.nom}" /></span>
            <span th:text="${personne.nom}"></span>
        </p>
        <p>
            <span th:text="#{personne.age}"></span>
            <span th:text="${personne.age}"></span>
        </p>
    </body>
</html>

Diese Ansicht ist vollkommen gültig und liefert dasselbe Ergebnis wie zuvor. Eines der Ziele von Thymeleaf ist es, sicherzustellen, dass eine Thymeleaf-Seite auch dann angezeigt werden kann, wenn sie nicht über Thymeleaf läuft. Erstellen wir also zwei neue statische Seiten:

  

Die Ansicht [view-04b.html] ist eine Kopie der Ansicht [view-04.html]. Das Gleiche gilt für die Ansicht [view-04a.html], allerdings haben wir den statischen Text von der Seite entfernt. Wenn wir beide Seiten aufrufen, erhalten wir folgende Ergebnisse:

Im Fall [1] wird die Seitenstruktur nicht angezeigt, während sie im Fall [2] deutlich sichtbar ist. Dies ist der Vorteil der Platzierung von statischem Text in einer Thymeleaf-Ansicht, auch wenn dieser zur Laufzeit durch anderen Text ersetzt wird.

Betrachten wir nun ein technisches Detail. In der Ansicht [vue-04.html] formatieren wir den Code mit [Strg+Umschalt+F]. Wir erhalten folgendes Ergebnis:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{title}">Spring 4 MVC</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p>
        <span th:text="#{personne.nom}">Nom :</span> <span
            th:text="${personne.nom}">Bill</span>
    </p>
    <p>
        <span th:text="#{personne.age}">Age :</span> <span
            th:text="${personne.age}">56</span>
    </p>
</body>
</html>

Die Tags sind falsch ausgerichtet, wodurch der Code schwerer lesbar wird. Wenn wir [vue-04.html] in [vue-04.xml] umbenennen und den Code neu formatieren, werden die Tags wieder ausgerichtet. Daher wäre die Endung [xml] praktischer. Es ist möglich, mit dieser Endung zu arbeiten. Dazu müssen wir Thymeleaf konfigurieren. Um zu vermeiden, dass wir unsere bisherigen Änderungen rückgängig machen, duplizieren wir das Projekt [springmvc-vues], das wir bisher untersucht haben, in ein Projekt [springmvc-vues-xml].

  

Wir ändern die Datei [pom.xml] wie folgt:


    <groupId>istia.st.springmvc</groupId>
    <artifactId>springmvc-vues-xml</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>springmvc-vues-xml</name>
<description>Les vues dans Spring MVC</description>

Der Projektname wird in den Zeilen 2 und 6 geändert. Außerdem ändern wir die Endung der Ansichten im Ordner [templates]:

  

Das Dokument [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html] listet die Spring Boot-Konfigurationseigenschaften auf, die in der Datei [application.properties] verwendet werden können:

  

Dieses Dokument listet die Eigenschaften auf, die Spring Boot während der Autokonfiguration verwendet und die durch eine andere Konfiguration von [application.properties] geändert werden können. Für Thymeleaf lauten die Autokonfigurationseigenschaften wie folgt:


# THYMELEAF (<a href="http://github.com/spring-projects/spring-boot/tree/v1.1.9.RELEASE/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java">ThymeleafAutoConfiguration</a>)
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html # ;charset=<encoding> is added
spring.thymeleaf.cache=true # set to false for hot refresh

Wir könnten also einfach die Zeile hinzufügen


spring.thymeleaf.suffix=.xml

in [application.properties] hinzufügen. Wir werden jedoch einen anderen Ansatz wählen: die Konfiguration über Code. Wir werden Thymeleaf in der [Config]-Klasse konfigurieren:


package istia.st.springmvc.main;
 
import java.util.Locale;
 
...
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
 
@Configuration
@ComponentScan({ "istia.st.springmvc.controllers", "istia.st.springmvc.models" })
@EnableAutoConfiguration
public class Config extends WebMvcConfigurerAdapter {
    ...
 
    @Bean
    public SpringResourceTemplateResolver templateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setPrefix("classpath:/templates/");
        templateResolver.setSuffix(".xml");
        templateResolver.setTemplateMode("HTML5");
        templateResolver.setCharacterEncoding("UTF-8");
     templateResolver.setCacheable(true);
        return templateResolver;
    }
 
    @Bean
    SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }
 
}
  • In den Zeilen 16–24 wird ein [TemplateResolver] für Thymeleaf konfiguriert. Dieses Objekt ist dafür zuständig, anhand eines von einer Aktion bereitgestellten View-Namens die entsprechende Datei zu finden;
  • In den Zeilen 18 und 19 werden das Präfix und das Suffix festgelegt, die dem View-Namen hinzugefügt werden, um die Datei zu finden. Wenn der View-Name also [vue04] lautet, lautet die gesuchte Datei [classpath:/templates/vue04.xml]. [classpath:/templates] ist eine Spring-Syntax, die sich auf einen Ordner [/templates] bezieht, der sich im Stammverzeichnis des Klassenpfads des Projekts befindet;
  • Zeile 21: Damit der HTTP-Header in der an den Client gesendeten Antwort lautet:

Content-Type:text/html;charset=UTF-8
  • Zeile 20: gibt an, dass die Ansicht dem HTML5-Standard entspricht;
  • Zeile 22: gibt an, dass Thymeleaf-Ansichten zwischengespeichert werden können;
  • Zeilen 26–31: legt die View-Auflösungs-Engine auf das Paar Spring/Thymeleaf fest, wobei die vorherige Auflösungs-Engine verwendet wird;

Führen wir die ausführbare Datei für dieses neue Projekt aus und rufen wir die URL [http://localhost:8080/v04.html?lang=en] auf:

 

Beachten Sie, dass in der URL die Aktion [/v04] erneut durch [v04.html] ersetzt wurde.

5.5. [/v05]: Ein Objekt in eine Thymeleaf-Ansicht einbinden

Wir erstellen die folgende [/v05]-Aktion:


    // creation of the M model of a V - 2 view
    @RequestMapping(value = "/v05", method = RequestMethod.GET)
    public String v05(Model model) {
        model.addAttribute("personne", new Personne(7, "martin", 17));
        return "vue-05";
}

Sie entspricht der Aktion [/v04]. Die Ansicht [vue-05.xml] sieht wie folgt aus:

  

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div th:object="${personne}">        
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
    </body>
</html>
  • Zeilen 8–17: In diesen Zeilen wird ein Thymeleaf-Objekt durch das Attribut [th:object="${person}"] (Zeile 8) definiert. Dieses Objekt ist das Objekt mit dem Schlüssel [person], das im Modell zu finden ist:
  • Zeile 11: Der Thymeleaf-Ausdruck [*{name}] entspricht [${object.name}], wobei [object] das aktuelle Thymeleaf-Objekt ist. Hier entspricht der Ausdruck [*{name}] also [${person.name}];
  • Zeile 15: wie oben;

Das Ergebnis:

 

5.6. [/v06]: Tests in einer Thymeleaf-Ansicht

Betrachten Sie die folgende [/v06]-Aktion:


    // creation of the M model of a V - 3 view
    @RequestMapping(value = "/v06", method = RequestMethod.GET)
    public String v06(Model model) {
        model.addAttribute("personne", new Personne(7, "martin", 17));
        return "vue-06";
}

Es ist identisch mit den beiden vorherigen Aktionen. Es zeigt die folgende Ansicht [vue-06.xml] an:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div th:object="${personne}">
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
            <p th:if="*{age} >= 18" th:text="#{personne.majeure}">Vous êtes majeur</p>
            <p th:if="*{age} &lt; 18" th:text="#{personne.mineure}">Vous êtes mineur</p>
        </div>
    </body>
</html>
  • Zeile 17: Das Attribut [th:if] wertet einen booleschen Ausdruck aus. Ist dieser Ausdruck wahr, wird das Tag angezeigt; andernfalls nicht. Wenn also hier ${person.age} >= 18 ist, wird der Text [#{person.majeure}] angezeigt, d. h. der Nachrichten-Schlüssel [person.majeure] in den Nachrichtendateien;
  • Zeile 18: Sie können nicht [*{age} < 18] schreiben, da das <-Zeichen ein reserviertes Zeichen ist. Sie müssen daher dessen HTML-Äquivalent [&lt;] verwenden, das auch als HTML-Entität [http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references] bekannt ist;

Die Nachrichtendateien werden geändert:

[messages_fr.properties]


title=Les vues dans Spring MVC
personne.nom=Nom :
personne.age=Age :
personne.mineure=Vous êtes mineur
personne.majeure=Vous êtes majeur

[messages_en.properties]


title=Views in Spring MVC
personne.nom=Name:
personne.age=Age:
personne.mineure=You are under 18
personne.majeure=You are over 18

Das Ergebnis lautet wie folgt:

5.7. [/v07]: Iteration in einer Thymeleaf-Ansicht

Betrachten Sie die folgende Aktion [/v07]:


    // creation of the M model of a V - 4 view
    @RequestMapping(value = "/v07", method = RequestMethod.GET)
    public String v07(Model model) {
        model.addAttribute("liste", new Personne[] { new Personne(7, "martin", 17), new Personne(8, "lucie", 32),
                new Personne(9, "paul", 7) });
        return "vue-07";
}
  • Die Aktion erstellt eine Liste mit drei Personen, fügt sie dem Modell mit dem Schlüssel [list] hinzu und zeigt die Ansicht [view-07] an;

Die Ansicht [view-07.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <h3 th:text="#{liste.personnes}">Liste de personnes</h3>
        <ul>
            <li th:each="element : ${liste}" th:text="'['+ ${element.id} + ', ' +${element.nom}+ ', ' + ${element.age} + ']'">[id,nom,age]</li>
        </ul>
    </body>
</html>
  • Zeile 10: Das Attribut [th:each] wiederholt das Tag, in dem es sich befindet, in diesem Fall ein <li>-Tag. Es hat hier zwei Parameter: [element : collection], wobei [collection] eine Sammlung von Objekten ist, in diesem Fall eine Liste von Personen. Thymeleaf durchläuft die Sammlung und generiert so viele <li>-Tags, wie es Elemente in der Sammlung gibt. Für jedes <li>-Tag repräsentiert [element] das mit dem Tag verknüpfte Element der Sammlung. Für dieses Element wird das Attribut [th:text] ausgewertet. Sein Ausdruck ist hier eine Zeichenfolgenverkettung, die das Ergebnis [id, name, age] erzeugt;
  • Zeile 8: Wir fügen den Schlüssel [liste.personnes] zu den Nachrichten-Dateien hinzu;

Hier ist das Ergebnis:

5.8. [/v08-/v10]: @ModelAttribute

Wir greifen etwas wieder auf, das wir beim Thema Aktionen behandelt haben: die Rolle der Annotation [@ModelAttribute]. Wir fügen die folgende neue Aktion hinzu:


    // --------------- Binding and ModelAttribute ----------------------------------
 
    // if the parameter is an object, it is instantiated and possibly modified by the query parameters
    // it will automatically become part of the view model with the key [key]
    // for @ModelAttribute("xx") parameter, key will equal xx
    // for @ModelAttribute parameter, key will be equal to the parameter's lowercase class name
    // if @ModelAttribute is absent, then everything happens as if it were present without a key
    // note that this automatic presence in the model is not performed if the parameter is not a
 
    @RequestMapping(value = "/v08", method = RequestMethod.GET)
    public String v08(@ModelAttribute("someone") Personne p, Model model) {
        System.out.println(String.format("Modèle=%s", model));
        return "vue-08";
}
  • Zeile 11: Die Annotation [@ModelAttribute("someone")] fügt dem Modell automatisch das Objekt [Person p] hinzu, das mit dem Schlüssel [someone] verknüpft ist;
  • Zeile 12: zum Überprüfen des Modells;
  • Zeile 13: zeigt die Ansicht [vue-08.xml] an;

Die Ansicht [view-08.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div th:object="${someone}">        
            <p>
                <span th:text="#{personne.id}">Id :</span>
                <span th:text="*{id}">14</span>
            </p>
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
    </body>
</html>
  • Zeile 8: Das Thymeleaf-Objekt wird mit dem Schlüsselobjekt [someone] initialisiert;

Das Ergebnis lautet wie folgt:

 

und in der Konsole sehen wir den folgenden Log:

Modèle={someone=[id=4, nom=x,  age=11], org.springframework.validation.BindingResult.someone=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

Betrachten wir nun die folgende [/v09]-Aktion:


    @RequestMapping(value = "/v09", method = RequestMethod.GET)
    public String v09(Personne p, Model model) {
        System.out.println(String.format("Modèle=%s", model));
        return "vue-09";
}
  • Zeile 1: Durch das Vorhandensein des Parameters [Person p] wird die Person [p] automatisch in das Modell eingefügt. Da kein Schlüssel angegeben ist, wird als Schlüssel der Klassenname mit dem ersten Buchstaben in Kleinbuchstaben verwendet. Daher entspricht [Person p] [@ModelAttribute("person") Person p];

Die Ansicht [view.09.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div th:object="${personne}">        
            <p>
                <span th:text="#{personne.id}">Id :</span>
                <span th:text="*{id}">14</span>
            </p>
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
    </body>
</html>
  • Zeile 8: Der verwendete Vorlagenschlüssel lautet [person];

Hier ist das Ergebnis:

 

und das Protokoll in der Serverkonsole:

Modèle={personne=[id=4, nom=x,  age=11], org.springframework.validation.BindingResult.personne=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

Betrachten wir nun die folgende neue Aktion [/v10]:


    @ModelAttribute("uneAutrePersonne")
    private Personne getPersonne(){
        return new Personne(24,"pauline",55);
    }
 
    @RequestMapping(value = "/v10", method = RequestMethod.GET)
    public String v10(Model model) {
        System.out.println(String.format("Modèle=%s", model));
        return "vue-10";
}
  • Zeilen 1–4: Definieren Sie eine Methode, die für jede Anfrage ein Schlüsselelement [anotherPerson] im Modell erstellt, das mit dem Objekt [new Person(24, "pauline", 55)] verknüpft ist;
  • Zeilen 6–10: Die Aktion [/v10] führt nichts weiter aus, als das empfangene Modell an die Ansicht [vue-10.xml] weiterzugeben. Beachten Sie, dass der Parameter [Model model] nur für die Anweisung in Zeile 8 benötigt wird. Ohne ihn ist er überflüssig;

Die Ansicht [view-10.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div th:object="${uneAutrePersonne}">        
            <p>
                <span th:text="#{personne.id}">Id :</span>
                <span th:text="*{id}">14</span>
            </p>
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
    </body>
</html>

Das Ergebnis lautet wie folgt:

 

und der Konsolenausdruck sieht wie folgt aus:

Modèle={uneAutrePersonne=[id=24, nom=pauline,  age=55]}

5.9. [/v11]: @SessionAttributes

Wir greifen etwas wieder auf, das wir beim Studium der Aktionen gesehen haben: die Rolle der Annotation [@SessionAttributes]. Wir fügen die folgende neue Aktion hinzu [/v11]:


    @ModelAttribute("jean")
    private Personne getJean(){
        return new Personne(33,"jean",10);
    }
 
    @RequestMapping(value = "/v11", method = RequestMethod.GET)
    public String v11(Model model, HttpSession session) {
        System.out.println(String.format("Modèle=%s, Session[jean]=%s", model, session.getAttribute("jean")));
        return "vue-11";
}

Wir haben hier etwas Ähnliches wie das, was wir gerade behandelt haben. Der Unterschied liegt in einer [@SessionAttributes]-Annotation, die an der Klasse selbst angebracht ist:


@Controller
@SessionAttributes("jean")
public class ViewsController {
  • Zeile 2: Wir legen fest, dass der Schlüssel [jean] aus dem Modell in die Sitzung aufgenommen werden muss;

Deshalb haben wir in Zeile 7 der Aktion die Sitzung eingefügt. In Zeile 8 zeigen wir den Wert der Sitzung an, der dem Schlüssel [jean] zugeordnet ist.

Die Ansicht [view-11.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div th:object="${jean}">
            <p>
                <span th:text="#{personne.id}">Id :</span>
                <span th:text="*{id}">14</span>
            </p>
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
        <hr />
        <div th:object="${session.jean}">
            <p>
                <span th:text="#{personne.id}">Id :</span>
                <span th:text="*{id}">14</span>
            </p>
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
    </body>
</html>

Es werden zwei Personen angezeigt:

  • Zeilen 8–21: die Person mit dem Schlüssel [jean] im Modell;
  • Zeilen 23–36: die Person mit dem Schlüssel [jean] in der Sitzung;

Die Ergebnisse lauten wie folgt:

  • in [1] die Person mit dem Schlüssel [jean] im Modell;
  • in [2] die Person mit dem Schlüssel [jean] in der Sitzung;

Das Konsolenprotokoll lautet wie folgt:


Modèle={uneAutrePersonne=[id=24, nom=pauline,  age=55], jean=[id=33, nom=jean,  age=10]}, Session[jean]=null

Oben sehen wir, dass der Schlüssel [jean] nicht in der von der Aktion empfangenen Sitzung enthalten ist. Daraus lässt sich schließen, dass der Schlüssel [jean] der Sitzung hinzugefügt wurde, nachdem die Aktion ausgeführt und bevor die Ansicht gerendert wurde.

Betrachten wir nun den Fall, in dem ein Schlüssel sowohl von [@ModelAttribute] als auch von [@SessionAttributes] referenziert wird. Wir erstellen die folgenden zwei Aktionen:


    @RequestMapping(value = "/v12a", method = RequestMethod.GET)
    @ResponseBody
    public void v12a(HttpSession session) {
        session.setAttribute("paul", new Personne(51, "paul", 33));
    }
 
    // if the key of [@ModelAttribute] is also a key of [@SessionAttributes]
    // in this case, the corresponding parameter is initialized with the session value
    @RequestMapping(value = "/v12b", method = RequestMethod.GET)
    public String v12b(Model model, @ModelAttribute("paul") Personne p) {
        System.out.println(String.format("Modèle=%s", model));
        return "vue-12";
}

Die Aktion [/v12a] dient ausschließlich dazu, das Element ['paul', new Person(51, "paul", 33)] in der Sitzung zu speichern. Sie führt keine weiteren Aktionen aus. Die Tatsache, dass sie mit [@ResponseBody] gekennzeichnet ist, zeigt an, dass sie die Antwort an den Client generiert. Da ihr Typ [void] ist, wird keine Antwort generiert.

Die Aktion [/v12b] akzeptiert [@ModelAttribute("paul") Person p] als Parameter. Wenn nichts weiter geschieht, wird ein [Person]-Objekt instanziiert und anschließend mit den Anfrageparametern initialisiert; dieses Objekt hat nichts mit dem Objekt mit dem Schlüssel [paul] zu tun, das von der Aktion [/v12a] in der Sitzung abgelegt wurde. Wir fügen den Schlüssel [paul] zu den Session-Attributen der Klasse hinzu:


@Controller
@SessionAttributes({ "jean", "paul" })
public class ViewsController {
  • Zeile 2 enthält nun zwei Session-Attribute;

Kehren wir zu den Aktionsparametern zurück [/v12b]:


public String v12b(Model model, @ModelAttribute("paul") Personne p) {

Nun wird das Objekt [Person p] nicht instanziiert, sondern verweist auf das Objekt mit dem Schlüssel [paul] in der Sitzung. Der Rest des Prozesses bleibt unverändert. Das Objekt mit dem Schlüssel [paul] erscheint in der Ansichtvorlage, die angezeigt wird. Genau das möchten wir in Zeile 11 der Aktion [/v12b] sehen.

Die Ansicht [vue-12.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <div th:object="${paul}">        
            <p>
                <span th:text="#{personne.id}">Id :</span>
                <span th:text="*{id}">14</span>
            </p>
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
    </body>
</html>
  • Zeile 8: verweist auf den Schlüssel [paul] aus dem View-Modell;

Dies führt zu folgendem Ergebnis (nach Ausführung der Aktion [/v12a], die den Schlüssel [paul] in die Sitzung einfügt):

 

Das Konsolenprotokoll sieht wie folgt aus:


Modèle={jean=[id=33, nom=jean,  age=10], uneAutrePersonne=[id=24, nom=pauline,  age=55], paul=[id=51, nom=paul,  age=33], org.springframework.validation.BindingResult.paul=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

Der Schlüssel [paul] wurde erfolgreich zum Modell hinzugefügt, wobei der Wert dem Schlüssel [paul] in der Sitzung zugeordnet wurde.

5.10. [/v13]: Erstellen eines Eingabeformulars

Wir werden nun die Formulareingabe und -validierung besprechen. Wir werden ein erstes Formular mit der folgenden [/v13]-Aktion erstellen:


  // generates a form for entering a person
  @RequestMapping(value = "/v13", method = RequestMethod.GET)
  public String v13() {
    return "vue-13";
}

was einfach die folgende Ansicht [vue-13.xml] anzeigt:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form action="/someURL" th:action="@{/v14.html}" method="post">
            <h2 th:text="#{personne.formulaire.titre}">Entrez les informations suivantes</h2>
            <div th:object="${personne}">
                <table>
                    <thead></thead>
                    <tbody>
                        <tr>
                            <td th:text="#{personne.id}">Id :</td>
                            <td>
                                <input type="text" name="id" value="11" th:value="''" />
                            </td>
                        </tr>
                        <tr>
                            <td th:text="#{personne.nom}">Nom :</td>
                            <td>
                                <input type="text" name="nom" value="Tintin" th:value="''" />
                            </td>
                        </tr>
                        <tr>
                            <td th:text="#{personne.age}">Age :</td>
                            <td>
                                <input type="text" name="age" value="17" th:value="''" />
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <input type="submit" value="Valider" th:value="#{personne.formulaire.valider}" />
        </form>
    </body>
</html>

Wenn wir diese Ansicht im Ordner [static] unter dem Namen [view-13.html] ablegen und die URL [http://localhost:8080/vue-13.html] aufrufen, erhalten wir die folgende Seite:

 
  • In Zeile 8 des Formulars finden wir das <form>-Tag mit dem Attribut [th:action]. Dieses Attribut wird von Thymeleaf ausgewertet, und sein Wert ersetzt den aktuellen Wert des Attributs [action], das daher nur zur Dekoration dient. Hier lautet der Wert des Attributs [th:action] [/v14.html];
  • In den Zeilen 17, 23 und 29 ersetzt der Wert des Attributs [th:value] den des Attributs [value]. Hier ist dieser Wert die leere Zeichenkette;

Wenn wir die URL [/v13.html] aufrufen, erhalten wir das folgende Ergebnis:

 

Schauen wir uns den von Thymeleaf generierten Quellcode an:


<!DOCTYPE html>
 
<html>
    <head>
        <title>Views in Spring MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form action="/v14.html" method="post">
            <h2>Please, enter information and validate</h2>
            <div>
                <table>
                    <thead></thead>
                    <tbody>
                        <tr>
                            <td>Identifier:</td>
                            <td>
                                <input type="text" name="id" value="" />
                            </td>
                        </tr>
                        <tr>
                            <td>Name:</td>
                            <td>
                                <input type="text" name="nom" value="" />
                            </td>
                        </tr>
                        <tr>
                            <td>Age:</td>
                            <td>
                                <input type="text" name="age" value="" />
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <input type="submit" value="Validate" />
        </form>
    </body>
</html>

In den Zeilen 9, 18, 24 und 30 wird gezeigt, wie Thymeleaf die Attribute [th:action] und [th:value] auswertet.

5.11. [/v14]: Verarbeitung von über ein Formular übermittelten Werten

Die Aktion [/v14] ist die Aktion, die die übermittelten Werte empfängt. Sie sieht wie folgt aus:


  // processes form values
  @RequestMapping(value = "/v14", method = RequestMethod.POST)
  public String v14(Personne p) {
    return "vue-14";
}
  • Zeile 3: Die gesendeten Werte werden in einem Objekt [Person p] gekapselt. Wir wissen, dass dieses Objekt automatisch Teil des M-Modells der V-Ansicht wird, die von der Aktion angezeigt wird, und mit dem Schlüssel [person] verknüpft ist;
  • Zeile 4: Die angezeigte Ansicht ist die Ansicht [vue-14.xml];

Die Ansicht [view-14.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
    <h2 th:text="#{personne.formulaire.saisies}">Voici vos saisies</h2>
        <div th:object="${personne}">        
            <p>
                <span th:text="#{personne.id}">Id :</span>
                <span th:text="*{id}">14</span>
            </p>
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
    </body>
</html>
  • Zeile 9: Das mit dem Schlüssel [person] verknüpfte Objekt aus dem Modell abrufen;
  • Zeilen 12, 16 und 20: Wir zeigen die Eigenschaften dieses Objekts an;

Dies führt zu folgendem Ergebnis:

5.12. [/v15-/v16]: Validierung eines Modells

Schauen wir uns anhand des vorherigen Beispiels die folgende Abfolge an:

  • In [1] geben wir falsche Werte für die Felder [id] und [age] vom Typ [int] ein;
  • In [2] zeigt die Antwort des Servers an, dass zwei Fehler aufgetreten sind;

Wir verwenden dasselbe Formular, leiten den Nutzer jedoch bei Validierungsfehlern auf eine Seite weiter, auf der diese Fehler aufgelistet sind, damit er sie beheben kann.

Die Aktion [/v15] sieht wie folgt aus:


    // ---------------------- form display
    @RequestMapping(value = "/v15", method = RequestMethod.GET)
    public String v15(SecuredPerson p) {
        return "vue-15";
}

Sie erhält den folgenden Typ [SecuredPerson] als Parameter:

  

package istia.st.springmvc.models;
 
import javax.validation.constraints.NotNull;
 
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
 
public class SecuredPerson {
 
    @Range(min = 1)
    private int id;
 
    @Length(min = 4, max = 10)
    private String nom;
 
    @Range(min = 8, max = 14)
    private int age;
 
    // manufacturers
    public SecuredPerson() {
 
    }
 
    public SecuredPerson(int id, String nom, int age) {
        this.id=id;
        this.nom = nom;
        this.age = age;
    }
 
    // getters and setters
...
}

Die Felder [id, name, age] wurden mit Validierungsbeschränkungen versehen. Die von der Aktion [/v15] angezeigte Ansicht [view-15.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form action="/someURL" th:action="@{/v16.html}" method="post">
            <h2 th:text="#{personne.formulaire.titre}">Entrez les informations suivantes</h2>
            <div th:object="${securedPerson}">
                <table>
                    <thead></thead>
                    <tbody>
                        <tr>
                            <td th:text="#{personne.id}">Id :</td>
                            <td>
                                <input type="text" name="id" value="11" th:value="*{id}" />
                            </td>
                            <td>
                                <span th:if="${#fields.hasErrors('id')}" th:errors="*{id}" style="color: red">Identifiant erroné</span>
                            </td>
                        </tr>
                        <tr>
                            <td th:text="#{personne.nom}">Nom :</td>
                            <td>
                                <input type="text" name="nom" value="Tintin" th:value="*{nom}" />
                            </td>
                            <td>
                                <span th:if="${#fields.hasErrors('nom')}" th:errors="*{nom}" style="color: red">Nom erroné</span>
                            </td>
                        </tr>
                        <tr>
                            <td th:text="#{personne.age}">Age :</td>
                            <td>
                                <input type="text" name="age" value="17" th:value="*{age}" />
                            </td>
                            <td>
                                <span th:if="${#fields.hasErrors('age')}" th:errors="*{age}" style="color: red">Âge erroné</span>
                            </td>
                        </tr>
                    </tbody>
                </table>
                <input type="submit" value="Valider" th:value="#{personne.formulaire.valider}" />
                <ul>
                    <li th:each="err : ${#fields.errors('*')}" th:text="${err}" style="color: red" />
                </ul>
            </div>
        </form>
    </body>
</html>
  • Zeilen 10–47: Das dem Schlüssel [securedPerson] zugeordnete Seitenmodellobjekt wird abgerufen. Nach der GET-Anfrage verfügen wir über ein Objekt mit den Instanzierungswerten [id=0, name=null, age=0];
  • Zeile 17: Der Wert des Feldes [securedPerson.id];
  • Zeile 20: Der Ausdruck [${#fields.hasErrors('id')}] ermittelt, ob im Feld [securedPerson.id] Validierungsfehler aufgetreten sind. Ist dies der Fall, zeigt das Attribut [th:errors="*{id}"] die zugehörige Fehlermeldung an;
  • dieses Szenario wiederholt sich in Zeile 29 für das Feld [name] und in Zeile 38 für das Feld [age];
  • Zeile 45: Der Ausdruck [${#fields.errors('*')}] bezieht sich auf alle Fehler in den Feldern des Objekts [securedPerson]. Somit ist es die Menge dieser Fehler, die in den Zeilen 44–46 angezeigt wird;
  • Zeile 16: Wir sehen, dass die Formularwerte an die Aktion [/v16] gesendet werden. Dies geschieht wie folgt:

    // -------------------- model validation------------------
    @RequestMapping(value = "/v16", method = RequestMethod.POST)
    public String v16(@Valid SecuredPerson p, BindingResult result) {
        // mistakes?
        if (result.hasErrors()) {
            return "vue-15";
        } else {
            return "vue-16";
        }
}
  • Zeile 3: Die Annotation [@Valid SecuredPerson p] erzwingt die Validierung der übermittelten Werte;
  • Zeile 5: prüft, ob das Aktionsmodell ungültig ist oder nicht;
  • Zeile 6: Ist es ungültig, wird das Formular [vue-15.xml] zurückgegeben. Da dieses Formular Fehlermeldungen anzeigt, werden wir diese sehen;
  • Zeile 8: Wenn das Aktionsmodell validiert ist, zeigen wir die folgende Ansicht [vue-16.xml] an:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
    <h2 th:text="#{personne.formulaire.saisies}">Voici vos saisies</h2>
        <div th:object="${securedPerson}">        
            <p>
                <span th:text="#{personne.id}">Id :</span>
                <span th:text="*{id}">14</span>
            </p>
            <p>
                <span th:text="#{personne.nom}">Nom :</span>
                <span th:text="*{nom}">Bill</span>
            </p>
            <p>
                <span th:text="#{personne.age}">Age :</span>
                <span th:text="*{age}">56</span>
            </p>
        </div>
    </body>
</html>

Hier sind einige Beispiele für die Ausführung:

5.13. [/v17-/v18]: Fehlermeldungen prüfen

Wenn die Aktion [/v15] zum ersten Mal aufgerufen wird, erhält man folgendes Ergebnis:

 

Möglicherweise möchten Sie anstelle von Nullen in den Feldern [Username, Age] ein leeres Formular anzeigen. Dazu ändern wir das Aktionsmodell wie folgt:


package istia.st.springmvc.models;
 
import javax.validation.constraints.Digits;
 
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;

public class StringSecuredPerson {
 
    @Range(min = 1)
    @Digits(fraction = 0, integer = 4)
    private String id;
 
    @Length(min = 4, max = 10)
    private String nom;
 
    @Range(min = 8, max = 14)
    @Digits(fraction = 0, integer = 2)
    private String age;
 
    // manufacturers
    public StringSecuredPerson() {
 
    }
 
    public StringSecuredPerson(String id, String nom, String age) {
        this.id = id;
        this.nom = nom;
        this.age = age;
    }
 
    // getters and setters
...
 
}
  • Zeilen 12 und 19: Die Felder [id] und [age] werden auf den Typ [String] gesetzt;
  • Zeile 11: Es wird festgelegt, dass das Feld [id] eine Zahl mit höchstens vier Ziffern ohne Dezimalstellen sein muss;
  • Zeile 18: Gleiches gilt für das Feld [age], das eine ganze Zahl mit höchstens zwei Ziffern sein muss;

Die Aktion [/v17] sieht nun wie folgt aus:


    // ---------------------- form display
    @RequestMapping(value = "/v17", method = RequestMethod.GET)
    public String v17(StringSecuredPerson p) {
        return "vue-17";
}

Die von der Aktion [/v17] angezeigte Ansicht [vue-17.xml] sieht wie folgt aus:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{title}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form action="/someURL" th:action="@{/v18.html}" method="post">
            <h2 th:text="#{personne.formulaire.titre}">Entrez les informations suivantes</h2>
            <div th:object="${stringSecuredPerson}">
                <table>
                    <thead></thead>
                    <tbody>
                        <tr>
                            <td th:text="#{personne.id}">Id :</td>
                            <td>
                                <input type="text" name="id" value="11" th:value="*{id}" />
                            </td>
                            <td>
                                <span th:each="err,status : ${#fields.errors('id')}" th:if="${status.index}==0" th:text="${err}" style="color: red">
                                    Identifiant erroné
                                </span>
                            </td>
                        </tr>
                        <tr>
                            <td th:text="#{personne.nom}">Nom :</td>
                            <td>
                                <input type="text" name="nom" value="Tintin" th:value="*{nom}" />
                            </td>
                            <td>
                                <span th:if="${#fields.hasErrors('nom')}" th:errors="*{nom}" style="color: red">Nom erroné</span>
                            </td>
                        </tr>
                        <tr>
                            <td th:text="#{personne.age}">Age :</td>
                            <td>
                                <input type="text" name="age" value="17" th:value="*{age}" />
                            </td>
                            <td>
                                <span th:if="${#fields.hasErrors('age')}" th:errors="*{age}" style="color: red">Âge erroné</span>
                            </td>
                        </tr>
                    </tbody>
                </table>
                <input type="submit" value="Valider" th:value="#{personne.formulaire.valider}" />
                <ul>
                    <li th:each="err : ${#fields.errors('*')}" th:text="${err}" style="color: red" />
                </ul>
            </div>
        </form>
    </body>
</html>

Die Änderungen werden in den folgenden Zeilen vorgenommen:

  • Zeile 10: Wir arbeiten nun mit dem Schlüsselmodellobjekt [stringSecuredPerson];
  • Zeile 20: Wir durchlaufen die Liste der Fehler für das Feld [id]. In der Syntax [th:each="err,status : ${#fields.errors('id')}"] durchläuft die Variable [err] die Liste. Die Variable [status] liefert Informationen zu jeder Iteration. Es handelt sich um ein Objekt [index, count, size, current], wobei:
    • index: die Nummer des aktuellen Elements ist,
    • current: der Wert des aktuellen Elements ist,
    • count, size: die Größe der Liste, durch die iteriert wird;
  • Zeile 20: Wir zeigen nur das erste Element der Liste an [th:if="${status.index}==0"] ;

Die Aktion [/v18], die den POST von Aktion [/v17] verarbeitet, lautet wie folgt:


    // -------------------- model validation------------------
    @RequestMapping(value = "/v18", method = RequestMethod.POST)
    public String v18(@Valid StringSecuredPerson p, BindingResult result) {
        // mistakes?
        if (result.hasErrors()) {
            return "vue-17";
        } else {
            return "vue-18";
        }
}

Die Meldungsdateien werden wie folgt aktualisiert:

[messages_fr.properties]


title=Les vues dans Spring MVC
personne.nom=Nom :
personne.age=Age :
personne.id=Identifiant :
personne.mineure=Vous êtes mineur
personne.majeure=Vous êtes majeur
liste.personnes=Liste de personnes
personne.formulaire.titre=Entrez les informations suivantes et validez
personne.formulaire.valider=Valider
personne.formulaire.saisies=Voici vos saisies
notNull=La donnée est obligatoire
Range.securedPerson.id=L''identifiant doit être un nombre entier >=1
Range.securedPerson.age=Seules les personnes entre 8 et 14 ans sont autorisées sur ce site
Length.securedPerson.nom=Le nom doit avoir entre 1 et 4 caractères
typeMismatch=Donnée invalide
Range.stringSecuredPerson.id=L''identifiant doit être un nombre entier >=1
Range.stringSecuredPerson.age=Seules les personnes entre 8 et 14 ans sont autorisées sur ce site
Length.stringSecuredPerson.nom=Le nom doit avoir entre 1 et 4 caractères
Digits.stringSecuredPerson.id=Tapez un nombre entier de 4 chiffres au plus
Digits.stringSecuredPerson.age=Tapez un nombre entier de 2 chiffres au plus

[messages_en.properties]


title=Views in Spring MVC
personne.nom=Name:
personne.age=Age:
personne.id=Identifier:
personne.mineure=You are under 18
personne.majeure=You are over 18
liste.personnes=Persons' list
personne.formulaire.titre=Please, enter information and validate
personne.formulaire.valider=Validate
personne.formulaire.saisies=Here are your inputs
NotNull=Data is required
Range.securedPerson.id=Identifier must be an integer >=1
Range.securedPerson.age=Only kids who are 8 to 14 years old are allowed on this site
Length.securedPerson.nom=Name must be 4 to 10 characters long
typeMismatch=Invalid format
Range.stringSecuredPerson.id=Identifier must be an integer >=1
Range.stringSecuredPerson.age=Only kids who are 8 to 14 years old are allowed on this site
Length.stringSecuredPerson.nom=Name must be 4 to 10 characters long
Digits.stringSecuredPerson.id=Should be an integer with at most four digits
Digits.stringSecuredPerson.age=Should be an integer with at most two digits

Schauen wir uns ein paar Beispiele an:

 

In [1] sehen wir, dass beide Validatoren für das Feld [age] ausgeführt wurden:


    @Range(min = 8, max = 14)
    @Digits(fraction = 0, integer = 2)
    private String age;

Gibt es eine bestimmte Reihenfolge für die Fehlermeldungen? Für das Feld [age] scheint es, als seien die Validatoren in der Reihenfolge [Digits, Range] ausgeführt worden. Wenn wir jedoch mehrere Anfragen stellen, können wir feststellen, dass sich diese Reihenfolge ändern kann. Daher können wir uns nicht auf die Reihenfolge der Validatoren verlassen. In [2] wird nur eine der beiden Fehlermeldungen für das Feld [id] angezeigt. In [3] werden alle Fehlermeldungen angezeigt.

5.14. [/v19-/v20]: Verwendung verschiedener Validatoren

Betrachten Sie das folgende neue Aktionsmodell:

  

package istia.st.springmvc.models;
 
import java.util.Date;
 
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
 
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;
import org.springframework.format.annotation.DateTimeFormat;
 
public class Form19 {
 
    @NotNull
    @AssertFalse
    private Boolean assertFalse;
 
    @NotNull
    @AssertTrue
    private Boolean assertTrue;
 
    @NotNull
    @Future
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date dateInFuture;
 
    @NotNull
    @Past
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date dateInPast;
 
    @NotNull
    @Max(value = 100)
    private Integer intMax100;
 
    @NotNull
    @Min(value = 10)
    private Integer intMin10;
 
    @NotNull
    @NotEmpty
    private String strNotEmpty;
 
    @NotNull
    @NotBlank
    private String strNotBlank;
 
    @NotNull
    @Size(min = 4, max = 6)
    private String strBetween4and6;
 
    @NotNull
    @Pattern(regexp = "^\\d{2}:\\d{2}:\\d{2}$")
    private String hhmmss;
 
    @NotNull
    @Email
    @NotBlank
    private String email;
 
    @NotNull
    @Length(max = 4, min = 4)
    private String str4;
 
    @Range(min = 10, max = 14)
    @NotNull
    private Integer int1014;
 
    @URL
    @NotBlank
    private String url;
 
    // getters and setters
...
}

Es wird durch die folgende [/v19]-Aktion angezeigt:


    // ------------------ form display
    @RequestMapping(value = "/v19", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String v19(Form19 formulaire) {
        return "vue-19";
}
  • Zeile 3: Die Aktion erhält ein [Form19-Formular]-Objekt als Parameter. Wenn die GET-Anfrage keine Parameter erhält, wird dieses Objekt mit den Standardwerten von Java initialisiert;
  • Zeile 4: Die Ansicht [vue-19.xml] wird angezeigt. Sie sieht wie folgt aus:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="/css/form19.css" />
    </head>
    <body>
        <h3>Formulaire - Validations côté serveur</h3>
        <form action="/someURL" th:action="@{/v20.html}" method="post" th:object="${form19}">
            <table>
                <thead>
                    <tr>
                        <th class="col1">Contrainte</th>
                        <th class="col2">Saisie</th>
                        <th class="col3">Erreur</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="col1">@NotEmpty</td>
                        <td class="col2">
                            <input type="text" th:field="*{strNotEmpty}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('strNotEmpty')}" th:errors="*{strNotEmpty}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@NotBlank</td>
                        <td class="col2">
                            <input type="text" th:field="*{strNotBlank}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('strNotBlank')}" th:errors="*{strNotBlank}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@assertFalse</td>
                        <td class="col2">
                            <input type="radio" th:field="*{assertFalse}" value="true" />
                            <label th:for="${#ids.prev('assertFalse')}">True</label>
                            <input type="radio" th:field="*{assertFalse}" value="false" />
                            <label th:for="${#ids.prev('assertFalse')}">False</label>
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('assertFalse')}" th:errors="*{assertFalse}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@assertTrue</td>
                        <td class="col2">
                            <select th:field="*{assertTrue}">
                                <option value="true">True</option>
                                <option value="false">False</option>
                            </select>
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('assertTrue')}" th:errors="*{assertTrue}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Past</td>
                        <td class="col2">
                            <input type="date" th:field="*{dateInPast}" th:value="*{dateInPast}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('dateInPast')}" th:errors="*{dateInPast}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Future</td>
                        <td class="col2">
                            <input type="date" th:field="*{dateInFuture}" th:value="*{dateInFuture}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('dateInFuture')}" th:errors="*{dateInFuture}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Max</td>
                        <td class="col2">
                            <input type="text" th:field="*{intMax100}" th:value="*{intMax100}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('intMax100')}" th:errors="*{intMax100}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Min</td>
                        <td class="col2">
                            <input type="text" th:field="*{intMin10}" th:value="*{intMin10}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('intMin10')}" th:errors="*{intMin10}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Size</td>
                        <td class="col2">
                            <input type="text" th:field="*{strBetween4and6}" th:value="*{strBetween4and6}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('strBetween4and6')}" th:errors="*{strBetween4and6}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Pattern(hh:mm:ss)</td>
                        <td class="col2">
                            <input type="text" th:field="*{hhmmss}" th:value="*{hhmmss}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('hhmmss')}" th:errors="*{hhmmss}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Email</td>
                        <td class="col2">
                            <input type="text" th:field="*{email}" th:value="*{email}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Length</td>
                        <td class="col2">
                            <input type="text" th:field="*{str4}" th:value="*{str4}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('str4')}" th:errors="*{str4}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@Range</td>
                        <td class="col2">
                            <input type="text" th:field="*{int1014}" th:value="*{int1014}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('int1014')}" th:errors="*{int1014}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">@URL</td>
                        <td class="col2">
                            <input type="text" th:field="*{url}" th:value="*{url}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('url')}" th:errors="*{url}" class="error">Donnée erronée</span>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p>
                <input type="submit" value="Valider" />
            </p>
        </form>
    </body>
</html>

Dieser Code zeigt die folgende Ansicht an:

 

Die Seite zeigt eine dreispaltige Tabelle an:

  • Spalte 1: die Validierung des Eingabefelds;
  • Spalte 2: das Eingabefeld;
  • Spalte 3: Fehlermeldungen für das Eingabefeld;

Betrachten wir zum Beispiel den View-Code [/v19.html] für den Validator [@Pattern]:


                    <tr>
                        <td class="col1">@Pattern(hh:mm:ss)</td>
                        <td class="col2">
                            <input type="text" th:field="*{hhmmss}" th:value="*{hhmmss}" />
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('hhmmss')}" th:errors="*{hhmmss}" class="error">Donnée erronée</span>
                        </td>
</tr>

Wir sehen hier den Code, den wir gerade bei den [Person]-Formularen behandelt haben:

  • Zeile 2: die erste Spalte: der Name des zu testenden Validators;
  • Zeile 4: Das Thymeleaf-Attribut [th:field="*{hhmmss}] generiert die HTML-Attribute [id="hhmmss"] und [name="hhmmss"]. Das Thymeleaf-Attribut [th:value="*{hhmmss}"] generiert das HTML-Attribut [value="Wert von [form19.hhmmss]]";
  • Zeile 7: Wenn der für das Feld [form19.hhmmss] eingegebene Wert falsch ist, zeigt Zeile 7 die mit diesem Feld verbundenen Fehlermeldungen an;

Die übermittelten Werte werden durch die folgende [/v20]-Aktion verarbeitet:


    // ----------------- form template validation
    @RequestMapping(value = "/v20", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
    public String v20(@Valid Form19 formulaire, BindingResult result, RedirectAttributes redirectAttributes) {
        if (result.hasErrors()) {
            return "vue-19";
        } else {
            // redirection to [vue-19]
            redirectAttributes.addFlashAttribute("form19", formulaire);
            return "redirect:/v19.html";
        }
}
  • Zeile 3: Die übermittelten Werte füllen die Felder des Objekts [Form19 form] aus, sofern sie gültig sind;
  • Zeilen 4–6: Sind die übermittelten Werte ungültig, wird das Formular [view-19] mit Fehlermeldungen erneut angezeigt;
  • Zeilen 6–10: Sind die übermittelten Werte gültig, wird das mit diesen Werten erstellte [Form19]-Objekt für die nächste Anfrage, in diesem Fall die Weiterleitung, bereitgestellt. Anschließend wird es gelöscht;
  • Zeile 9: Der Client wird zur Aktion [/v19.html] weitergeleitet. Dadurch wird das Formular [vue-19] erneut angezeigt, das Code wie den folgenden enthält:

<form action="/someURL" th:action="@{/v20.html}" method="post" th:object="${form19}">

Das Attribut [th:object="${form19}"] ruft dann das mit dem Flash-Attribut [form19] verknüpfte Objekt ab und zeigt das Formular somit so an, wie es eingegeben wurde.

Der Formularcode bedarf einer näheren Erläuterung. Betrachten Sie den folgenden Code:


                    <tr>
                        <td class="col1">@assertFalse</td>
                        <td class="col2">
                            <input type="radio" th:field="*{assertFalse}" value="true" />
                            <label th:for="${#ids.prev('assertFalse')}">True</label>
                            <input type="radio" th:field="*{assertFalse}" value="false" />
                            <label th:for="${#ids.prev('assertFalse')}">False</label>
                        </td>
                        <td class="col3">
                            <span th:if="${#fields.hasErrors('assertFalse')}" th:errors="*{assertFalse}" class="error">Donnée erronée</span>
                        </td>
</tr>

Dies erzeugt den folgenden HTML-Code:


<tr>
  <td class="col1">@assertFalse</td>
  <td class="col2">
    <input type="radio" value="true" id="assertFalse1" name="assertFalse" />
    <label for="assertFalse1">True</label>
    <input type="radio" value="false" id="assertFalse2" name="assertFalse" />
    <label for="assertFalse2">False</label>
  </td>
  <td class="col3">
  </td>
</tr>

Im Code


<input type="radio" th:field="*{assertFalse}" value="true" />
<label th:for="${#ids.prev('assertFalse')}">True</label>
<input type="radio" th:field="*{assertFalse}" value="false" />
<label th:for="${#ids.prev('assertFalse')}">False</label>

Die Thymeleaf-Attribute in den Zeilen 1 und 3 [th:field="*{assertFalse}"] stellen ein Problem dar. Wir haben festgestellt, dass dieses Attribut die HTML-Attribute [id=assertFalse] und [name=assertFalse] generiert. Die Schwierigkeit entsteht dadurch, dass dies in den Zeilen 1 und 3 generiert wird, sodass wir am Ende zwei identische [name]-Attribute und zwei identische [id]-Attribute erhalten. Während dies beim [name]-Attribut möglich ist, ist es beim [id]-Attribut nicht der Fall. Wie im generierten HTML-Code zu sehen ist, hat Thymeleaf zwei verschiedene [id]-Attribute generiert: [id=assertFalse1] und [id=assertFalse2]. Das ist gut so. Das Problem ist, dass wir diese Bezeichner nicht kennen und sie möglicherweise benötigen. Dies ist beim [label]-Tag in Zeile 2 der Fall. Das [for]-Attribut eines HTML-[label]-Tags muss auf ein [id]-Attribut verweisen, in diesem Fall auf dasjenige, das für das [input]-Tag in Zeile 1 generiert wurde. In der Thymeleaf-Dokumentation heißt es, dass der Ausdruck [${#ids.prev('assertFalse')}] das letzte [id]-Attribut abruft, das für das Feld [assertFalse] generiert wurde.

Sehen wir uns nun den Code für die Dropdown-Liste des Formulars an:


<select th:field="*{assertTrue}">
   <option value="true">True</option>
   <option value="false">False</option>
</select>

Dieser Code generiert den HTML-Code für eine Dropdown-Liste:

1
2
3
4
<select id="assertTrue" name="assertTrue">
  <option value="true">True</option>
  <option value="false">False</option>
</select>

Der übermittelte Wert wird unter dem Namen [name="assertTrue"] gesendet.

Die Ansicht [vue-19.xml] verwendet ein Stylesheet:


    <head>
        <title>Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="/css/form19.css" />
</head>

Zeile 4: Das verwendete Stylesheet muss im Ordner [static] des Projekts abgelegt werden:

  

Der Inhalt lautet wie folgt:


@CHARSET "UTF-8";
 
.col1 {
    background: lightblue;
}
 
.col2 {
    background: Cornsilk;
}
 
.col3 {
    background: #e2d31d;
}
 
.error {
    color: red;
}

Schauen wir uns nun die Daten an:


    @NotNull
    @Future
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date dateInFuture;
 
    @NotNull
    @Past
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date dateInPast;

Die Überprüfung des Netzwerkverkehrs in Chrome DevTools (Strg+Umschalt+I) zeigt, dass Datumsangaben im Format (yyyy-mm-dd) gesendet werden:

 

Aus diesem Grund wurden die Datumsangaben mit dem Validator wie folgt markiert:


@DateTimeFormat(pattern = "yyyy-MM-dd")

, der das erwartete Format für die übermittelten Datumswerte festlegt.

Schließlich die französische Sprachdatei [messages_fr.properties]:


title=Les vues dans Spring MVC
personne.nom=Nom :
personne.age=Age :
personne.id=Identifiant :
personne.mineure=Vous êtes mineur
personne.majeure=Vous êtes majeur
liste.personnes=Liste de personnes
personne.formulaire.titre=Entrez les informations suivantes et validez
personne.formulaire.valider=Valider
personne.formulaire.saisies=Voici vos saisies
NotNull=La donnée est obligatoire
Range.securedPerson.id=L''identifiant doit être un nombre entier >=1
Range.securedPerson.age=Seules les personnes entre 8 et 14 ans sont autorisées sur ce site
Length.securedPerson.nom=Le nom doit avoir entre 1 et 4 caractères
typeMismatch=Donnée invalide
Range.stringSecuredPerson.id=L''identifiant doit être un nombre entier >=1
Range.stringSecuredPerson.age=Seules les personnes entre 8 et 14 ans sont autorisées sur ce site
Length.stringSecuredPerson.nom=Le nom doit avoir entre 1 et 4 caractères
Digits.stringSecuredPerson.id=Tapez un nombre entier de 4 chiffres au plus
Digits.stringSecuredPerson.age=Tapez un nombre entier de 2 chiffres au plus
Future.form19.dateInFuture=La date doit être postérieure à celle d''aujourd'hui
Past.form19.dateInPast=La date doit être antérieure à celle d''aujourd'hui
Size.form19.strBetween4and6=la chaîne doit avoir entre 4 et 6 caractères
Min.form19.intMin10=La valeur doit être supérieure ou égale à 10
Max.form19.intMax100=La valeur doit être inférieure ou égale à 100
Length.form19.str4=La chaîne doit avoir quatre caractères exactement
Email.form19.email=Adresse mail invalide
URL.form19.url=URL invalide
Range.form19.int1014=La valeur doit être dans l''intervalle [10,14]
AssertTrue=Seule la valeur True est acceptée
AssertFalse=Seule la valeur False est acceptée
Pattern.form19.hhmmss=Tapez l''heure sous la forme hh:mm:ss
NotEmpty=La donnée ne peut être vide
NotBlank=La donnée ne peut être vide

Sehen wir uns einige Ausführungsbeispiele an:

 
 

Oben, zwischen [1] und [2], sieht es so aus, als wäre nichts passiert. Wenn wir uns jedoch den Netzwerkverkehr ansehen (Strg-Umschalt-I), stellen wir fest, dass zwei Netzwerkkommunikationen mit dem Server stattgefunden haben:

  • in [1] der anfängliche POST an [/v20];
  • in [2] ist die Antwort auf diese Aktion eine Weiterleitung;
  • bei [3] die zweite Anfrage, diesmal an [/v19];

Die Aktion [/v19] wird dann ausgeführt:


    // ------------------ form display
    @RequestMapping(value = "/v19", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String v19(Form19 formulaire) {
        return "vue-19";
}
  • Zeile 3: Der Parameter [Form19 form] wird mit dem Flash-Attribut [form19] initialisiert, das durch die vorherige Aktion [/v19] erstellt wurde und ein Objekt vom Typ [Form19] ist, das die an die Aktion [/v19] übermittelten Werte enthält;
  • Zeile 4: Die Ansicht [view-19.xml] wird mit einem Objekt [Form19 form] in ihrer Vorlage angezeigt, das mit den übermittelten Werten initialisiert ist. Deshalb sieht der Benutzer das Formular genau so, wie er es übermittelt hat;

Warum eine Weiterleitung? Warum haben wir nicht einfach an die obige Aktion [/v19] gesendet? Wir hätten das gleiche Ergebnis erhalten, mit einigen Unterschieden:

  • Der Browser hätte [http://localhost:8080/v20.html] in seine Adressleiste eingegeben statt [http://localhost:8080/v19.html], wie es hier der Fall war, da er die zuletzt aufgerufene URL anzeigt;
  • Wenn der Benutzer die Seite aktualisiert (F5), ist das Ergebnis völlig anders:
    • Im Falle einer Weiterleitung lautet die angezeigte URL [http://localhost:8080/v19.html], die über eine GET-Anfrage abgerufen wurde. Der Browser führt diesen Befehl erneut aus und erhält dann ein brandneues Formular (das Flash-Attribut wird nur einmal verwendet);
    • im Falle einer Nicht-Weiterleitung lautet die angezeigte URL [http://localhost:8080/v20.html], die über eine POST-Anfrage abgerufen wurde. Der Browser führt diesen Befehl erneut aus und führt somit einen weiteren POST mit denselben Werten wie zuvor durch. Hier hat dies keine Konsequenzen, ist jedoch oft unerwünscht, weshalb eine Weiterleitung im Allgemeinen bevorzugt wird;

5.15. [/v21-/v22]: Umgang mit Radiobuttons

Betrachten Sie die folgende Spring-Komponente [Lists]:

  

package istia.st.springmvc.models;

import org.springframework.stereotype.Component;
 
@Component
public class Listes {
 
    private String[] deplacements = new String[] { "0", "1", "2", "3", "4" };
    private String[] libellesDeplacements = new String[] { "vélo", "marche", "train", "avion", "autre" };
    private String[] libellesBijoux = new String[] { "émeraude", "rubis", "diamant", "opaline" };
 
    // getters and setters
  ...
 
}
  • Zeile 5: Die Klasse [Lists] wird eine Spring-Komponente sein;
  • Zeilen 8–10: Listen, die zum Befüllen von Optionsfeldern, Kontrollkästchen und Dropdown-Listen verwendet werden;

In der Konfigurationsklasse [Config] steht:


@Configuration
@ComponentScan({ "istia.st.springmvc.controllers", "istia.st.springmvc.models" })
@EnableAutoConfiguration
public class Config extends WebMvcConfigurerAdapter {
  • Zeile 2: Das Paket [models], in dem sich die Komponente [Lists] befindet, wird von Spring gescannt;

Wir erstellen die folgenden neuen Aktionen:


    // ------------------ form with radio buttons
    @Autowired
    private Listes listes;
 
    @RequestMapping(value = "/v21", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String v21(@ModelAttribute("form") Form21 formulaire, Model model) {
        model.addAttribute("listes", listes);
        return "vue-21";
    }
 
    @RequestMapping(value = "/v22", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
    public String v22(@ModelAttribute("form") Form21 formulaire, RedirectAttributes redirectAttributes) {
        redirectAttributes.addFlashAttribute("form", formulaire);
        return "redirect:/v21.html";
}
  • Zeilen 2–3: Die Komponente [Lists] wird in den Controller injiziert;
  • Zeile 6: Wir verarbeiten ein [Form21]-Formular, das wir noch beschreiben werden. Beachten Sie, dass wir dessen Schlüssel [form] im View-Modell angegeben haben. Erinnern Sie sich daran, dass dies standardmäßig [form21] gewesen wäre;
  • Zeile 7: Wir injizieren die [Lists]-Komponente in das Modell. Die Ansicht wird sie benötigen;
  • Zeile 8: Wir zeigen die Ansicht [vue-21.xml] an. Diese Ansicht zeigt das Formular [Form21] an, und die übermittelten Werte werden in den Zeilen 12–15 an die Aktion [/v22] gesendet;
  • Zeilen 12–15: Die Aktion [/v22] leitet einfach zur Aktion [/v21] weiter und legt die empfangenen übermittelten Werte in einem Flash-Attribut mit dem Schlüssel [form] ab. Es ist wichtig, dass dieser Schlüssel mit dem in Zeile 6 verwendeten übereinstimmt;

Das Modell [Form21] sieht wie folgt aus:

  

package istia.st.springmvc.models;
 
public class Form21 {
 
    // posted values
    private String marie = "non";
    private String deplacement = "4";
    private String[] couleurs;
    private String strCouleurs;
    private String[] bijoux;
    private String strBijoux;
    private int couleur2;
    private int[] bijoux2;
    private String strBijoux2;
 
    // getters and setters
    ...
}

Die Ansicht [view-21.xml] sieht wie folgt aus:


<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="/css/form19.css" />
    </head>
    <body>
 
        <h3>Formulaire - Boutons radio</h3>
        <form action="/someURL" th:action="@{/v22.html}" method="post" th:object="${form}">
            <table>
                <thead>
                    <tr>
                        <th class="col1">Texte</th>
                        <th class="col2">Saisie</th>
                        <th class="col3">Valeur</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="col1">Etes-vous marié(e)</td>
                        <td class="col2">
                            <input type="radio" th:field="*{marie}" value="oui" />
                            <label th:for="${#ids.prev('marie')}">Oui</label>
                            <input type="radio" th:field="*{marie}" value="non" />
                            <label th:for="${#ids.prev('marie')}">Non</label>
                        </td>
                        <td class="col3">
                            <span th:text="*{marie}"></span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">Mode de déplacement</td>
                        <td class="col2">
                            <span th:each="mode, status : ${listes.deplacements}">
                                <input type="radio" th:field="*{deplacement}" th:value="${mode}" />
                                <label th:for="${#ids.prev('deplacement')}" th:text="${listes.libellesDeplacements[status.index]}">Autre</label>
                            </span>
                        </td>
                        <td class="col3">
                            <span th:text="*{deplacement}"></span>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p>
                <input type="submit" value="Valider" />
            </p>
        </form>
    </body>
</html>
  • Zeilen 36–40: Beachten Sie die Verwendung der Komponente [Lists] in der Vorlage zur Generierung der Beschriftungen für die Kontrollkästchen;
  • Spalte 3 zeigt den per POST übermittelten Wert oder den Anfangswert des Formulars während der ersten GET-Anfrage an;

Dieser Code zeigt die folgende Seite an:

 

entsprechend dem folgenden HTML-Code:


<!DOCTYPE HTML>
 
<html>
    <head>
        <title>Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="/css/form19.css" />
    </head>
    <body>
 
        <h3>Formulaire - Boutons radio</h3>
        <form action="/v22.html" method="post">
            <table>
                <thead>
                    <tr>
                        <th class="col1">Texte</th>
                        <th class="col2">Saisie</th>
                        <th class="col3">Valeur</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="col1">Etes-vous marié(e)</td>
                        <td class="col2">
                            <input type="radio" value="oui" id="marie1" name="marie" />
                            <label for="marie1">Oui</label>
                            <input type="radio" value="non" id="marie2" name="marie" checked="checked" />
                            <label for="marie2">Non</label>
                        </td>
                        <td class="col3">
                            <span>non</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">Mode de déplacement</td>
                        <td class="col2">
                            <span>
                                <input type="radio" value="0" id="deplacement1" name="deplacement" />
                                <label for="deplacement1">vélo</label>
                            </span>
                            <span>
                                <input type="radio" value="1" id="deplacement2" name="deplacement" />
                                <label for="deplacement2">marche</label>
                            </span>
                            <span>
                                <input type="radio" value="2" id="deplacement3" name="deplacement" />
                                <label for="deplacement3">train</label>
                            </span>
                            <span>
                                <input type="radio" value="3" id="deplacement4" name="deplacement" />
                                <label for="deplacement4">avion</label>
                            </span>
                            <span>
                                <input type="radio" value="4" id="deplacement5" name="deplacement" checked="checked" />
                                <label for="deplacement5">autre</label>
                            </span>
                        </td>
                        <td class="col3">
                            <span>4</span>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p>
                <input type="submit" value="Valider" />
            </p>
        </form>
    </body>
</html>

Wir können sehen, dass die übermittelten Werte (Namensattribute) an die folgenden Felder im Modell [Form21] übermittelt werden:


    private String marie = "non";
    private String deplacement = "4";

Leser werden gebeten, Tests durchzuführen. Beachten Sie, dass das [value]-Attribut der Optionsfelder übermittelt wird.

5.16. [/v23-/v24]: Verwaltung von Kontrollkästchen

Wir fügen die folgende neue Aktion hinzu:


    // ------------------ form with checkboxes
    @RequestMapping(value = "/v23", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String av20(@ModelAttribute("form") Form21 formulaire, Model model) {
        model.addAttribute("listes", listes);
        return "vue-23";
}
  • Zeile 3: Wir verwenden weiterhin das Modell [Form21];

Die Ansicht [vue-23.xml] sieht wie folgt aus:


<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="/css/form19.css" />
    </head>
    <body>
        <h3>Formulaire - Cases à cocher</h3>
        <form action="/someURL" th:action="@{/v24.html}" method="post" th:object="${form}">
            <table>
                <thead>
                    <tr>
                        <th class="col1">Texte</th>
                        <th class="col2">Saisie</th>
                        <th class="col3">Valeur</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="col1">Vos couleurs préférées</td>
                        <td class="col2">
                            <input type="checkbox" th:field="*{couleurs}" value="0" />
                            <label th:for="${#ids.prev('couleurs')}">rouge</label>
                            <input type="checkbox" th:field="*{couleurs}" value="1" />
                            <label th:for="${#ids.prev('couleurs')}">vert</label>
                            <input type="checkbox" th:field="*{couleurs}" value="2" />
                            <label th:for="${#ids.prev('couleurs')}">bleu</label>
                        </td>
                        <td class="col3">
                            <span th:text="*{strCouleurs}"></span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">Pierres préférées</td>
                        <td class="col2">
                            <span th:each="label, status : ${listes.libellesBijoux}">
                                <input type="checkbox" th:field="*{bijoux}" th:value="${status.index}" />
                                <label th:for="${#ids.prev('bijoux')}" th:text="${label}">Autre</label>
                            </span>
                        </td>
                        <td class="col3">
                            <span th:text="*{strBijoux}"></span>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p>
                <input type="submit" value="Valider" />
            </p>
        </form>
    </body>
</html>
  • Zeilen 37–41: Beachten Sie die Verwendung der Komponente [Lists] zur Generierung der Beschriftungen für die Kontrollkästchen;

Dieser Code zeigt die folgende Seite an:

 

generiert aus dem folgenden HTML-Code:


<!DOCTYPE HTML>
 
<html>
    <head>
        <title>Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="/css/form19.css" />
    </head>
    <body>
        <h3>Formulaire - Cases à cocher</h3>
        <form action="/v24.html" method="post">
            <table>
                <thead>
                    <tr>
                        <th class="col1">Texte</th>
                        <th class="col2">Saisie</th>
                        <th class="col3">Valeur</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="col1">Vos couleurs préférées</td>
                        <td class="col2">
                            <input type="checkbox" value="0" id="couleurs1" name="couleurs" /><input type="hidden" name="_couleurs" value="on" />
                            <label for="couleurs1">rouge</label>
                            <input type="checkbox" value="1" id="couleurs2" name="couleurs" /><input type="hidden" name="_couleurs" value="on" />
                            <label for="couleurs2">vert</label>
                            <input type="checkbox" value="2" id="couleurs3" name="couleurs" /><input type="hidden" name="_couleurs" value="on" />
                            <label for="couleurs3">bleu</label>
                        </td>
                        <td class="col3">
                            <span></span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">Pierres préférées</td>
                        <td class="col2">
                            <span>
                                <input type="checkbox" value="0" id="bijoux1" name="bijoux" /><input type="hidden" name="_bijoux" value="on" />
                                <label for="bijoux1">émeraude</label>
                            </span>
                            <span>
                                <input type="checkbox" value="1" id="bijoux2" name="bijoux" /><input type="hidden" name="_bijoux" value="on" />
                                <label for="bijoux2">rubis</label>
                            </span>
                            <span>
                                <input type="checkbox" value="2" id="bijoux3" name="bijoux" /><input type="hidden" name="_bijoux" value="on" />
                                <label for="bijoux3">diamant</label>
                            </span>
                            <span>
                                <input type="checkbox" value="3" id="bijoux4" name="bijoux" /><input type="hidden" name="_bijoux" value="on" />
                                <label for="bijoux4">opaline</label>
                            </span>
                        </td>
                        <td class="col3">
                            <span></span>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p>
                <input type="submit" value="Valider" />
            </p>
        </form>
    </body>
</html>

Beachten Sie, dass die übermittelten Werte (Namensattribute) an die folgenden Felder in [Form21] übermittelt werden:


    private String[] couleurs;
    private String[] bijoux;

Es handelt sich um Arrays, da es für jedes Feld mehrere Kontrollkästchen gibt, die mit dem Namen des Feldes beschriftet sind. Daher ist es möglich, dass mehrere übermittelte Werte mit demselben Namen (dem name-Attribut des Formulars) eingehen. Um diese abzurufen, ist daher ein Array erforderlich.

Kehren wir zum Thymeleaf-Code in Spalte 3 der Seite zurück:


  <td class="col3">
    <span th:text="*{strCouleurs}"></span>
  </td>
</tr>
<tr>
  <td class="col1">Pierres préférées</td>
  <td class="col2">
    <span th:each="label, status : ${listes.libellesBijoux}">
      <input type="checkbox" th:field="*{bijoux}" th:value="${status.index}" />
      <label th:for="${#ids.prev('bijoux')}" th:text="${label}">Autre</label>
    </span>
  </td>
  <td class="col3">
    <span th:text="*{strBijoux}"></span>
  </td>
</tr>

Die in den Zeilen 2 und 14 genannten Felder lauten wie folgt:


    private String strCouleurs;
    private String strBijoux;

Sie werden von der Aktion [/v24] berechnet, die den POST-Request verarbeitet:


    // mapper Jackson / jSON
    private ObjectMapper mapper = new ObjectMapper();
 
    @RequestMapping(value = "/v24", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
    public String av21(@ModelAttribute("form") Form21 formulaire, RedirectAttributes redirectAttributes) throws JsonProcessingException {
        redirectAttributes.addFlashAttribute("form", formulaire);
        formulaire.setStrCouleurs(mapper.writeValueAsString(formulaire.getCouleurs()));
        formulaire.setStrBijoux(mapper.writeValueAsString(formulaire.getBijoux()));
        return "redirect:/v23.html";
}

Beachten Sie, dass die Jackson/JSON-Bibliothek in den Projektabhängigkeiten enthalten ist.

  • Zeile 2: Wir erstellen einen Typ [ObjectMapper], mit dem wir Objekte in JSON serialisieren und aus JSON deserialisieren können.
  • Zeile 7: Wir serialisieren das Farben-Array in JSON. Das Ergebnis wird im Feld [strCouleurs] gespeichert;
  • Zeile 8: Wir serialisieren das Array „jewelry“ in JSON. Das Ergebnis wird im Feld [strBijoux] gespeichert;

Hier ist ein Ausführungsbeispiel:

Beachten Sie, dass das [value]-Attribut der Kontrollkästchen übermittelt wird.

5.17. [/25-/v26]: Listen verwalten

Wir fügen die folgende Aktion hinzu [/v25]:


  // ------------------ form with lists
  @RequestMapping(value = "/v25", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
  public String v25(@ModelAttribute("form") Form21 formulaire, Model model) {
        model.addAttribute("listes", listes);
        return "vue-25";
}

Die Ansicht [vue-25.xml] sieht wie folgt aus:


<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="/css/form19.css" />
    </head>
    <body>
 
        <h3>Formulaire - Listes</h3>
        <form action="/someURL" th:action="@{/v26.html}" method="post"
            th:object="${form}">
            <table>
                <thead>
                    <tr>
                        <th class="col1">Texte</th>
                        <th class="col2">Saisie</th>
                        <th class="col3">Valeur</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="col1">Votre couleur préférée</td>
                        <td class="col2">
                            <select th:field="*{couleur2}">
                                <option value="0">rouge</option>
                                <option value="1">bleu</option>
                                <option value="2">vert</option>
                            </select>
                        </td>
                        <td class="col3">
                            <span th:text="*{couleur2}"></span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">Pierres préférées (choix multiple)</td>
                        <td class="col2">
                            <select th:field="*{bijoux2}" multiple="multiple" size="3">
                                <option th:each="label, status : ${listes.libellesBijoux}"
                                    th:text="${label}" th:value="${status.index}">
                                </option>
                            </select>
                        </td>
                        <td class="col3">
                            <span th:text="*{strBijoux2}"></span>
                        </td>
                    </tr>
 
                </tbody>
            </table>
            <input type="submit" value="Valider" />
        </form>
    </body>
</html>
  • Zeilen 38–42: Erstellen einer Dropdown-Liste, deren Bezeichnungen aus der bereits verwendeten [Lists]-Komponente stammen;

Die angezeigte Seite sieht wie folgt aus:

 

erzeugt durch den folgenden HTML-Code:


<!DOCTYPE HTML>
 
<html>
    <head>
        <title>Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="/css/form19.css" />
    </head>
    <body>
 
        <h3>Formulaire - Listes</h3>
        <form action="/v26.html" method="post">
            <table>
                <thead>
                    <tr>
                        <th class="col1">Texte</th>
                        <th class="col2">Saisie</th>
                        <th class="col3">Valeur</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="col1">Votre couleur préférée</td>
                        <td class="col2">
                            <select id="couleur2" name="couleur2">
                                <option value="0" selected="selected">rouge</option>
                                <option value="1">bleu</option>
                                <option value="2">vert</option>
                            </select>
                        </td>
                        <td class="col3">
                            <span>0</span>
                        </td>
                    </tr>
                    <tr>
                        <td class="col1">Pierres préférées (choix multiple)</td>
                        <td class="col2">
                            <select multiple="multiple" size="3" id="bijoux2" name="bijoux2">
                                <option value="0">émeraude</option>
                                <option value="1">rubis</option>
                                <option value="2">diamant</option>
                                <option value="3">opaline</option>
                            </select>
                            <input type="hidden" name="_bijoux2" value="1" />
                        </td>
                        <td class="col3">
                            <span></span>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p>
                <input type="submit" value="Valider" />
            </p>
        </form>
    </body>
</html>
  • Zeile 44: Beachten Sie, dass Thymeleaf ein verstecktes Feld erstellt hat. Ich verstehe dessen Zweck nicht:
  • Die übermittelten Werte (Wert-Attribute der Option-Tags) werden in den folgenden Feldern (Name-Attribute) von [Form21] gespeichert:

    private int couleur2;
    private int[] bijoux2;
  • Zeile 38: Die Liste [jewelry2] ist eine Mehrfachauswahlliste. Daher können mehrere Werte unter dem Namen [jewelry2] gepostet werden. Um sie abzurufen, muss das Feld [jewelry2] ein Array sein. Beachten Sie, dass es sich um ein Array von Ganzzahlen handelt. Dies ist möglich, da die geposteten Werte in diesen Typ konvertiert werden können;

Die Werte werden an die folgende [/v26]-Aktion gesendet:


  @RequestMapping(value = "/v26", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
  public String v26(@ModelAttribute("form") Form21 formulaire, RedirectAttributes redirectAttributes) throws JsonProcessingException {
    redirectAttributes.addFlashAttribute("form", formulaire);
    formulaire.setStrBijoux2(mapper.writeValueAsString(formulaire.getBijoux2()));
    return "redirect:/v25.html";
}

Hier gibt es nichts, was wir nicht schon kennen. Hier ist ein Ausführungsbeispiel:

5.18. [/v27]: Nachrichten konfigurieren

Betrachten Sie die folgende [/v27]-Aktion:


  // ------------------ set messages
  @RequestMapping(value = "/v27", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
  public String v27(Model model) {
        model.addAttribute("param1","paramètre un");
        model.addAttribute("param2","paramètre deux");
        model.addAttribute("param3","paramètre trois");
        model.addAttribute("param4","messages.param4");        
        return "vue-27";
}

Die Aktion setzt einfach vier Werte im Modell und zeigt die folgende Ansicht [view-27.xml] an:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title th:text="#{messages.titre}">Spring 4 MVC</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <h2 th:text="#{messages.titre}">Spring 4 MVC</h2>
        <p th:text="#{messages.msg1(${param1})}"></p>
        <p th:text="#{messages.msg2(${param2},${param3})}"></p>
        <p th:text="#{messages.msg3(#{${param4}})}"></p>
    </body>
</html>
  • Zeile 8: eine Nachricht ohne Parameter;
  • Zeile 9: eine Nachricht mit einem Parameter [$param1], der aus der Vorlage übernommen wurde;
  • Zeile 10: eine Nachricht mit zwei Parametern [$param2, $param3], die aus der Vorlage übernommen wurden;
  • Zeile 11: eine Nachricht mit einem Parameter. Dieser Parameter ist selbst ein Nachrichtenschlüssel (gekennzeichnet durch #). Der Schlüssel wird durch [$param4] bereitgestellt;

Die französische Meldungsdatei sieht wie folgt aus:

[messages_fr.properties]


messages.titre=Messages paramétrés
messages.msg1=Un message avec un paramètre : {0}
messages.msg2=Un message avec deux paramètres : {0}, {1}
messages.msg3=Un message avec une clé de message comme paramètre : {0}
messages.param4=paramètre quatre

Um das Vorhandensein von Parametern in der Nachricht anzuzeigen, verwenden wir die Symbole {0}, {1}, ...

Durch das Zusammenführen der von der Aktion [/v27] erstellten Vorlage mit der Ansicht [vue-27] entsteht der folgende HTML-Code:


<!DOCTYPE html>
 
<html>
    <head>
        <title>Messages paramétrés</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <h2>Messages paramétrés</h2>
        <p>Un message avec un paramètre : paramètre un</p>
        <p>Un message avec deux paramètre : paramètre deux, paramètre trois</p>
        <p>Un message avec une clé de message comme paramètre : paramètre quatre</p>
    </body>
</html>

was zu folgender Ansicht führt:

 

Die englische Sprachdatei lautet wie folgt:

[messages_fr.properties]


messages.titre=Parameterized messages
messages.msg1=Message with one parameter: {0}
messages.msg2=Message with two parameters: {0}, {1}
messages.msg3=Message with a message key as a parameter: {0}
messages.param4=parameter four

Durch das Zusammenführen der von der Aktion [/v27] erstellten Vorlage mit der Ansicht [vue-27] entsteht der folgende HTML-Code:


<!DOCTYPE html>
 
<html>
    <head>
        <title>Parameterized messages</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <h2>Parameterized messages</h2>
        <p>Message with one parameter: paramètre un</p>
        <p>Message with two parameters: paramètre deux, paramètre trois</p>
        <p>Message with a message key as a parameter: parameter four</p>
    </body>
</html>

was zu folgender Ansicht führt:

 

Wir sehen, dass die letzte Nachricht vollständig internationalisiert wurde, was bei den beiden vorherigen nicht der Fall ist.

5.19. Verwendung einer Master-Seite

In einer Webanwendung teilen sich Ansichten oft eine Reihe von Elementen, die in eine Master-Seite ausgelagert werden können. Hier ein Beispiel:

Oben sehen wir zwei ähnliche Seiten, bei denen Fragment [1] durch Fragment [2] ersetzt wurde. Die Ansicht entspricht einer Master-Seite mit drei festen Fragmenten [3–5] und einem variablen Fragment [6].

5.19.1. Das Projekt

Wir erstellen ein Projekt [springmvc-masterpage] nach dem in Abschnitt 5.1 beschriebenen Ansatz.

  

Die [pom.xml]-Datei sieht wie folgt aus:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>istia.st.springmvc</groupId>
    <artifactId>springmvc-masterpage</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>springmvc-masterpage</name>
    <description>Page maître</description>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>istia.st.springmvc.main.Main</start-class>
        <java.version>1.7</java.version>
    </properties>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>

Eine der in dieser Datei enthaltenen Abhängigkeiten ist für die Master-Seite erforderlich:

 

Die Pakete [config] und [main] sind identisch mit den gleichnamigen Paketen im vorherigen Projekt.

5.19.2. Die Master-Seite

  

Die Master-Seite ist die folgende [layout.xml]-Ansicht:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <title>Layout</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <table style="width: 400px">
            <tr>
                <td colspan="2" bgcolor="#ccccff">
                    <div th:include="entete" />
                </td>
            </tr>
            <tr style="height: 200px">
                <td bgcolor="#ffcccc">
                    <div th:include="menu" />
                </td>
                <td>
                    <section layout:fragment="contenu">
                        <h2>Contenu</h2>
                    </section>
                </td>
            </tr>
            <tr bgcolor="#ffcc66">
                <td colspan="2">
                    <div th:include="basdepage" />
                </td>
            </tr>
        </table>
    </body>
</html>
  • Zeile 2: Die Master-Seite muss den Namespace [xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"] definieren, dessen Element in Zeile 19 verwendet wird;
  • Zeilen 10–12: Erzeugen den Bereich [1] unten. Mit dem Thymeleaf-Tag [th:include] können Sie ein in einer anderen Datei definiertes Fragment in die aktuelle Ansicht einbinden. So können Sie Fragmente in mehreren Ansichten wiederverwenden;
  • Zeilen 15–17: Erzeugt den Bereich [2] unten;
  • Zeilen 19–20: Erzeugt den Bereich [3] unten. Das Attribut [layout:fragment] gehört zum Namespace [xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"]. Es kennzeichnet einen Bereich, der zur Laufzeit durch einen anderen ersetzt werden kann;
  • Zeilen 24–28: Erzeugt den Bereich [4] unten;

5.19.3. Die Fragmente

Die Fragmente [entete.xml], [menu.xml] und [basdepage.xml] lauten wie folgt:

[entete.xml]


<!DOCTYPE html>
<html>
    <h2>entête</h2>
</html>

[menu.xml]


<!DOCTYPE html>
<html>
    <h2>menu</h2>
</html>

[footer.xml]


<!DOCTYPE html>
<html>
    <h2>bas de page</h2>
</html>

Das Fragment [page1.xml] lautet wie folgt:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout">
    <section layout:fragment="contenu">
        <h2>Page 1</h2>
        <form action="/someURL" th:action="@{/page2.html}" method="post">
            <input type="submit" value="Page 2" />
        </form>
    </section>
</html>
  • Zeile 2: Das Attribut [layout:decorator="layout"] gibt an, dass die aktuelle Seite [page1.xml] „dekoriert“ ist, d. h., dass sie zu einer Master-Seite gehört. Dies ist der Wert des Attributs, in diesem Fall die Ansicht [layout.xml];
  • Zeile 3: Hier wird festgelegt, in welches Fragment der Master-Seite [page1.xml] eingefügt wird. Das Attribut [layout:fragment="contenu"] gibt an, dass [page1.xml] in das Fragment mit dem Namen [contenu] eingefügt wird, d. h. in Zone [3] der Master-Seite;
  • Zeilen 5–7: Der Inhalt des Fragments ist ein Formular, das eine POST-Schaltfläche enthält, die auf die Aktion [/page2.html] verweist;

Das Fragment [page2.xml] ist ähnlich:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorator="layout">
    <section layout:fragment="contenu">
        <h2>Page 2</h2>
        <form action="/someURL" th:action="@{/page1.html}" method="post">
            <input type="submit" value="Page 1" />
        </form>
    </section>
</html>

5.19.4. Die Aktionen

 

Der Controller [Layout.java] sieht wie folgt aus:


package istia.st.springmvc.controllers;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
@Controller
public class Layout {
    @RequestMapping(value = "/page1")
    public String page1() {
        return "page1";
    }
 
    @RequestMapping(value = "/page2", method=RequestMethod.POST)
    public String page2() {
        return "page2";
    }
}
  • Zeilen 10–12: Die Aktion [/page1] zeigt einfach die Ansicht [page1.xml] an;
  • Zeilen 15–17: dasselbe gilt für die Aktion [/page2], die die Ansicht [page2.xml] anzeigt;