Skip to content

5. De Thymeleaf-weergaven

Laten we teruggaan naar de architectuur van een Spring-applicatie MVC.

In de twee voorgaande hoofdstukken zijn verschillende aspecten van het blok [1], de acties, beschreven. We gaan nu in op:

  • het [2]-blok van de V-weergaven;
  • het blok [3] van het model M dat door deze weergaven wordt getoond;

Sinds de introductie van Spring MVC werd de technologie voor het genereren van HTML-pagina’s die naar de clientbrowsers werden verzonden, gevormd door die van JSP-pagina’s (Java Server Pages). Sinds enkele jaren kan ook de [Thymeleaf] [http://www.thymeleaf.org/]-technologie worden gebruikt. Deze technologie stellen we nu voor.

5.1. Het STS-project

We maken een nieuw project aan:

  • In [3] geven we aan dat het project de afhankelijkheden [Thymeleaf] nodig heeft. Hierdoor worden naast de afhankelijkheden [Spring MVC] van het vorige project ook die van het framework [Thymeleaf] en [5] toegevoegd;

Laten we dit project nu als volgt uitbreiden:

  

We laten ons inspireren door het vorige project:

  • [istia.st.springmvc.controllers] bevat de controllers;
  • [istia.st.springmvc.models] bevat de modellen voor de acties en weergaven;
  • [istia.st.springmvc.main] is het pakket met de uitvoerbare Spring Boot-klasse;
  • [templates] bevat de Thymeleaf-weergaven;
  • [i18n] bevat de geïnternationaliseerde berichten die door de weergaven worden weergegeven;

De klasse [Application] is als volgt:


package istia.st.springmvc.main;

import org.springframework.boot.SpringApplication;

public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Config.class, args);
    }
}

De klasse [Config] is als volgt:


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

Deze configuratie maakt het voorlopig mogelijk om locales te beheren.

De controller [ViewController] is als volgt:


package istia.st.springmvc.actions;

import org.springframework.stereotype.Controller;

@Controller
public class ViewsController {

}
  • op regel 5 heeft de annotatie [@Controller] de annotatie [@RestController] vervangen, omdat de acties voortaan geen antwoord naar de klant genereren. Ze zullen:
    • een M-model opbouwen
    • een type [String] retourneren, wat de naam zal zijn van de weergave [Thymeleaf] die verantwoordelijk is voor het weergeven van dit model. Het is de combinatie van deze weergave V en dit model M die de stroom HTML zal genereren die naar de klant wordt verzonden;

Het bestand [messages.properties] is voorlopig leeg.

5.2. [/v01]: de basisprincipes van Thymeleaf

We bekijken de volgende actie in [ViewsController]:


    // de basisprincipes van Thymeleaf - 1
    @RequestMapping(value = "/v01", method = RequestMethod.GET)
    public String v01() {
        return "v01";
}
  • regel 3: de actie retourneert een type [String]. Dit wordt de naam van de actie;
  • regel 4: deze weergave is [v01]. Standaard moet deze zich in de map [templates] bevinden en [v01.html] heten;

De weergave [v01.html] is als volgt:


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

Dit is een bestand met de naam HTML. De aanwezigheid van Thymeleaf is te zien:

  • aan de naamruimte [th] op regel 2;
  • aan de attributen [th:text] op regel 4 en 8;

Dit is een geldig HTML-bestand dat kan worden bekeken. We plaatsen het in de map [static] [2] onder de naam [vue-01.html] en roepen het rechtstreeks op via een browser:

Als we de broncode van de pagina in [2] bekijken, zien we dat de attributen [th:text] door de server zijn verzonden en door de browser zijn genegeerd. Wanneer een weergave het resultaat is van een actie, treedt Thymeleaf in werking en interpreteert het de attributen [th] voordat het antwoord naar de client wordt verzonden.

De tag HTML:


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

wordt door Thymeleaf als volgt verwerkt:

  • th:text heeft de syntaxis th:text="uitdrukking", waarbij uitdrukking een uit te rekenen uitdrukking is. Wanneer deze uitdrukking een tekenreeks is, zoals hier, moet deze tussen apostrofs worden geplaatst;
  • de waarde van [expression] vervangt de tekst van de tag HTML, in dit geval de tekst van de tag [title];

Na verwerking is de bovenstaande tag als volgt geworden:


<title>Les vues</title>

Laten we de actie [/v01] aanroepen:

  • in [2] zien we de vervanging die door Thymeleaf is uitgevoerd;

Laten we nu de actie URL [http://localhost:8080/v01.html] uitvoeren:

 

Hoe moeten we dit interpreteren? Is de weergave [templates/v01.html] rechtstreeks geleverd zonder tussenkomst van een actie? Om duidelijkheid te scheppen, maken we de volgende actie [/v02] aan:


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

De weergave [vue-02.html] is een kopie van [v01.html]:

  

Laten we nu de URL en [http://localhost:8080/vue-02.html] opvragen:

 

URL is niet gevonden. Laten we nu URL en [http://localhost:8080/v02.html] opvragen

  • in de consolelogboeken bij [1] zien we dat de actie [/v02] is aangeroepen, en dat deze de weergave [vue-02.html] in [2] heeft weergegeven;

Nu weten we dat URL [http://localhost:8080/v02.html] ook kan verwijzen naar een bestand [/v02.html] in de map [static]. Wat gebeurt er als dit bestand bestaat? Laten we het eens proberen. We maken in de map [static] het volgende bestand [v02.html] aan:

  

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

en vragen vervolgens om URL en [http://localhost:8080/v02.html]:

[1] en [2] laten zien dat de actie [/v02] is aangeroepen. We onthouden dus dat wanneer de aangevraagde URL de vorm [/x.html] heeft, Spring / Thymeleaf:

  • de actie [/x] uitvoert, indien deze bestaat;
  • de pagina [/static/x.html] weergeven als deze bestaat;
  • genereert anders een 404 Not found-uitzondering;

Om verwarring te voorkomen, zullen acties en weergaven voortaan niet meer dezelfde namen hebben.

5.3. [/v03]: internationalisering van weergaven

Dankzij de Spring/Thymeleaf-integratie kan Thymeleaf gebruikmaken van de berichtbestanden van Spring. Laten we eens kijken naar de volgende nieuwe actie [/v03]:


    // internationalisering van weergaven
    @RequestMapping(value = "/v03", method = RequestMethod.GET)
    public String v03() {
        return "vue-03";
}

Deze actie zorgt ervoor dat de volgende weergave [vue-03.html] wordt weergegeven:

  

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

Op regel 4 en 8 is de uitdrukking van het attribuut [th:text] #{title}, waarvan de waarde het sleutelbericht [title] is. We maken de volgende bestanden [messages_fr.properties] en [messages_en.properties] aan:

[messages_fr.properties]


title=Les vues dans Spring MVC

[messages_en.properties]


title=Views in Spring MVC

Laten we de volgende URL, [http://localhost:8080/v03.html?lang=fr] en [http://localhost:8080/v03.html?lang=en] opvragen:

Merk op dat we hier gebruik hebben gemaakt van wat we onlangs hebben geleerd. In plaats van de actie [v03] aan te duiden met [/v03], hebben we deze aangeduid met [/v03.html].

5.4. [/v04]: het M-model van een V-weergave aanmaken

Laten we eens kijken naar de volgende nieuwe actie [/v04]:


    // het M-sjabloon voor een V-weergave maken
    @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";
}
  • regel 4: het sjabloon van de weergave wordt in de parameters van de actie ingevoegd. Standaard is dit initiële sjabloon leeg. We zullen zien dat het mogelijk is om het vooraf in te vullen;
  • regel 4: een sjabloon van het type [Model] is een soort woordenboek met elementen van het type <String, Object>. In regel 4 voegen we een item toe aan dit woordenboek met de sleutel [personne], gekoppeld aan een waarde van het type [Personne];
  • regel 5: we geven het model weer op de console om te zien hoe het eruitziet;
  • regel 6: we laten de weergave [vue-04.html] weergeven;

De klasse [Personne] is dezelfde als die in het vorige hoofdstuk is gebruikt:

  

package istia.st.springmvc.models;

public class Personne {

    // ID
    private Integer id;
    // naam
    private String nom;
    // leeftijd
    private int age;

    // constructors
    public Personne() {

    }

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

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

    @Override
    public String toString() {
        return String.format("[id=%s, nom=%s,  age=%d]", id, nom, age);
    }

    // getters en setters
...
}

De weergave [vue-04.html] ziet er als volgt uit:

  

<!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>
  • op regel 10 wordt een nieuw type Thymeleaf-uitdrukking ${var} geïntroduceerd, waarbij var een sleutel is van het M-model van de weergave. We herinneren ons dat de actie [/v04] in het model een sleutel [personne] heeft geplaatst die gekoppeld is aan een type Personne[id, nom, age];
  • regel 10: geeft de naam weer van de persoon die in het model voorkomt;
  • regel 14: geeft de leeftijd weer;

De berichtbestanden worden aangepast om de sleutels [personne.nom] en [personne.age] van de regels 9 en 13 toe te voegen. Het resultaat is als volgt:

en de aard van het sjabloon M is te vinden in de logbestanden van de console: [2].

Men kan zich afvragen waarom de weergave [vue-04] niet als volgt wordt geschreven:


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

Deze weergave is volkomen toegestaan en levert hetzelfde resultaat op als hierboven. Een van de doelstellingen van Thymeleaf is dat de Thymeleaf-pagina kan worden weergegeven, zelfs als deze niet door Thymeleaf wordt verwerkt. Laten we daarom twee nieuwe statische pagina’s aanmaken:

  

De weergave [vue-04b.html] is een kopie van de weergave [vue-04.html]. Hetzelfde geldt voor de weergave [vue-04a.html], maar daaruit is de statische tekst verwijderd. Als we beide pagina’s bekijken, krijgen we de volgende resultaten:

In het geval van [1] is de structuur van de pagina niet zichtbaar, terwijl deze in het geval van [2] wel duidelijk zichtbaar is. Dit is het voordeel van het opnemen van statische tekst in een Thymeleaf-weergave, ook al wordt deze tijdens de uitvoering vervangen door andere tekst.

Laten we nu eens naar een technisch detail kijken. In de weergave [vue-04.html] formatteren we de code met [ctrl-Maj-F]. We krijgen het volgende resultaat:


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

De tags zijn niet goed uitgelijnd en de code wordt moeilijker te lezen. Als we [vue-04.html] hernoemen naar [vue-04.xml] en de code opnieuw opmaken, worden de tags weer uitgelijnd. Het achtervoegsel [xml] zou dus praktischer zijn. Het is mogelijk om met dit achtervoegsel te werken. Hiervoor moet Thymeleaf worden geconfigureerd. Om te voorkomen dat we ongedaan maken wat we hebben gedaan, dupliceren we het bestudeerde project [springmvc-vues] naar een project [springmvc-vues-xml]

  

We passen het bestand [pom.xml] als volgt aan:


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

De projectnaam wordt op regel 2 en 6 gewijzigd. Daarnaast wijzigen we het achtervoegsel van de weergaven in de map [templates]:

  

Het document [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html] geeft een overzicht van de Spring Boot-configuratie-eigenschappen die in het bestand [application.properties] kunnen worden gebruikt:

  

Dit document geeft de eigenschappen weer die Spring Boot gebruikt bij het uitvoeren van de automatische configuratie en die kunnen worden gewijzigd door een andere configuratie in te voeren in [application.properties]. Voor Thymeleaf zijn de eigenschappen voor automatische configuratie als volgt:


# 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> is toegevoegd
spring.thymeleaf.cache=true # ingesteld op false voor hot refresh

We zouden dus volstaan met het toevoegen van de regel


spring.thymeleaf.suffix=.xml

in [application.properties]. We kiezen echter voor een andere aanpak, namelijk configuratie via programmering. We gaan Thymeleaf configureren in de klasse [Config]:


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

}
  • de regels 16-24 configureren een [TemplateResolver] voor Thymeleaf. Dit object wordt geladen op basis van een door een actie aangeleverde viewnaam, om het bijbehorende bestand te vinden;
  • regels 18 en 19 stellen het voorvoegsel en het achtervoegsel vast die aan de naam van de view moeten worden toegevoegd om het bestand te vinden. Als de naam van de view dus [vue04] is, zal het gezochte bestand [classpath:/templates/vue04.xml] zijn. [classpath:/templates] is een Spring-syntaxis die verwijst naar een map [/templates] die zich in de root van het classpath van het project bevindt;
  • regel 21: om ervoor te zorgen dat het antwoord aan de client de header HTTP bevat:

Content-Type:text/html;charset=UTF-8
  • regel 20: geeft aan dat de weergave voldoet aan de norm HTML5;
  • regel 22: geeft aan dat Thymeleaf-weergaven in de cache kunnen worden opgeslagen;
  • regels 26-31: stelt de view-resolutie-engine van het Spring/Thymeleaf-paar in op de vorige resolutie-engine;

Laten we het uitvoerbare bestand van dit nieuwe project starten en de URL [http://localhost:8080/v04.html?lang=en] opvragen:

 

We merken op dat in de URL de actie [/v04] ook hier weer kon worden vervangen door [v04.html].

5.5. [/v05]: factorisatie van een object in een Thymeleaf-weergave

We maken de volgende actie [/v05] aan:


    // aanmaken van model M van een weergave V - 2
    @RequestMapping(value = "/v05", method = RequestMethod.GET)
    public String v05(Model model) {
        model.addAttribute("personne", new Personne(7, "martin", 17));
        return "vue-05";
}

Deze is identiek aan de actie [/v04]. De weergave [vue-05.xml] ziet er als volgt uit:

  

<!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>
  • regels 8-17: binnen deze regels wordt een Thymeleaf-object gedefinieerd door het attribuut [th:object="${personne}"] (regel 8). Dit object is hier het sleutelobject [personne] dat in het model staat:
  • regel 11: de Thymeleaf-uitdrukking [*{nom}] is gelijk aan [${objet.nom}], waarbij [objet] het huidige Thymeleaf-object is. Dus hier is de uitdrukking [*{nom}] gelijk aan [${personne.nom}];
  • regel 15: idem;

Het resultaat:

 

5.6. [/v06]: testen in een Thymeleaf-weergave

Laten we eens kijken naar de volgende actie [/v06]:


    // aanmaken van het M-model van een V-weergave - 3
    @RequestMapping(value = "/v06", method = RequestMethod.GET)
    public String v06(Model model) {
        model.addAttribute("personne", new Personne(7, "martin", 17));
        return "vue-06";
}

Deze actie is identiek aan de twee voorgaande acties. Ze geeft de volgende weergave [vue-06.xml] weer:


<!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>
  • regel 17: het attribuut [th:if] evalueert een booleaanse uitdrukking. Als deze uitdrukking waar is, wordt de tag weergegeven; anders niet. Dus als hier ${personne.age}>=18, wordt de tekst [#{personne.majeure}] weergegeven, d.w.z. het bericht met sleutel [personne.majeure] in de berichtenbestanden;
  • regel 18: men kan [*{age} < 18] niet schrijven omdat het teken < een gereserveerd teken is. Men moet dus het equivalent HTML [&lt;] gebruiken, ook wel entiteit HTML [http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references] genoemd;

De berichtbestanden zijn gewijzigd:

[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

Het resultaat is als volgt:

5.7. [/v07]: iteratie in een Thymeleaf-weergave

Laten we eens kijken naar de volgende actie [/v07]:


    // aanmaken van het M-model van een V-weergave - 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";
}
  • de actie maakt een lijst met drie personen aan, plaatst deze in het sjabloon dat gekoppeld is aan de sleutel [liste] en laat de weergave [vue-07] weergeven;

De weergave [vue-07.xml] ziet er als volgt uit:


<!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>
  • regel 10: het attribuut [th:each] herhaalt de tag waarin het zich bevindt, in dit geval een <li>-tag. Het heeft hier twee parameters: [element : collection] en [collection], waarbij [collection] een verzameling objecten is, in dit geval een lijst met personen. Thymeleaf doorloopt de verzameling en genereert evenveel <li>-tags als er elementen in de verzameling zitten. Voor elke <li>-tag vertegenwoordigt [element] het element uit de verzameling dat aan de tag is gekoppeld. Voor dit element wordt het attribuut [th:text] geëvalueerd. De uitdrukking hiervan is hier een aaneenschakeling van tekenreeksen, wat het resultaat [id, nom, age] oplevert;
  • regel 8: de sleutel [liste.personnes] wordt toegevoegd aan de berichtbestanden;

Dit is het resultaat:

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

We komen terug op iets wat we al hebben gezien bij het bestuderen van de acties, namelijk de rol van de annotatie [@ModelAttribute]. We voegen de volgende nieuwe actie toe:


    // --------------- Binding en ModelAttribute ----------------------------------

    // als de parameter een object is, wordt deze geïnstantieerd en eventueel gewijzigd door de parameters van de query
    // het wordt automatisch onderdeel van het model van de weergave met de sleutel [key]
    // voor de parameter @ModelAttribute("xx") is de sleutel gelijk aan xx
    // voor de parameter @ModelAttribute zal de sleutel gelijk zijn aan de naam van de parameterklasse, beginnend met een kleine letter
    // als @ModelAttribute ontbreekt, dan verloopt alles alsof het aanwezig is zonder sleutel
    // merk op dat deze automatische aanwezigheid in het model niet plaatsvindt als de parameter geen object is

    @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";
}
  • regel 11: de annotatie [@ModelAttribute("someone")] voegt automatisch het object [Personne p] toe aan het model, gekoppeld aan de sleutel [someone];
  • regel 12: om het model te controleren;
  • regel 13: toont de weergave [vue-08.xml];

De weergave [vue-08.xml] ziet er als volgt uit:


<!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>
  • regel 8: het Thymeleaf-object wordt geïnitialiseerd met het sleutelobject [someone];

Het resultaat is als volgt:

 

en in de console zien we de volgende log:

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

Laten we nu eens kijken naar de volgende actie [/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";
}
  • regel 1: de aanwezigheid van de parameter [Personne p] zorgt ervoor dat de persoon [p] automatisch in het model wordt geplaatst. Aangezien er geen sleutel is opgegeven, wordt de naam van de klasse met een kleine beginletter als sleutel gebruikt. [Personne p] is dus gelijk aan [@ModelAttribute("personne") Personne p];

De weergave [vue.09.xml] ziet er als volgt uit:


<!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>
  • regel 8: de gebruikte sjabloonsleutel is [personne];

Hier is een voorbeeld:

 

en het logboek in de serverconsole:

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

Laten we nu eens kijken naar de volgende nieuwe actie [/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";
}
  • regels 1-4: definiëren een methode die in het model van elke aanvraag een sleutelelement [uneAutrePersonne] aanmaakt dat gekoppeld is aan het object [new Personne(24,"pauline",55)];
  • regels 6-10: de actie [/v10] doet niets anders dan het ontvangen model doorgeven aan de weergave [vue-10.xml]. Let op: de parameter [Model model] hoeft alleen aanwezig te zijn voor de instructie op regel 8. Zonder deze parameter is hij overbodig;

De weergave [vue-10.xml] ziet er als volgt uit:


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

Het resultaat is als volgt:

 

en het consolelogboek ziet er als volgt uit:

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

5.9. [/v11]: @SessionAttributes

We komen terug op iets wat we al hebben gezien bij het bestuderen van de acties, namelijk de rol van de annotatie [@SessionAttributes]. We voegen de volgende nieuwe actie [/v11] toe:


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

We hebben hier iets dat vergelijkbaar is met wat we zojuist hebben bestudeerd. Het verschil zit hem in een annotatie [@SessionAttributes] die op de klasse zelf is geplaatst:


@Controller
@SessionAttributes("jean")
public class ViewsController {
  • regel 2: hier wordt aangegeven dat de sleutel [jean] van het model in de sessie moet worden geplaatst;

Daarom is in regel 7 van de actie de sessie ingevoegd. In regel 8 wordt de waarde van de sessie weergegeven die gekoppeld is aan de sleutel [jean].

De weergave [vue-11.xml] ziet er als volgt uit:


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

Er worden twee personen weergegeven:

  • regels 8-21: de persoon met sleutel [jean] in het model;
  • regels 23-36: de persoon met sleutel [jean] in de sessie;

De resultaten zijn als volgt:

  • in [1], de persoon met sleutel [jean] in het model;
  • naar [2], de persoon met sleutel [jean] in de sessie;

Het consolelogboek ziet er als volgt uit:


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

Hierboven zien we dat de sleutel [jean] niet voorkomt in de sessie die de actie ontvangt. Hieruit kunnen we afleiden dat de sleutel [jean] in de sessie is geplaatst na de uitvoering van de actie en vóór de weergave van de weergave.

Laten we nu eens kijken naar het geval waarin een sleutel zowel door [@ModelAttribute] als door [@SessionAttributes] wordt aangeroepen. We stellen de volgende twee acties samen:


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

    // in het geval dat de sleutel van [@ModelAttribute] ook een sleutel is van [@SessionAttributes]
    // in dit geval wordt de bijbehorende parameter geïnitialiseerd met de waarde van de sessie
    @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";
}

De actie [/v12a] dient uitsluitend om het element ['paul',new Personne(51, "paul", 33)] in de sessie op te nemen. Ze doet verder niets. Het feit dat deze actie is getagd met [@ResponseBody] geeft aan dat deze actie het antwoord aan de klant genereert. Aangezien het type ervan [void] is, wordt er geen antwoord gegenereerd.

De actie [/v12b] accepteert [@ModelAttribute("paul") Personne p] als parameter. Als er verder niets gebeurt, wordt een object [Personne] geïnstantieerd en vervolgens geïnitialiseerd met de parameters van de aanvraag, en dit object heeft niets te maken met het sleutelobject [paul] dat door de actie [/v12a] in de sessie is geplaatst. We gaan de sleutel [paul] toevoegen aan de sessieattributen van de klasse:


@Controller
@SessionAttributes({ "jean", "paul" })
public class ViewsController {
  • regel 2, er zijn nu twee sessieattributen;

Laten we teruggaan naar de parameters van de actie [/v12b]:


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

Het object [Personne p] wordt nu niet geïnstantieerd, maar verwijst naar het sleutelobject [paul] in de sessie. Vervolgens verloopt de procedure op dezelfde manier. Het sleutelobject [paul] komt met name terecht in het model van de weergave die zal worden getoond. Dit is wat we willen zien op regel 11 van de actie [/v12b].

De weergave [vue-12.xml] ziet er als volgt uit:


<!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>
  • regel 8: er wordt verwezen naar de sleutel [paul] uit het weergavemodel;

Dit levert het volgende resultaat op (na het uitvoeren van de actie [/v12a], die de sleutel [paul] in de sessie plaatst):

 

Het consolelogboek ziet er als volgt uit:


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}

De sleutel [paul] is inderdaad in het sjabloon geplaatst met als waarde de waarde die in de sessie aan de sleutel [paul] is gekoppeld.

5.10. [/v13]: een invoerformulier genereren

We gaan nu in op het invoeren van formulieren en de validatie ervan. We bouwen een eerste formulier met de volgende actie [/v13]:


  // genereert een formulier om een persoon in te voeren
  @RequestMapping(value = "/v13", method = RequestMethod.GET)
  public String v13() {
    return "vue-13";
}

die alleen de volgende weergave [vue-13.xml] weergeeft:


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

Als we deze weergave in de map [static] plaatsen onder de naam [vue-13.html] en we URL [http://localhost:8080/vue-13.html] opvragen, krijgen we de volgende pagina te zien:

 
  • Op regel 8 van het formulier vinden we de tag <form> met het attribuut [th:action]. Dit attribuut wordt door Thymeleaf geëvalueerd en de waarde ervan vervangt de huidige waarde van het attribuut [action], dat dus alleen ter versiering dient. Hier zal de waarde van het attribuut [th:action] [/v14.html] zijn;
  • in de regels 17, 23 en 29 zal de waarde van het attribuut [th:value] die van het attribuut [value] vervangen. Hier zal deze waarde de lege tekenreeks zijn;

Wanneer we URL [/v13.html] opvragen, krijgen we het volgende resultaat:

 

Laten we eens kijken naar de door Thymeleaf gegenereerde broncode:


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

Op regels 9, 18, 24 en 30 zien we de evaluatie van de attributen [th:action] en [th:value] door Thymeleaf.

5.11. [/v14]: de via een formulier verzonden waarden verwerken

De actie [/v14] is de actie die de verzonden waarden ontvangt. Deze ziet er als volgt uit:


  // verwerkt de waarden van het formulier
  @RequestMapping(value = "/v14", method = RequestMethod.POST)
  public String v14(Personne p) {
    return "vue-14";
}
  • regel 3: de verzonden waarden worden ingekapseld in een object [Personne p]. We weten dat dit object automatisch deel uitmaakt van het model M van de weergave V die door de actie zal worden weergegeven, gekoppeld aan de sleutel [personne];
  • regel 4: de weergegeven weergave is de weergave [vue-14.xml];

De weergave [vue-14.xml] ziet er als volgt uit:


<!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>
  • regel 9: in het model wordt het object opgehaald dat gekoppeld is aan de sleutel [personne];
  • regels 12, 16 en 20: de kenmerken van dit object worden weergegeven;

Dit levert het volgende resultaat op:

5.12. [/v15-/v16]: validatie van een model

Laten we aan de hand van het vorige voorbeeld eens kijken naar de volgende reeks:

  • in [1] voeren we onjuiste waarden in voor de velden [id] en [age] van het type [int];
  • in [2] geeft het antwoord van de server aan dat er twee fouten zijn opgetreden;

We gaan hetzelfde formulier gebruiken, maar in geval van validatiefouten sturen we een pagina terug waarop deze fouten worden gemeld, zodat de gebruiker ze kan corrigeren.

De actie [/v15] is als volgt:


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

Deze actie ontvangt als parameter een type [SecuredPerson] met de volgende gegevens:

  

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;

    // constructors
    public SecuredPerson() {

    }

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

    // getters en setters
...
}

De velden [id, nom, age] zijn voorzien van validatievoorwaarden. De weergave [vue-15.xml] die door de actie [/v15] wordt getoond, is als volgt:


<!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>
  • regels 10-47: het modelobject van de pagina dat aan de sleutel [securedPerson] is gekoppeld, wordt opgehaald. Na afloop van GET hebben we een object met de instantiatiewaarde [id=0, nom=null, age=0];
  • regel 17: de waarde van het veld [securedPerson.id];
  • regel 20: de uitdrukking [${#fields.hasErrors('id')}] geeft aan of er validatiefouten zijn opgetreden in het veld [securedPerson.id]. Als dat het geval is, geeft het attribuut [th:errors="*{id}"] de bijbehorende foutmelding weer;
  • dit scenario herhaalt zich op regel 29 voor het veld [nom] en op regel 38 voor het veld [age];
  • regel 45: de uitdrukking [${#fields.errors('*')}] verwijst naar alle fouten in de velden van het object [securedPerson]. Al deze fouten worden dus weergegeven in de regels 44-46;
  • regel 16: we zien dat de waarden van het formulier worden verzonden naar de actie [/v16]. Deze is als volgt:

    // -------------------- validatie van een model------------------
    @RequestMapping(value = "/v16", method = RequestMethod.POST)
    public String v16(@Valid SecuredPerson p, BindingResult result) {
        // fouten?
        if (result.hasErrors()) {
            return "vue-15";
        } else {
            return "vue-16";
        }
}
  • regel 3: de aantekening [@Valid SecuredPerson p] dwingt de validatie van de verzonden waarden af;
  • regel 5: controleert of het sjabloon van de actie foutief is of niet;
  • regel 6: als het onjuist is, wordt het formulier [vue-15.xml] teruggestuurd. Aangezien dit formulier de foutmeldingen weergeeft, zullen we deze bekijken;
  • regel 8: als het actiemodel is gevalideerd, wordt de volgende weergave [vue-16.xml] weergegeven:

<!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 volgen enkele voorbeelden van de uitvoering:

5.13. [/v17-/v18]: controle van foutmeldingen

Wanneer de actie [/v15] voor het eerst wordt aangevraagd, krijgt men het volgende resultaat:

 

Misschien wil je liever een leeg formulier dan nullen in de velden [Identifiant, Age]. Om dit te bereiken, passen we het sjabloon van de actie als volgt aan:


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;

    // constructors
    public StringSecuredPerson() {

    }

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

    // getters en setters
...

}
  • regels 12 en 19: de velden [id] en [age] worden gewijzigd in het type [String];
  • regel 11: er wordt aangegeven dat het veld [id] een getal van maximaal vier cijfers moet zijn, zonder decimalen;
  • regel 18: hetzelfde geldt voor het veld [age], dat een geheel getal van maximaal twee cijfers moet zijn;

De actie [/v17] wordt als volgt:


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

De weergave [vue-17.xml] die door de actie [/v17] wordt weergegeven, is als volgt:


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

De wijzigingen vinden plaats op de volgende regels:

  • regel 10: er wordt nu gewerkt met het sleutelsjabloonobject [stringSecuredPerson];
  • regel 20: de foutenlijst van het veld [id] wordt doorlopen. In de syntaxis [th:each="err,status : ${#fields.errors('id')}"] is het de variabele [err] die de lijst doorloopt. De variabele [status] geeft informatie over elke iteratie. Dit is een object [index, count, size, current] waarbij:
    • index: het nummer van het huidige element is,
    • current: de waarde van dit huidige element,
    • count, size: de grootte van de doorlopen lijst;
  • regel 20: er wordt alleen het eerste element van de lijst [th:if="${status.index}==0"] weergegeven;

De actie [/v18], die de POST van de actie [/v17] verwerkt, is als volgt:


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

De berichtbestanden veranderen als volgt:

[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

Laten we eens enkele voorbeelden bekijken:

 

Uit [1] blijkt dat de twee validaties van het veld [age] zijn uitgevoerd:


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

Is er een vaste volgorde voor de foutmeldingen? Voor het veld [age] lijkt het erop dat de validatoren in de volgorde van [Digits, Range] zijn uitgevoerd. Maar als we meerdere verzoeken indienen, zien we dat deze volgorde kan veranderen. We kunnen dus niet vertrouwen op de volgorde van de validatoren. In [2] wordt slechts één van de twee foutmeldingen van het veld [id] weergegeven. In [3] worden alle foutmeldingen weergegeven.

5.14. [/v19-/v20]: gebruik van verschillende validators

Laten we het volgende nieuwe actiemodel eens bekijken:

  

package istia.st.springmvc.models;

import java.util.Date;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;
import org.springframework.format.annotation.DateTimeFormat;

public class Form19 {

    @NotNull
    @AssertFalse
    private Boolean assertFalse;

    @NotNull
    @AssertTrue
    private Boolean assertTrue;
    
    @NotNull
    @Future
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date dateInFuture;
    
    @NotNull
    @Past
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date dateInPast;
    
    @NotNull
    @Max(value = 100)
    private Integer intMax100;
    
    @NotNull
    @Min(value = 10)
    private Integer intMin10;
    
    @NotNull
    @NotEmpty
    private String strNotEmpty;
    
    @NotNull
    @NotBlank
    private String strNotBlank;
    
    @NotNull
    @Size(min = 4, max = 6)
    private String strBetween4and6;
    
    @NotNull
    @Pattern(regexp = "^\\d{2}:\\d{2}:\\d{2}$")
    private String hhmmss;
    
    @NotNull
    @Email
    @NotBlank
    private String email;
    
    @NotNull
    @Length(max = 4, min = 4)
    private String str4;
    
    @Range(min = 10, max = 14)
    @NotNull
    private Integer int1014;
    
    @URL
    @NotBlank
    private String url;

    // getters en setters
...
}

Dit wordt weergegeven door de volgende actie [/v19]:


    // ------------------ een formulier weergeven
    @RequestMapping(value = "/v19", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String v19(Form19 formulaire) {
        return "vue-19";
}
  • regel 3: de actie ontvangt als parameter een object [Form19 formulaire]. Als GET geen parameters ontvangt, wordt dit object geïnitialiseerd met de standaardwaarden van Java;
  • regel 4: de weergave [vue-19.xml] wordt weergegeven. Deze is als volgt:

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

Deze code geeft de volgende weergave weer:

 

De pagina bevat een tabel met drie kolommen:

  • kolom 1: de validator van het invoerveld;
  • kolom 2: het invoerveld;
  • kolom 3: de foutmeldingen bij het invoerveld;

Laten we bijvoorbeeld eens kijken naar de code van de weergave [/v19.html] voor de 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>

We zien hier code terug die we zojuist hebben bestudeerd bij de formulieren van het type [Personne]:

  • regel 2: de eerste kolom: de naam van de geteste validator;
  • regel 4: het Thymeleaf-attribuut [th:field="*{hhmmss}] genereert de attributen HTML, [id="hhmmss"] en [name="hhmmss"]. Het Thymeleaf-attribuut [th:value="*{hhmmss}"] genereert het attribuut HTML [value="valeur de [form19.hhmmss]]";
  • regel 7: als de ingevoerde waarde voor het veld [form19.hhmmss] onjuist is, worden op regel 7 de foutmeldingen weergegeven die bij dit veld horen;

De geboekte waarden worden verwerkt door de volgende actie [/v20]:


    // ----------------- validatie van het formuliermodel
    @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 {
            // omleiding naar [vue-19]
            redirectAttributes.addFlashAttribute("form19", formulaire);
            return "redirect:/v19.html";
        }
}
  • regel 3: de ingevoerde waarden vullen de velden van het object [Form19 formulaire] in als ze geldig zijn;
  • regel 4-6: als de ingevoerde waarden niet geldig zijn, wordt het formulier [vue-19] opnieuw weergegeven met de foutmeldingen;
  • regels 6-10: als de verzonden waarden geldig zijn, wordt het met deze waarden opgebouwde object [Form19 formulaire] ter beschikking gesteld aan de volgende aanvraag, in dit geval die van de omleiding. Vervolgens wordt het vernietigd;
  • regel 9: de klant wordt doorgestuurd naar de actie [/v19.html]. Deze zal het formulier [vue-19] opnieuw weergeven, dat code bevat zoals:

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

Het attribuut [th:object="${form19}"] haalt vervolgens het object op dat gekoppeld is aan het Flash-attribuut [form19] en geeft zo het formulier weer zoals het is ingevuld.

De code van het formulier verdient nog wat uitleg. Laten we de volgende code eens bekijken:


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

Dit genereert de volgende HTML-code:


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

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

vormen de Thymeleaf-attributen op regel 1 en 3, [th:field="*{assertFalse}"], een probleem. Er werd gezegd dat dit attribuut de attributen HTML, [id=assertFalse] en [name=assertFalse] genereert. Het probleem is dat, aangezien dit op regel 1 en 3 wordt gegenereerd, er twee identieke [name]-attributen en twee identieke [id]-attributen ontstaan. Hoewel dit wel mogelijk is met het attribuut [name], is het niet mogelijk met het attribuut [id]. Zoals te zien is in de gegenereerde code HTML, heeft Thymeleaf twee verschillende [id]-attributen gegenereerd: [id=asserFalse1] en [id=assertFalse2]. Dat is een goede zaak. Het probleem is dat we deze ID’s niet kennen en dat we ze wellicht nodig hebben. Dit is het geval voor de tag [label] op regel 2. Het attribuut [for] van een tag HTML [label] moet verwijzen naar een attribuut [id], in dit geval het attribuut dat is gegenereerd voor de tag [input] op regel 1. In de Thymeleaf-documentatie staat dat de uitdrukking [${#ids.prev('assertFalse')}"] het laatste attribuut [id] oplevert dat is gegenereerd voor het veld [assertFalse].

Laten we nu eens kijken naar de code van de keuzelijst in het formulier:


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

Deze code genereert de code HTML voor een keuzelijst:

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

De verzonden waarde krijgt de naam [name="assertTrue"].

De weergave [vue-19.xml] maakt gebruik van een 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>

Regel 4: het gebruikte stylesheet moet in de map [static] van het project worden geplaatst:

  

De inhoud ervan is als volgt:


@CHARSET "UTF-8";

.col1 {
    background: lightblue;
}

.col2 {
    background: Cornsilk;
}

.col3 {
    background: #e2d31d;
}

.error {
    color: red;
}

Laten we nu eens naar de datums kijken:


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

Uit de analyse van het netwerkverkeer in de Chrome-ontwikkelaarstool (Ctrl-Shift-I) blijkt dat de datums worden verzonden in het formaat (jjjj-mm-dd):

 

Dit is de reden waarom de datums in de validator zijn gemarkeerd:


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

waarmee de verwachte indeling voor de verzonden datumwaarden wordt vastgelegd.

Tot slot het bestand met de Franse berichten [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

Laten we enkele uitvoervoorbeelden bekijken:

 
 

Hierboven, tussen [1] en [2], lijkt het alsof er niets is gebeurd. Als we echter naar het netwerkverkeer kijken (Ctrl-Shift-I), zien we dat er toch twee netwerkverkeersuitwisselingen met de server hebben plaatsgevonden:

  • in [1], de oorspronkelijke POST naar [/v20];
  • naar [2], het antwoord op deze actie is een omleiding;
  • in [3], het tweede verzoek, ditmaal naar [/v19];

De actie [/v19] wordt vervolgens uitgevoerd:


    // ------------------ weergave van een formulier
    @RequestMapping(value = "/v19", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String v19(Form19 formulaire) {
        return "vue-19";
}
  • regel 3, de parameter [Form19 formulaire] wordt geïnitialiseerd met het Flash-attribuut van sleutel [form19], dat was aangemaakt door de vorige actie [/v19] en dat een object was van het type [Form19] met als waarden de waarden die bij de actie [/v19] waren doorgegeven;
  • regel 4: de weergave [vue-19.xml] wordt weergegeven met in het model een object van het type [Form19 formulaire] dat is geïnitialiseerd met de verzonden waarden. Daarom ziet de gebruiker het formulier terug zoals hij het heeft verzonden;

Waarom een omleiding? Waarom hebben we niet gewoon gegevens verzonden naar de bovenstaande actie [/v19]? We zouden hetzelfde resultaat hebben gekregen. Op een paar verschillen na:

  • de browser zou in het adresveld [http://localhost:8080/v20.html] hebben weergegeven in plaats van [http://localhost:8080/v19.html] zoals hier het geval is, omdat hij de laatst aangeroepen URL weergeeft;
  • als de gebruiker de pagina vernieuwt (F5), krijgen we helemaal niet hetzelfde resultaat:
    • in het geval van de omleiding is de weergegeven URL in feite [http://localhost:8080/v19.html], verkregen via een GET. De browser zal dit laatste commando opnieuw uitvoeren en krijgt dan een geheel nieuw formulier (het Flash-attribuut wordt slechts één keer gebruikt),
    • in het geval van geen omleiding is de weergegeven URL de [http://localhost:8080/v20.html] die is verkregen met een POST. De browser zal dit laatste commando opnieuw uitvoeren en dus opnieuw een POST genereren met dezelfde verzonden waarden als eerder. Hier heeft dit geen gevolgen, maar het is vaak ongewenst en daarom geeft men over het algemeen de voorkeur aan omleiding;

5.15. [/v21-/v22]: keuzerondjes beheren

Laten we eens kijken naar de volgende Spring-component [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" };

    // getters en setters
  ...

}
  • regel 5: de klasse [Listes] is een Spring-component;
  • regels 8-10: lijsten die worden gebruikt om keuzerondjes, selectievakjes en vervolgkeuzelijsten te vullen;

In de configuratieklasse [Config] staat het volgende:


@Configuration
@ComponentScan({ "istia.st.springmvc.controllers", "istia.st.springmvc.models" })
@EnableAutoConfiguration
public class Config extends WebMvcConfigurerAdapter {
  • regel 2: het pakket [models] waarin de component [Listes] zich bevindt, zal door Spring grondig worden doorzocht;

We maken de volgende nieuwe acties aan:


    // ------------------ formulier met keuzerondjes
    @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";
}
  • regels 2-3: de component [Listes] wordt in de controller geïnjecteerd;
  • regel 6: we beheren een formulier van het type [Form21] dat we hieronder zullen beschrijven. Merk op dat we de sleutel [form] in het view-model hebben gespecificeerd. Ter herinnering: standaard zou dit [form21] zijn geweest;
  • regel 7: we voegen de component [Listes] toe aan het model. De weergave heeft deze nodig;
  • regel 8: we geven de weergave [vue-21.xml] weer. Deze weergave toont het formulier [Form21] en de verzonden waarden worden doorgestuurd naar de actie [/v22] in de regels 12-15;
  • regels 12-15: de actie [/v22] zorgt alleen voor een omleiding naar de actie [/v21], waarbij de verzonden waarden die zij heeft ontvangen in een Flash-attribuut met sleutel [form] worden geplaatst. Het is belangrijk dat deze sleutel dezelfde is als die in regel 6;

Het model [Form21] ziet er als volgt uit:

  

package istia.st.springmvc.models;

public class Form21 {

    // verzonden waarden
    private String marie = "non";
    private String deplacement = "4";
    private String[] couleurs;
    private String strCouleurs;
    private String[] bijoux;
    private String strBijoux;
    private int couleur2;
    private int[] bijoux2;
    private String strBijoux2;

    // getters en setters
    ...
}

De weergave [vue-21.xml] ziet er als volgt uit:


<!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>
  • regels 36-40: let op het gebruik van de component [Listes] die in het model is opgenomen, om de teksten van de selectievakjes te genereren;
  • kolom 3 geeft de ingevoerde waarde weer voor een POST, of de oorspronkelijke waarde van het formulier bij de eerste GET;

Deze code geeft de volgende pagina weer:

 

die overeenkomt met de volgende 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>

We zien dat de verzonden waarden (name-attributen) in de volgende velden van het sjabloon [Form21] worden geplaatst:


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

De lezer wordt uitgenodigd om zelf tests uit te voeren. Let er goed op dat het attribuut [value] van de keuzerondjes wordt verzonden.

5.16. [/v23-/v24]: selectievakjes beheren

We voegen de volgende nieuwe actie toe:


    // ------------------ formulier met selectievakjes
    @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";
}
  • regel 3: we blijven het sjabloon [Form21] gebruiken;

De weergave [vue-23.xml] ziet er als volgt uit:


<!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>
  • regels 37-41: let op het gebruik van de component [Listes] voor het genereren van de labels van de selectievakjes;

Deze code geeft de volgende pagina weer:

 

afkomstig van de volgende 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>

Merk op dat de verzonden waarden (attributen name) worden geplaatst in de volgende velden van [Form21]:


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

Dit zijn tabellen omdat er voor elk veld meerdere selectievakjes zijn met de naam van het veld. Het is dus mogelijk dat er meerdere verzonden waarden binnenkomen met dezelfde naam (attribuut name van het formulier). Er is daarom een tabel nodig om deze op te halen.

Laten we teruggaan naar de Thymeleaf-code in kolom 3 van de pagina:


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

De velden waarnaar in regel 2 en 14 wordt verwezen, zijn de volgende:


    private String strCouleurs;
    private String strBijoux;

Ze worden berekend door de actie [/v24], die de actie POST beheert:


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

Houd er hierbij rekening mee dat de Jackson-bibliotheek / jSON tot de afhankelijkheden van het project behoort.

  • regel 2: er wordt een type [ObjectMapper] aangemaakt waarmee objecten in jSON kunnen worden geserialiseerd en gedeserialiseerd,
  • regel 7: de kleurenarray wordt geserialiseerd naar jSON. Het resultaat wordt in het veld [strCouleurs] geplaatst;
  • regel 8: de sieradenlijst wordt geserialiseerd naar jSON. Het resultaat wordt in het veld [strBijoux] geplaatst;

Hier volgt een uitvoervoorbeeld:

Let wel dat het attribuut [value] van de selectievakjes wordt verzonden.

5.17. [/25-/v26]: lijsten beheren

We voegen de volgende actie toe: [/v25]:


  // ------------------ formulier met lijsten
  @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";
}

De weergave [vue-25.xml] ziet er als volgt uit:


<!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>
  • regels 38-42: genereren van een keuzelijst waarbij de labels worden overgenomen uit de component [Listes] die we al eerder hebben gebruikt;

De weergegeven pagina ziet er als volgt uit:

 

gegenereerd door de volgende 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>
  • regel 44: we zien dat Thymeleaf een verborgen veld heeft aangemaakt. Ik begrijp de functie ervan niet:
  • de verzonden waarden (attributen value van de tags option) worden in de volgende velden (attributen name) van [Form21] geplaatst:

    private int couleur2;
    private int[] bijoux2;
  • regel 38: de lijst [bijoux2] is een meerkeuzelijst. Er kunnen dus meerdere waarden worden verzonden die gekoppeld zijn aan de naam [bijoux2]. Om deze op te halen, moet het veld [bijoux2] een array zijn. Merk op dat het een array van gehele getallen is. Dit is mogelijk omdat de verzonden waarden naar dit type kunnen worden geconverteerd;

De waarden worden bij de volgende actie [/v26] vastgelegd:


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

Dit is niets wat we nog niet eerder hebben gezien. Hier volgt een uitvoervoorbeeld:

5.18. [/v27]: instellingen voor berichten

Laten we eens kijken naar de volgende actie [/v27]:


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

De actie voegt slechts vier waarden toe aan het sjabloon en geeft de volgende weergave [vue-27.xml] weer:


<!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>
  • regel 8: een bericht zonder parameters;
  • regel 9: een bericht met één parameter [$param1] uit het sjabloon;
  • regel 10: een bericht met twee parameters [$param2, $param3] uit het sjabloon;
  • regel 11: een bericht met één parameter. Deze parameter is zelf een berichtcode (met een #). De code wordt geleverd door [$param4];

Het bestand met de Franse berichten ziet er als volgt uit:

[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

Om aan te geven dat er parameters in het bericht aanwezig zijn, worden de symbolen {0}, {1}, ... gebruikt

Door het samenvoegen van het door de actie [/v27] opgebouwde sjabloon met de weergave [vue-27] ontstaat de volgende 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>

wat de volgende weergave oplevert:

 

Het Engelse berichtbestand is als volgt:

[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

Door het samenvoegen van het sjabloon dat is gemaakt door de actie [/v27] met de weergave [vue-27] ontstaat de volgende 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>

wat de volgende weergave oplevert:

 

We zien dat het laatste bericht volledig is geïnternationaliseerd, wat bij de twee voorgaande berichten niet het geval is.

5.19. Gebruik van een masterpagina

In een webapplicatie is het gebruikelijk dat weergaven een aantal elementen delen die in een masterpagina kunnen worden ondergebracht. Hier volgt een voorbeeld:

Hierboven zien we twee vergelijkbare pagina's waarbij het fragment [1] is vervangen door het fragment [2]. De weergave is die van een masterpagina met drie vaste fragmenten [3-5] en één variabel fragment [6].

5.19.1. Het project

We bouwen een project [springmvc-masterpage] volgens de werkwijze uit paragraaf 5.1.

  

Het bestand [pom.xml] ziet er als volgt uit:


<?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/> <!-- bovenliggende entiteit opzoeken in de repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>istia.st.springmvc.main.Main</start-class>
        <java.version>1.7</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Een van de afhankelijkheden die dit bestand met zich meebrengt, is nodig voor de masterpagina:

 

De pakketten [config] en [main] zijn identiek aan die met dezelfde namen uit het vorige project.

5.19.2. De masterpagina

  

De masterpagina is de volgende weergave [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>
  • regel 2: de masterpagina moet de naamruimte [xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"] definiëren, waarvan een element wordt gebruikt op regel 19;
  • regels 10-12: genereren het onderstaande veld [1]. Met de Thymeleaf-tag [th:include] kan een fragment dat in een ander bestand is gedefinieerd, in de huidige weergave worden opgenomen. Hierdoor kunnen fragmenten die in meerdere weergaven worden gebruikt, worden gefactoreerd;
  • regels 15-17: genereren het onderstaande gebied [2];
  • regels 19-20: genereren het onderstaande gebied [3]. Het attribuut [layout:fragment] is een attribuut van de naamruimte [xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"]. Het verwijst naar een zone die tijdens de uitvoering door een andere kan worden vervangen;
  • regels 24-28: genereren het onderstaande veld [4];

5.19.3. De fragmenten

De fragmenten [entete.xml], [menu.xml] en [basdepage.xml] zijn als volgt:

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

Het fragment [page1.xml] luidt als volgt:


<!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>
  • regel 2: het attribuut [layout:decorator="layout"] geeft aan dat de huidige pagina [page1.xml] 'versierd' is, d.w.z. dat deze tot een masterpagina behoort. Dit is de waarde van het attribuut, in dit geval de weergave [layout.xml];
  • regel 3: hier wordt aangegeven in welk fragment van de hoofdpagina [page1.xml] zal worden ingevoegd. Het attribuut [layout:fragment="contenu"] geeft aan dat [page1.xml] zal worden ingevoegd in het fragment met de naam [contenu], d.w.z. het gebied [3] van de masterpagina;
  • regels 5-7: de inhoud van het fragment is een formulier met een knop van POST naar de actie [/page2.html];

Het fragment [page2.xml] is vergelijkbaar:


<!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. De acties

 

De controller [Layout.java] is als volgt:


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";
    }
}
  • regels 10-12: de actie [/page1] zorgt er alleen voor dat de weergave [page1.xml] wordt weergegeven;
  • regels 15-17: hetzelfde geldt voor de actie [/page2], die de weergave [page2.xml] weergeeft;