Skip to content

5. Die Thymeleaf-Ansichten

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

In den beiden vorangegangenen Kapiteln wurden verschiedene Aspekte des Blocks [1], der Aktionen, beschrieben. Wir befassen uns nun mit:

  • den Block [2] der V-Ansichten;
  • den Block [3] des Modells M, das von diesen Ansichten angezeigt wird;

Seit der Einführung von Spring MVC basierte die Technologie zur Generierung der an die Client-Browser gesendeten Seiten HTML auf den Java Server Pages (JSP). Seit einigen Jahren kann auch die Technologie [Thymeleaf] [http://www.thymeleaf.org/] verwendet werden. Diese stellen wir nun vor.

5.1. Das Projekt STS

Wir erstellen ein neues Projekt:

  • In [3] geben wir an, dass das Projekt die Abhängigkeiten von [Thymeleaf] benötigt. Dadurch werden zusätzlich zu den Abhängigkeiten [Spring MVC] aus dem vorherigen Projekt die Abhängigkeiten des Frameworks [Thymeleaf] und [5] 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 Modelle für die Aktionen und Ansichten;
  • [istia.st.springmvc.main] ist das Paket der ausführbaren Spring-Boot-Klasse;
  • [templates] enthält die Thymeleaf-Ansichten;
  • [i18n] enthält die internationalisierten Meldungen, die von den Views angezeigt werden;

Die Klasse [Application] 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 ermöglicht vorerst die Verwaltung der Sprachumgebungen.

Der Controller [ViewController] lautet wie folgt:


package istia.st.springmvc.actions;

import org.springframework.stereotype.Controller;

@Controller
public class ViewsController {

}
  • In Zeile 5 hat die Anmerkung [@Controller] die Anmerkung [@RestController] ersetzt, da die Aktionen nun keine Antwort an den Client generieren. Sie werden:
    • ein M-Modell erstellen
    • einen Typ [String] zurückgeben, der den Namen der Ansicht [Thymeleaf] bildet, die für die Anzeige dieses Modells zuständig ist. Die Kombination aus dieser Ansicht V und diesem Modell M erzeugt den an den Client gesendeten Datenfluss HTML;

Die Datei [messages.properties] ist derzeit leer.

5.2. [/v01]: Die Grundlagen von Thymeleaf

Betrachten wir die nächste Aktion in [ViewsController]:


    // Thymeleaf-Grundlagen – 1
    @RequestMapping(value = "/v01", method = RequestMethod.GET)
    public String v01() {
        return "v01";
}
  • Zeile 3: Die Aktion gibt einen Typ [String] zurück. Dies wird der Name der Aktion sein;
  • 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>

Es handelt sich um eine Datei namens HTML. Die Verwendung von Thymeleaf ist erkennbar:

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

Wir haben hier eine gültige HTML-Datei, die angezeigt werden kann. Wir legen sie im Ordner [static] [2] unter dem Namen [vue-01.html] ab und rufen sie direkt mit einem Browser auf:

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

Das Tag HTML:


<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 wie hier eine Zeichenkette ist, muss diese in Anführungszeichen gesetzt werden;
  • der Wert von [expression] ersetzt den Text des Tags HTML, in diesem Fall den Text des Tags [title];

Nach der Verarbeitung lautet das obige Tag nun:


<title>Les vues</title>

Rufen wir nun die Aktion [/v01] auf:

  • In [2] sieht man die von Thymeleaf durchgeführte Ersetzung;

Rufen wir nun die Aktion URL für [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-Grundlagen – 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 Transaktionen URL und [http://localhost:8080/vue-02.html] auf:

 

URL wurde nicht gefunden. Nun fordern wir URL und [http://localhost:8080/v02.html] an

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

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

  

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

Anschließend fordern wir die Dateien URL und [http://localhost:8080/v02.html] an:

[1] und [2] zeigen, dass die Aktion [/v02] aufgerufen wurde. Man beachte also: Wenn die angeforderte Aktion URL die Form [/x.html] hat, führt Spring/Thymeleaf:

  • die Aktion [/x] aus, sofern diese existiert;
  • die Seite [/static/x.html] aus, sofern sie existiert;
  • löst andernfalls eine 404-Not-Found-Ausnahme aus;

Um Verwechslungen zu vermeiden, werden Aktionen und Ansichten ab sofort unterschiedliche Namen tragen.

5.3. [/v03]: Internationalisierung von Ansichten

Die Spring-/Thymeleaf-Integration ermöglicht es Thymeleaf, die Spring-Meldungsdateien zu verwenden. Betrachten wir die folgende neue Aktion [/v03]:


    // Internationalisierung von Ansichten
    @RequestMapping(value = "/v03", method = RequestMethod.GET)
    public String v03() {
        return "vue-03";
}

Sie lässt die folgende Ansicht [vue-03.html] anzeigen:

  

<!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 des Attributs [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

Lassen Sie uns die URL, [http://localhost:8080/v03.html?lang=fr] und [http://localhost:8080/v03.html?lang=en] abfragen:

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

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

Betrachten wir die folgende neue Aktion [/v04]:


    // Erstellung der M-Vorlage für eine V-Ansicht
    @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: Die Vorlage der Ansicht wird in die Parameter der Aktion eingefügt. Standardmäßig ist diese ursprüngliche Vorlage leer. Wir werden sehen, dass es möglich ist, sie vorab auszufüllen;
  • Zeile 4: Eine Vorlage 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 [personne] hinzu, der einem Wert vom Typ [Personne] zugeordnet ist;
  • Zeile 5: Wir geben das Modell auf der Konsole aus, um zu sehen, wie es aussieht;
  • Zeile 6: Wir lassen die Ansicht [vue-04.html] anzeigen;

Die Klasse [Personne] ist diejenige, die im vorigen Kapitel verwendet wurde:

  

package istia.st.springmvc.models;

public class Personne {

    // Bezeichner
    private Integer id;
    // Name
    private String nom;
    // Alter
    private int age;

    // Konstruktoren
    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);
    }

    // Getter und Setter
...
}

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>
  • In Zeile 10 wird ein neuer Thymeleaf-Ausdruckstyp ${var} eingeführt, wobei var ein Schlüssel des Modells M der Ansicht ist. Wir erinnern uns, dass die Aktion [/v04] einen Schlüssel [personne] in die Vorlage eingefügt hat, der mit dem Typ Personne[id, nom, age] verknüpft ist;
  • Zeile 10: Zeigt den Namen der im Modell vorhandenen Person an;
  • Zeile 14: Zeigt das Alter an;

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

und die Art der Vorlage M findet sich in den Protokollen der Konsole unter [2].

Man könnte sich fragen, warum die Ansicht [vue-04] nicht wie folgt geschrieben wird:


<!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 völlig zulässig und liefert dasselbe Ergebnis wie zuvor. Eines der Ziele von Thymeleaf ist es, dass die Thymeleaf-Seite auch dann angezeigt werden kann, wenn sie nicht durch Thymeleaf verarbeitet wird. Erstellen wir also zwei neue statische Seiten:

  

Die Ansicht [vue-04b.html] ist eine Kopie der Ansicht [vue-04.html]. Das Gleiche gilt für die Ansicht [vue-04a.html], allerdings wurden hier die statischen Texte von der Seite entfernt. Wenn wir die beiden Seiten anzeigen, erhalten wir folgende Ergebnisse:

Im Fall von [1] wird die Seitenstruktur nicht angezeigt, während sie im Fall von [2] deutlich sichtbar ist. Darin liegt der Vorteil, statische Texte in eine Thymeleaf-Ansicht einzufügen, auch wenn sie bei der Ausführung durch andere Texte ersetzt werden.

Betrachten wir nun ein technisches Detail. In der Ansicht [vue-04.html] formatieren wir den Code mit [ctrl-Maj-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 korrekt ausgerichtet. Daher wäre das Suffix „[xml]“ praktischer. Es ist möglich, mit diesem Suffix zu arbeiten. Dazu muss Thymeleaf konfiguriert werden. Um unsere bisherigen Änderungen nicht rückgängig zu machen, duplizieren wir das untersuchte Projekt „[springmvc-vues]“ 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 Dateiendungen 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 enthält die Eigenschaften, die Spring Boot bei der Autokonfiguration verwendet und die durch eine abweichende Konfiguration in der Datei [application.properties] geändert werden können. Für Thymeleaf lauten die Autokonfigurationseigenschaften wie folgt:


# THYMELEAF (ThymeleafAutoConfiguration)
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> wird hinzugefügt
spring.thymeleaf.cache=true # auf „false“ gesetzt für Hot Refresh

Man könnte sich also damit begnügen, die Zeile


spring.thymeleaf.suffix=.xml

in [application.properties] einzufügen. Wir werden jedoch einen anderen Weg einschlagen, nämlich die Konfiguration per Programmierung. Wir werden Thymeleaf in der Klasse [Config] 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;
    }

}
  • Die Zeilen 16–24 konfigurieren ein [TemplateResolver] für Thymeleaf. Dieses Objekt wird anhand eines von einer Aktion gelieferten View-Namens geladen, um die entsprechende Datei zu finden;
  • In den Zeilen 18 und 19 werden das Präfix und das Suffix festgelegt, die dem Namen der Ansicht hinzugefügt werden, um die Datei zu finden. Wenn der Name der Ansicht also [vue04] lautet, lautet die gesuchte Datei [classpath:/templates/vue04.xml]. „[classpath:/templates]“ ist eine Spring-Syntax, die auf einen Ordner „[/templates]“ verweist, der sich im Stammverzeichnis des Classpath des Projekts befindet;
  • Zeile 21: Damit in der Antwort an den Client der Header „HTTP“ enthalten ist:

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

Starten wir die ausführbare Datei dieses neuen Projekts und rufen wir die URL [http://localhost:8080/v04.html?lang=en] auf:

 

Wir stellen fest, dass in der Aktion URL die Aktion [/v04] erneut durch [v04.html] ersetzt werden konnte.

5.5. [/v05]: Faktorisierung eines Objekts in einer Thymeleaf-Ansicht

Wir erstellen die folgende Aktion [/v05]:


    // Erstellung der Vorlage M für eine Ansicht V – 2
    @RequestMapping(value = "/v05", method = RequestMethod.GET)
    public String v05(Model model) {
        model.addAttribute("personne", new Personne(7, "martin", 17));
        return "vue-05";
}

Sie ist identisch mit 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: Innerhalb dieser Zeilen wird ein Thymeleaf-Objekt durch das Attribut [th:object="${personne}"] (Zeile 8) definiert. Dieses Objekt ist hier das Schlüsselobjekt [personne], das sich im Modell befindet:
  • Zeile 11: Der Thymeleaf-Ausdruck [*{nom}] entspricht [${objet.nom}], wobei [objet] das aktuelle Thymeleaf-Objekt ist. Daher ist hier der Ausdruck [*{nom}] gleichbedeutend mit [${personne.nom}];
  • Zeile 15: ebenso;

Das Ergebnis:

 

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

Betrachten wir die folgende Aktion „[/v06]“:


    // Erstellung des M-Modells einer V-Ansicht – 3
    @RequestMapping(value = "/v06", method = RequestMethod.GET)
    public String v06(Model model) {
        model.addAttribute("personne", new Personne(7, "martin", 17));
        return "vue-06";
}

Sie ist identisch mit den beiden vorherigen Aktionen. Sie 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 ${personne.age} >= 18 ist, wird der Text [#{personne.majeure}] angezeigt, d. h. die Meldung mit dem Schlüssel [personne.majeure] aus den Meldungsdateien;
  • Zeile 18: Man kann nicht „[*{age} < 18]“ schreiben, da das Zeichen < ein reserviertes Zeichen ist. Daher muss das Äquivalent HTML [&lt;] verwendet werden, das auch als Entität HTML [http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references] bezeichnet wird;

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 wir die folgende Aktion [/v07]:


    // Erstellung des M-Modells einer V-Ansicht – 4
    @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 in die Vorlage mit dem Schlüssel [liste] ein und lässt die Ansicht [vue-07] anzeigen;

Die Ansicht [vue-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, hier ein <li>-Tag. Es hat hier zwei Parameter [element : collection], wobei [collection] eine Sammlung von Objekten ist, hier eine Liste von Personen. Thymeleaf durchläuft die Sammlung und generiert so viele <li>-Tags, wie Elemente in der Sammlung vorhanden sind. Für jedes <li>-Tag stellt [element] das Element der Sammlung dar, das mit dem Tag verknüpft ist. Für dieses Element wird das Attribut [th:text] ausgewertet. Sein Ausdruck ist hier eine Verkettung von Zeichenketten, um das Ergebnis [id, nom, age] zu erhalten;
  • Zeile 8: Der Schlüssel [liste.personnes] wird in die Meldungsdateien eingefügt;

Hier ist das Ergebnis:

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

Wir kommen noch einmal auf etwas zurück, das wir bei der Betrachtung der Aktionen gesehen haben: die Rolle der Anmerkung [@ModelAttribute]. Wir fügen die folgende neue Aktion hinzu:


    // --------------- Binding und ModelAttribute ----------------------------------

    // Wenn der Parameter ein Objekt ist, wird er instanziiert und gegebenenfalls durch die Parameter der Abfrage geändert
    // Es wird automatisch Teil des Ansichtsmodells mit dem Schlüssel [key]
    // Für den Parameter @ModelAttribute("xx") ist der Schlüssel gleich xx
    // Für den Parameter @ModelAttribute entspricht der Schlüssel dem Namen der Parameterklasse, beginnend mit einem Kleinbuchstaben
    // Wenn @ModelAttribute fehlt, verhält sich das System so, als wäre er ohne Schlüssel vorhanden
    // Es ist zu beachten, dass diese automatische Einfügung in das Modell nicht erfolgt, wenn der Parameter kein Objekt ist

    @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 Anmerkung [@ModelAttribute("someone")] fügt automatisch das Objekt [Personne p] in das Modell ein, 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 [vue-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 erscheint folgender 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 Aktion [/v09]:


    @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 [Personne p] wird die Person [p] automatisch in die Vorlage eingefügt. Da kein Schlüssel angegeben ist, wird als Schlüssel der Name der Klasse mit einem kleinen Anfangsbuchstaben verwendet. Somit entspricht [Personne p] dem Wert [@ModelAttribute("personne") Personne p];

Die Ansicht [vue.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 [personne];

Hier ist ein Ergebnis:

 

und der Eintrag im Server-Protokoll:

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 eine Methode, die im Modell jeder Anfrage ein Schlüsselelement [uneAutrePersonne] erstellt, das dem Objekt [new Personne(24,"pauline",55)] zugeordnet ist;
  • Zeilen 6–10: Die Aktion [/v10] führt nichts weiter aus, als das ihr übergebene Modell an die Ansicht [vue-10.xml] weiterzugeben. Beachten Sie, dass der Parameter [Model model] nur für die Anweisung in Zeile 8 vorhanden sein muss. Ohne ihn ist er überflüssig;

Die Ansicht [vue-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 sieht wie folgt aus:

 

und das Konsolenprotokoll sieht wie folgt aus:

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

5.9. [/v11]: @SessionAttributes

Wir kommen noch einmal auf etwas zurück, das wir bei der Betrachtung der Aktionen gesehen haben: die Rolle der Anmerkung [@SessionAttributes]. Wir fügen die folgende neue Aktion [/v11] hinzu:


    @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 untersucht haben. Der Unterschied besteht in einer Annotation [@SessionAttributes], die auf der Klasse selbst platziert ist:


@Controller
@SessionAttributes("jean")
public class ViewsController {
  • Zeile 2: Es wird angegeben, dass der Schlüssel [jean] des Modells in die Sitzung eingefügt werden soll;

Deshalb wurde in Zeile 7 der Aktion die Sitzung eingefügt. In Zeile 8 wird der Wert der Sitzung angezeigt, die dem Schlüssel [jean] zugeordnet ist.

Die Ansicht [vue-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 ist zu sehen, dass der Schlüssel [jean] nicht in der Sitzung enthalten ist, die die Aktion empfängt. Daraus lässt sich ableiten, dass der Schlüssel [jean] nach der Ausführung der Aktion und vor der Anzeige der Ansicht in die Sitzung aufgenommen wurde.

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


    @RequestMapping(value = "/v12a", method = RequestMethod.GET)
    @ResponseBody
    public void v12a(HttpSession session) {
        session.setAttribute("paul", new Personne(51, "paul", 33));
    }

    // In diesem Fall ist der Schlüssel von [@ModelAttribute] ebenfalls ein Schlüssel von [@SessionAttributes]
    // In diesem Fall wird der entsprechende Parameter mit dem Wert der Sitzung initialisiert
    @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 lediglich dazu, das Element ['paul',new Personne(51, "paul", 33)] in die Sitzung einzufügen. Sie führt keine weiteren Schritte aus. Die Tatsache, dass sie mit [@ResponseBody] getaggt 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") Personne p] als Parameter. Wenn keine weiteren Maßnahmen ergriffen werden, wird ein Objekt vom Typ [Personne] instanziiert und anschließend mit den Parametern der Anfrage initialisiert; dieses Objekt hat nichts mit dem Schlüsselobjekt [paul] zu tun, das durch die Aktion [/v12a] in die Sitzung gesetzt wurde. Wir fügen den Schlüssel [paul] zu den Sitzungsattributen der Klasse hinzu:


@Controller
@SessionAttributes({ "jean", "paul" })
public class ViewsController {
  • In Zeile 2 gibt es nun zwei Sitzungsattribute;

Kehren wir zu den Parametern der Aktion [/v12b] zurück:


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

Nun wird das Objekt [Personne p] nicht instanziiert, sondern verweist auf das Schlüsselobjekt [paul] in der Sitzung. Anschließend bleibt der Ablauf derselbe. Das Schlüsselobjekt [paul] wird insbesondere in der Vorlage der anzuzeigenden Ansicht enthalten sein. 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: Es wird auf den Schlüssel [paul] aus der Ansichtsvorlage verwiesen;

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

 

Das Konsolenprotokoll lautet wie folgt:


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 tatsächlich in das Modell eingefügt, und zwar mit dem Wert, der dem Schlüssel [paul] in der Sitzung zugeordnet ist.

5.10. [/v13]: Erstellen eines Eingabeformulars

Wir befassen uns nun mit der Eingabe in Formularen und deren Validierung. Wir erstellen ein erstes Formular mit der folgenden Aktion [/v13]:


  // erzeugt ein Formular zur Eingabe einer Person
  @RequestMapping(value = "/v13", method = RequestMethod.GET)
  public String v13() {
    return "vue-13";
}

die lediglich 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 [vue-13.html] ablegen und die Seiten URL sowie [http://localhost:8080/vue-13.html] aufrufen, erhalten wir die folgende Seite:

 
  • In Zeile 8 des Formulars findet sich das Tag <form> mit dem Attribut [th:action]. Dieses Attribut wird von Thymeleaf ausgewertet und sein Wert ersetzt den aktuellen Wert des Attributs [action], das somit 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 Wert des Attributs [value]. Hier ist dieser Wert die leere Zeichenkette;

Wenn man URL [/v13.html] abfragt, erhält man folgendes Ergebnis:

 

Sehen 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 sieht man die Auswertung der Attribute [th:action] und [th:value] durch Thymeleaf.

5.11. [/v14]: Verarbeiten der von einem Formular übermittelten Werte

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


  // verarbeitet die Werte des Formulars
  @RequestMapping(value = "/v14", method = RequestMethod.POST)
  public String v14(Personne p) {
    return "vue-14";
}
  • Zeile 3: Die übermittelten Werte werden in ein Objekt [Personne p] gekapselt. Dieses Objekt ist automatisch Teil des Modells M der Ansicht V, die von der Aktion angezeigt wird, und ist mit dem Schlüssel [personne] verknüpft;
  • Zeile 4: Die angezeigte Ansicht ist die Ansicht [vue-14.xml];

Die Ansicht [vue-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: Im Modell wird das Objekt abgerufen, das dem Schlüssel [personne] zugeordnet ist;
  • Zeilen 12, 16 und 20: Die Eigenschaften dieses Objekts werden angezeigt;

Das ergibt folgendes Ergebnis:

5.12. [/v15-/v16]: Validierung einer Vorlage

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

  • In [1] werden fehlerhafte Werte für die Felder [id] und [age] vom Typ [int] eingegeben;
  • In [2] zeigt uns die Antwort des Servers an, dass zwei Fehler aufgetreten sind;

Wir werden dasselbe Formular verwenden, aber im Falle von Validierungsfehlern eine Seite anzeigen, auf der diese Fehler aufgeführt sind, damit der Benutzer sie korrigieren kann.

Die Aktion [/v15] sieht wie folgt aus:


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

Sie erhält als Parameter einen Typ [SecuredPerson] wie folgt:

  

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;

    // Hersteller
    public SecuredPerson() {

    }

    public SecuredPerson(int id, String nom, int age) {
        this.id=id;
        this.nom = nom;
        this.age = age;
    }

    // Getter und Setter
...
}

Die Felder [id, nom, age] wurden mit Validierungsbedingungen versehen. Die von der Aktion [/v15] angezeigte Ansicht [vue-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 Ausführung von GET liegt ein Objekt mit dem Instanzwert [id=0, nom=null, age=0] vor;
  • Zeile 17: Der Wert des Feldes [securedPerson.id];
  • Zeile 20: Der Ausdruck [${#fields.hasErrors('id')}] gibt an, ob beim 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 [nom] und in Zeile 38 für das Feld [age];
  • Zeile 45: Der Ausdruck [${#fields.errors('*')}] bezeichnet die Gesamtheit der Fehler in den Feldern des Objekts [securedPerson]. Somit wird die Gesamtheit dieser Fehler in den Zeilen 44–46 angezeigt;
  • Zeile 16: Es ist ersichtlich, dass die Werte des Formulars an die Aktion [/v16] übermittelt werden. Diese lautet wie folgt:

    // -------------------- Validierung eines Modells -------------------
    @RequestMapping(value = "/v16", method = RequestMethod.POST)
    public String v16(@Valid SecuredPerson p, BindingResult result) {
        // Fehler?
        if (result.hasErrors()) {
            return "vue-15";
        } else {
            return "vue-16";
        }
}
  • Zeile 3: Die Anmerkung [@Valid SecuredPerson p] erzwingt die Validierung der übermittelten Werte;
  • Zeile 5: Prüft, ob die Vorlage der Aktion fehlerhaft ist oder nicht;
  • Zeile 6: Ist es fehlerhaft, wird das Formular [vue-15.xml] zurückgegeben. Da dieses die Fehlermeldungen anzeigt, sehen wir uns diese an;
  • Zeile 8: Wenn das Aktionsmodell validiert ist, wird die folgende Ansicht [vue-16.xml] 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="#{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 Ausführungsbeispiele:

5.13. [/v17-/v18]: Überprüfung der Fehlermeldungen

Beim ersten Aufruf der Aktion [/v15] erhält man folgendes Ergebnis:

 

Möglicherweise möchte man anstelle von Nullen in den Feldern ein leeres Formular erhalten ([Identifiant, Age]). Um dies zu erreichen, passen wir das Modell der Aktion wie folgt an:


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;

    // Konstruktoren
    public StringSecuredPerson() {

    }

    public StringSecuredPerson(String id, String nom, String age) {
        this.id = id;
        this.nom = nom;
        this.age = age;
    }

    // Getter und Setter
...

}
  • Zeilen 12 und 19: Die Felder [id] und [age] werden auf den Typ [String] geändert;
  • 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] lautet nun wie folgt:


    // ---------------------- Anzeige eines Formulars
    @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 erfolgen in den folgenden Zeilen:

  • Zeile 10: Es wird nun mit dem Schlüsselmodellobjekt [stringSecuredPerson] gearbeitet;
  • Zeile 20: Die Fehlerliste des Feldes [id] wird durchlaufen. 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 dieses aktuellen Elements,
    • count, size: die Größe der durchlaufenen Liste;
  • Zeile 20: Es wird nur das erste Element der Liste [th:if="${status.index}==0"] angezeigt;

Die Aktion [/v18], die das POST der Aktion [/v17] verarbeitet, lautet wie folgt:


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

Die Meldungsdateien entwickeln sich wie folgt:

[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

Sehen wir uns einige Beispiele an:

 

Bei [1] ist zu erkennen, dass die beiden Validatoren des Feldes [age] ausgeführt wurden:


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

Gibt es eine bestimmte Reihenfolge der Fehlermeldungen? Für das Feld [age] scheint es, als seien die Validatoren in der Reihenfolge [Digits, Range] ausgeführt worden. Wenn man jedoch mehrere Abfragen durchführt, stellt man fest, dass sich diese Reihenfolge ändern kann. Man kann sich also nicht auf die Reihenfolge der Validatoren verlassen. Bei [2] wird nur eine der beiden Fehlermeldungen des Feldes [id] angezeigt. Bei [3] werden alle Fehlermeldungen angezeigt.

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

Betrachten wir 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;

    // Getter und Setter
...
}

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


    // ------------------ Anzeige eines Formulars
    @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 als Parameter ein Objekt [Form19 formulaire]. Wenn GET keine Parameter erhält, wird dieses Objekt mit den Java-Standardwerten initialisiert;
  • Zeile 4: Die Ansicht [vue-19.xml] wird angezeigt. Diese lautet wie folgt:

<!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 enthält eine Tabelle mit drei Spalten:

  • Spalte 1: die Validierung des Eingabefelds;
  • Spalte 2: das Eingabefeld;
  • Spalte 3: die Fehlermeldungen zum Eingabefeld;

Betrachten wir beispielsweise den Code der Ansicht [/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>

Hier finden wir denselben Code wieder, den wir gerade bei den Formularen vom Typ [Personne] untersucht haben:

  • Zeile 2: 1. Spalte: Name des getesteten Validators;
  • Zeile 4: Das Thymeleaf-Attribut [th:field="*{hhmmss}] generiert die Attribute HTML, [id="hhmmss"] und [name="hhmmss"]. Das Thymeleaf-Attribut [th:value="*{hhmmss}"] generiert das Attribut HTML [value="valeur de [form19.hhmmss]] ;
  • Zeile 7: Wenn der für das Feld [form19.hhmmss] eingegebene Wert fehlerhaft ist, werden in Zeile 7 die zu diesem Feld gehörenden Fehlermeldungen angezeigt;

Die gebuchten Werte werden durch die folgende Aktion [/v20] verarbeitet:


    // ----------------- Validierung des Formularmodells
    @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 {
            // Weiterleitung zu [vue-19]
            redirectAttributes.addFlashAttribute("form19", formulaire);
            return "redirect:/v19.html";
        }
}
  • Zeile 3: Die gebuchten Werte füllen die Felder des Objekts [Form19 formulaire] aus, sofern sie gültig sind;
  • Zeile 4–6: Sind die gebuchten Werte ungültig, wird das Formular [vue-19] mit den Fehlermeldungen erneut angezeigt;
  • Zeilen 6–10: Sind die übermittelten Werte gültig, wird das mit diesen Werten erstellte Objekt [Form19 formulaire] für die nächste Anfrage – in diesem Fall die Weiterleitung – bereitgestellt. Anschließend wird es gelöscht;
  • Zeile 9: Der Client wird an die Aktion [/v19.html] weitergeleitet. Diese zeigt das Formular [vue-19] erneut an, 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 Objekt ab, das dem Flash-Attribut [form19] zugeordnet ist, und zeigt so das Formular so wieder an, wie es eingegeben wurde.

Der Code des Formulars bedarf noch einiger Erläuterungen. Betrachten wir 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 Code HTML:


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

stellen die Thymeleaf-Attribute in den Zeilen 1 und 3 ([th:field="*{assertFalse}"]) ein Problem dar. Es wurde bereits erwähnt, dass dieses Attribut die Attribute HTML, [id=assertFalse] und [name=assertFalse] generiert. Die Schwierigkeit besteht darin, dass dadurch in den Zeilen 1 und 3 zwei identische Attribute „[name]“ und zwei identische Attribute „[id]“ entstehen. Während dies beim Attribut [name] möglich ist, ist es beim Attribut [id] nicht möglich. Wie im generierten Code HTML zu sehen ist, hat Thymeleaf zwei unterschiedliche Attribute [id] generiert: [id=asserFalse1] und [id=assertFalse2]. Das ist gut so. Das Problem ist, dass wir diese Identifikatoren nicht kennen und sie möglicherweise benötigen. Dies ist beim Tag [label] in Zeile 2 der Fall. Das Attribut [for] eines Tags HTML [label] muss auf ein Attribut [id] verweisen, in diesem Fall dasjenige, das für das Tag [input] in Zeile 1 generiert wurde. In der Thymeleaf-Dokumentation wird angegeben, dass der Ausdruck [${#ids.prev('assertFalse')}"] das zuletzt für das Feld [assertFalse] generierte Attribut [id] liefert.

Betrachten wir nun den Code der Dropdown-Liste des Formulars:


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

Dieser Code generiert den Code HTML 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 erhält den Namen [name="assertTrue"].

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:

  

Sein Inhalt lautet wie folgt:


@CHARSET "UTF-8";

.col1 {
    background: lightblue;
}

.col2 {
    background: Cornsilk;
}

.col3 {
    background: #e2d31d;
}

.error {
    color: red;
}

Sehen wir uns nun die Datumsangaben 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 im Chrome-Entwicklertool (Strg-Umschalt-I) zeigt, dass die Daten im Format (JJJJ-MM-TT) gesendet werden:

 

Aus diesem Grund wurden die Datumsangaben im Validator wie folgt markiert:


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

der das erwartete Format für den übermittelten Wert der Datumsangaben festlegt.

Abschließend die Datei mit den französischen Meldungen [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], hat man den Eindruck, dass nichts passiert ist. Wenn man sich jedoch den Netzwerkverkehr ansieht (Strg-Umschalt-I), stellt man fest, dass zwei Netzwerkverbindungen mit dem Server stattgefunden haben:

  • bei [1] die ursprüngliche Anfrage von POST an [/v20];
  • zu [2] – die Antwort auf diese Aktion ist eine Weiterleitung;
  • in [3], die zweite Anfrage, diesmal an [/v19];

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


    // ------------------ Anzeige eines Formulars
    @RequestMapping(value = "/v19", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String v19(Form19 formulaire) {
        return "vue-19";
}
  • Zeile 3, wird der Parameter [Form19 formulaire] mit dem Flash-Attribut des Schlüssels [form19] initialisiert, der durch die vorherige Aktion [/v19] erstellt wurde und ein Objekt vom Typ [Form19] mit folgenden Werten war: die an die Aktion [/v19] übermittelten Werte;
  • Zeile 4: Die Ansicht [vue-19.xml] wird angezeigt, wobei ihr Modell ein Objekt vom Typ [Form19 formulaire] enthält, das mit den übermittelten Werten initialisiert wurde. Deshalb sieht der Benutzer das Formular so wieder, wie er es übermittelt hat;

Warum eine Weiterleitung? Warum haben wir nicht einfach an die oben genannte Aktion [/v19] gesendet? Das Ergebnis wäre dasselbe gewesen. Mit einigen kleinen Unterschieden:

  • Der Browser hätte in sein Adressfeld „[http://localhost:8080/v20.html]“ anstelle von „[http://localhost:8080/v19.html]“ eingetragen, wie es hier der Fall war, da er die zuletzt aufgerufene Aktion „URL“ anzeigt;
  • wenn der Nutzer die Seite aktualisiert (F5), erhält man ein völlig anderes Ergebnis:
    • Im Falle der Weiterleitung wird URL angezeigt, das aus [http://localhost:8080/v19.html] stammt, welches wiederum mit GET abgerufen wurde. Der Browser führt diesen letzten Befehl erneut aus und erhält dann ein brandneues Formular (das Flash-Attribut wird nur einmal verwendet),
    • Im Fall ohne Weiterleitung ist der angezeigte URL der [http://localhost:8080/v20.html], der aus einem POST erhalten wurde. Der Browser führt diesen letzten Befehl erneut aus und erzeugt somit erneut ein „POST“ mit denselben übermittelten Werten wie zuvor. Hier hat das zwar keine Auswirkungen, ist aber oft unerwünscht, weshalb man im Allgemeinen die Weiterleitung bevorzugt;

5.15. [/v21-/v22]: Radiobuttons verwalten

Betrachten wir die folgende Spring-Komponente [Listes]:

  

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

    // Getter und Setter
  ...

}
  • Zeile 5: Die Klasse [Listes] ist eine Spring-Komponente;
  • Zeilen 8–10: Listen, die zur Besetzung 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 [Listes] befindet, wird von Spring ordnungsgemäß erkundet;

Wir erstellen die folgenden neuen Aktionen:


    // ------------------ Formular mit Optionsfeldern
    @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 [Listes] wird in den Controller injiziert;
  • Zeile 6: Wir verwalten ein Formular vom Typ [Form21], das wir im Folgenden beschreiben werden. Beachten Sie, dass wir dessen Schlüssel [form] im View-Modell angegeben haben. Zur Erinnerung: Standardmäßig wäre dies [form21] gewesen;
  • Zeile 7: Wir fügen die Komponente [Listes] in das Modell ein. Die Ansicht wird diese benötigen;
  • Zeile 8: Die Ansicht [vue-21.xml] wird angezeigt. Diese Ansicht zeigt das Formular [Form21] an, und die übermittelten Werte werden an die Aktion [/v22] in den Zeilen 12–15 weitergeleitet;
  • Zeilen 12–15: Die Aktion [/v22] führt lediglich eine Weiterleitung zur Aktion [/v21] durch und übergibt die empfangenen übermittelten Werte in einem Flash-Attribut mit dem Schlüssel [form]. Es ist wichtig, dass dieser Schlüssel mit dem in Zeile 6 verwendeten übereinstimmt;

Das Template [Form21] sieht wie folgt aus:

  

package istia.st.springmvc.models;

public class Form21 {

    // übermittelte Werte
    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;

    // Getter und Setter
    ...
}

Die Ansicht [vue-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 im Modell hinterlegten Komponente [Listes] zur Generierung der Beschriftungen der Kontrollkästchen;
  • In Spalte 3 wird der für ein POST übermittelte Wert oder der Ausgangswert des Formulars beim ursprünglichen GET angezeigt;

Dieser Code zeigt die folgende Seite an:

 

entsprechend dem folgenden Code HTML:


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

Man sieht, dass die übermittelten Werte (Attribut „name“) in den folgenden Feldern des Modells [Form21] hinterlegt sind:


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

Der Leser ist eingeladen, Tests durchzuführen. Es ist zu beachten, dass das Attribut [value] der Optionsfelder übermittelt wird.

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

Wir fügen die folgende neue Aktion hinzu:


    // ------------------ Formular mit Kontrollkästchen
    @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 die Vorlage [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 [Listes] zur Generierung der Beschriftungen der Kontrollkästchen;

Dieser Code zeigt die folgende Seite an:

 

basierend auf dem folgenden Code HTML:


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

Es ist zu beachten, dass die übermittelten Werte (Attribute name) in die folgenden Felder von [Form21] geschrieben werden:


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

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

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 sind die folgenden:


    private String strCouleurs;
    private String strBijoux;

Sie werden durch die Aktion [/v24] berechnet, die die Aktion POST verwaltet:


    // Jackson-Mapper / 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";
}

Dabei ist zu beachten, dass die Bibliothek „jackson / jSON“ zu den Abhängigkeiten des Projekts gehört.

  • Zeile 2: Es wird ein Typ [ObjectMapper] definiert, der die Serialisierung und Deserialisierung von Objekten in jSON ermöglicht,
  • Zeile 7: Das Farbarray wird in jSON serialisiert. Das Ergebnis wird in das Feld [strCouleurs] geschrieben;
  • Zeile 8: Das Schmuck-Array wird in jSON serialisiert. Das Ergebnis wird in das Feld [strBijoux] geschrieben;

Hier ein Ausführungsbeispiel:

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

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

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


  // ------------------ Formular mit Listen
  @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: Erstellung einer Auswahlliste, deren Bezeichnungen aus der Komponente [Listes] stammen, die wir bereits verwendet haben;

Die angezeigte Seite sieht wie folgt aus:

 

erzeugt durch den folgenden Code HTML:


<!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: Man kann feststellen, dass Thymeleaf ein verstecktes Feld erstellt hat. Ich habe dessen Funktion nicht verstanden:
  • Die übermittelten Werte (Attribute value der Tags option) werden in die folgenden Felder (Attribute name) von [Form21] übernommen:

    private int couleur2;
    private int[] bijoux2;
  • Zeile 38: Die Liste [bijoux2] ist eine Mehrfachauswahlliste. Daher können mehrere Werte über den Namen [bijoux2] übermittelt werden. Um diese abzurufen, muss das Feld [bijoux2] ein Array sein. Es ist zu beachten, dass es sich um ein Array von Ganzzahlen handelt. Dies ist möglich, da die übermittelten Werte in diesen Typ konvertiert werden können;

Die Werte werden an die folgende Aktion [/v26] übermittelt:


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

Das ist nichts, was wir nicht schon gesehen hätten. Hier ein Ausführungsbeispiel:

5.18. [/v27]: Konfiguration der Nachrichten

Betrachten wir die folgende Aktion [/v27]:


  // ------------------ parametrisierte Meldungen
  @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 fügt lediglich vier Werte in die Vorlage ein und lässt die folgende Ansicht [vue-27.xml] anzeigen:


<!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 Meldung ohne Parameter;
  • Zeile 9: eine Meldung 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 (durch das Zeichen # gekennzeichnet). Der Schlüssel wird von [$param4] bereitgestellt;

Die Datei mit den französischen Meldungen lautet wie folgt:

[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, werden die Symbole {0}, {1}, … verwendet

Die Zusammenführung der durch die Aktion [/v27] erstellten Vorlage mit der Ansicht [vue-27] ergibt den folgenden Code HTML:


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

Die Zusammenführung der durch die Aktion [/v27] erstellten Vorlage mit der Ansicht [vue-27] ergibt den folgenden Code HTML:


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

 

Man sieht, 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 ist es üblich, dass sich mehrere Ansichten bestimmte Elemente teilen, die in einer Master-Seite zusammengefasst werden können. Hier ein Beispiel:

Oben sehen wir zwei ähnliche Seiten, bei denen das Fragment [1] durch das 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] gemäß der Vorgehensweise in Abschnitt 5.1.

  

Die Datei [pom.xml] 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/> <!-- Übergeordnetes Element aus Repository abrufen -->
    </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 von dieser Datei mitgelieferten Abhängigkeiten wird für die Master-Seite benötigt:

 

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

5.19.2. Die Master-Seite

  

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


<!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 Namensraum [xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"] definieren, dessen Element in Zeile 19 verwendet wird;
  • Zeilen 10–12: Erzeugen das untenstehende Feld [1]. Mit dem Thymeleaf-Tag [th:include] lässt sich ein in einer anderen Datei definiertes Fragment in die aktuelle Ansicht einbinden. Auf diese Weise können Fragmente, die in mehreren Ansichten verwendet werden, als Bausteine wiederverwendet werden;
  • Zeilen 15–17: Erzeugen den unten stehenden Bereich [2];
  • Zeilen 19–20: erzeugen den unten stehenden Bereich [3]. Das Attribut [layout:fragment] ist ein Attribut des Namensraums [xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"]. Es bezeichnet ein Feld, das zur Laufzeit durch ein anderes ersetzt werden kann;
  • Zeilen 24–28: erzeugen das untenstehende Feld [4];

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>

[basdepage.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] „gestaltet“ ist, d. h., dass sie zu einer Masterseite gehört. Dies ist der Wert des Attributs, in diesem Fall die Ansicht [layout.xml];
  • Zeile 3: Hier wird angegeben, in welches Fragment der Master-Seite [page1.xml] eingefügt werden soll. Das Attribut [layout:fragment="contenu"] gibt an, dass [page1.xml] in das Fragment mit dem Namen [contenu] eingefügt wird, d. h. in den Bereich [3] der Master-Seite;
  • Zeilen 5–7: Der Inhalt des Fragments ist ein Formular, das eine Schaltfläche von POST zur Aktion [/page2.html] enthält;

Das Fragment [page2.xml] ist analog:


<!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] sorgt lediglich dafür, dass die Ansicht [page1.xml] angezeigt wird;
  • Zeilen 15–17: Gleiches gilt für die Aktion [/page2], die die Ansicht [page2.xml] anzeigt;