7. Ajax-Umstellung einer Spring-Anwendung MVC
7.1. Die Rolle von AJAX in einer Webanwendung
Bislang wiesen die untersuchten Lernbeispiele folgende Architektur auf:
![]() |
Um von einer Ansicht [Vue1] zu einer Ansicht [Vue2] zu wechseln, führt der Browser folgende Schritte aus:
- eine Anfrage an die Webanwendung senden;
- erhält die Ansicht [Vue2] und zeigt sie anstelle der Ansicht [Vue1] an.
Das ist das klassische Schema:
- Anfrage des Browsers;
- Erstellung einer Ansicht als Antwort an den Client durch den Webserver;
- Anzeige dieser neuen Ansicht durch den Browser.
Seit einigen Jahren gibt es eine weitere Art der Interaktion zwischen dem Browser und dem Webserver: AJAX (Asynchronous JavaScript and XML). Dabei handelt es sich um Interaktionen zwischen der vom Browser angezeigten Ansicht und dem Webserver. Der Browser tut weiterhin das, was er kann, nämlich eine Ansicht anzeigen, wird nun aber durch in der angezeigten Ansicht eingebettetes JavaScript gesteuert. Das Schema sieht wie folgt aus:
![]() |
- In [1] tritt ein Ereignis auf der im Browser angezeigten Seite ein (Klick auf eine Schaltfläche, Änderung eines Textes usw.). Dieses Ereignis wird von in die Seite eingebettetem JavaScript (jS) abgefangen;
- in [2] führt der JavaScript-Code eine Anfrage HTTP durch, wie es der Browser getan hätte. Die Anfrage erfolgt asynchron: Der Benutzer kann weiterhin mit der Seite interagieren, ohne durch das Warten auf die Antwort auf die Anfrage HTTP blockiert zu werden. Die Anfrage durchläuft den üblichen Verarbeitungsprozess. Sie unterscheidet sich in keiner (oder kaum) Weise von einer herkömmlichen Anfrage;
- in [3] wird eine Antwort an den Client jS gesendet. Anstelle einer vollständigen Ansicht HTML wird eher eine Teilansicht HTML, ein Datenstrom XML oder jSON (JavaScript Object Notation) gesendet wird;
- In [4] ruft das JavaScript diese Antwort ab und verwendet sie, um einen Bereich der angezeigten Seite HTML zu aktualisieren.
Für den Benutzer ergibt sich eine Änderung der Ansicht, da sich das, was er sieht, geändert hat. Es findet jedoch kein vollständiges Neuladen der Seite statt, sondern lediglich eine teilweise Änderung der angezeigten Seite. Dies trägt zur Flüssigkeit und Interaktivität der Seite bei: Da kein vollständiges Neuladen der Seite erfolgt, können Ereignisse verarbeitet werden, die zuvor nicht berücksichtigt wurden. Beispielsweise kann dem Benutzer eine Liste mit Optionen angeboten werden, während er Zeichen in ein Eingabefeld eingibt. Bei jedem neu eingegebenen Zeichen wird eine Anfrage AJAX an den Server gesendet, der daraufhin weitere Vorschläge zurücksendet. Ohne Ajax war diese Art der Eingabehilfe früher unmöglich. Man konnte nicht bei jedem eingegebenen Zeichen eine neue Seite neu laden.
7.2. Aktualisierung einer Seite mit einem Feed HTML
7.2.1. Die Ansichten
Wir wollen die folgende Anwendung untersuchen:
![]() |
- in [1], dem Zeitpunkt des Ladens der Seite;
- in [2] werden die vier arithmetischen Operationen mit zwei reellen Zahlen A und B durchgeführt;
- in [3] wird die Antwort des Servers in einen Bereich der Seite geschrieben;
- in [4]: der Zeitpunkt der Berechnung. Dieser unterscheidet sich vom Zeitpunkt des Ladens der Seite [5]. Letztere entspricht [1], was zeigt, dass der Bereich [6] nicht neu geladen wurde. Außerdem haben sich die Werte URL und [7] der Seite nicht geändert.
7.2.2. Die Aktion [/ajax-01]
![]() |
Der Controller [Ajax.java] definiert die folgende Aktion [/ajax-01]:
@RequestMapping(value = "/ajax-01", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax01(Locale locale, Model modèle, HttpSession session, String tempo) {
// Ist das Tempo gültig?
if (tempo != null) {
boolean valide = false;
int valueTempo = 0;
try {
valueTempo = Integer.parseInt(tempo);
valide = valueTempo >= 0;
} catch (NumberFormatException e) {
}
if (valide) {
session.setAttribute("tempo", new Integer(valueTempo));
}
}
// Vorbereitung der Ansichtvorlage [vue-01]
...
}
- Zeile 2: Die Aktion [/ajax-01] akzeptiert nur einen einzigen Parameter [tempo]. Dies ist die Zeit in Millisekunden, die der Server abwarten muss, bevor er die Ergebnisse der arithmetischen Operationen sendet;
- Zeile 4: Der Parameter [tempo] ist optional;
- Zeilen 5–12: Es wird überprüft, ob der Wert des Parameters [tempo] zulässig ist;
- Zeilen 13–15: Ist dies der Fall, wird der Wert der Verzögerungszeit in der Sitzung gespeichert. Das bedeutet, dass er so lange gültig bleibt, bis er geändert wird;
Der Code der Aktion [/ajax-01] lautet wie folgt:
@RequestMapping(value = "/ajax-01", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax01(Locale locale, Model modèle, HttpSession session, String tempo) {
// Gültiges Tempo?
...
// Vorbereitung der Ansichtvorlage [vue-01]
modèle.addAttribute("actionModel01", new ActionModel01());
...
// Ansicht
return "vue-01";
}
Die Klasse [ActionModel01] dient hauptsächlich dazu, die von der Aktion [/ajax-01] übermittelten Werte zu kapseln. Hier werden keine Werte übermittelt. Wir erstellen eine leere Klasse, die wir in das Modell aufnehmen, da die Ansicht [vue-01.xml] diese verwendet. Die Klasse [ActionModel01] sieht wie folgt aus:
package istia.st.springmvc.models;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
public class ActionModel01 {
// übermittelte Daten
@NotNull
@DecimalMin(value = "0.0")
private Double a;
@NotNull
@DecimalMin(value = "0.0")
private Double b;
// Getter und Setter
...
}
- Zeilen 11 und 15: zwei reelle Werte [a,b], die über ein Formular gesendet werden;
Kehren wir zum Code der Aktion zurück:
@RequestMapping(value = "/ajax-01", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax01(Locale locale, Model modèle, HttpSession session, String tempo) {
...
// Das Modell der Ansicht [vue-01] wird vorbereitet
modèle.addAttribute("actionModel01", new ActionModel01());
Resultats résultats = new Resultats();
modèle.addAttribute("resultats", résultats);
...
// Ansicht
return "vue-01";
}
- Zeilen 6–7: Wir fügen eine Instanz vom Typ [Resultats] in die Vorlage ein;
Der in das Modell eingefügte Typ [Resultats] lautet wie folgt:
![]() |
package istia.st.springmvc.models;
public class Resultats {
// Daten
private String aplusb;
private String amoinsb;
private String amultiplieparb;
private String adiviseparb;
private String heureGet;
private String heurePost;
private String erreur;
private String vue;
private String culture;
// Getter und Setter
...
}
- Zeilen 6–9: das Ergebnis der vier arithmetischen Operationen mit den Zahlen [a,b];
- Zeile 10: der Zeitpunkt des ersten Ladens der Seite;
- Zeile 11: der Zeitpunkt der Ausführung der vier arithmetischen Operationen;
- Zeile 12: eine eventuelle Fehlermeldung;
- Zeile 13: die gegebenenfalls anzuzeigende Ansicht;
- Zeile 14: die Kultur der Ansicht, [fr-FR] oder [en-US];
Der Code der Aktion [/ajax-01] setzt sich wie folgt fort:
@RequestMapping(value = "/ajax-01", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax01(ActionModel01 formulaire, Locale locale, Model modèle, HttpSession session) {
...
// Ländereinstellung
setLocale(locale, modèle, résultats);
...
}
- Zeile 5: Die Methode [setLocale] dient dazu, die zu verwendende Kultur – [fr-FR] oder [en-US] – in die Vorlage der Ansicht einzufügen. Diese Kultur ist für das in die Ansicht eingebettete JavaScript bestimmt;
Die Methode [setLocale] lautet wie folgt:
private void setLocale(Locale locale, Model modèle, Resultats résultats) {
// Es werden nur die Locales fr-FR und en-US unterstützt
String language = locale.getLanguage();
String country = null;
switch (language) {
case "fr":
country = "FR";
break;
default:
language = "en";
country = "US";
break;
}
// Kultur
résultats.setCulture(String.format("%s-%s", language, country));
}
In der Vorlage ist die Zeichenfolge [${resultats.culture}] auf „fr-FR“ oder „en-US“ gesetzt.
Kehren wir zur Aktion [/ajax-01] zurück:
@RequestMapping(value = "/ajax-01", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax01(ActionModel01 formulaire, Locale locale, Model modèle, HttpSession session) {
...
// Lokalisierung
setLocale(locale, modèle, résultats);
// Uhrzeit
résultats.setHeureGet(new SimpleDateFormat("hh:mm:ss").format(new Date()));
// Ansicht
return "vue-01";
}
- Zeile 7: Die Uhrzeit von GET wird in die Vorlage übernommen;
- Zeile 9: Die Ansicht [vue-01.xml] wird angezeigt:
7.2.3. Die Ansicht [vue-01.xml]
![]() | ![]() |
Die Ansicht [vue-01.xml] sieht wie folgt aus:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-01</title>
<link rel="stylesheet" href="/css/ajax01.css" />
<script type="text/javascript" src="/js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.validate.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/globalize.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="/js/jquery/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="/js/json3.js"></script>
<script type="text/javascript" src="/js/client-validation.js"></script>
<script type="text/javascript" src="/js/local1.js"></script>
<script th:inline="javascript">
/*<![CDATA[*/
var culture = [[${resultats.culture}]];
Globalize.culture(culture);
/*]]>*/
</script>
</head>
<body>
<h2>Ajax - 01</h2>
<p>
<strong th:text="#{labelHeureGetCulture(${resultats.heureGet},${resultats.culture})}">
Heure de chargement :
</strong>
</p>
<h4>
<p th:text="#{titre.part1}">
Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls
</p>
</h4>
<form id="formulaire" name="formulaire" ... ">
...
</form>
<hr />
<div id="resultats" />
</body>
</html>
- Zeilen 7–12: die Bibliotheken jQuery für die Validierung und Internationalisierung (Kulturen);
- Zeile 15: die in Abschnitt 6.3 erstellte Bibliothek [client-validation];
- Zeile 14: die Bibliothek jSON, die von der Bibliothek [client-validation] verwendet wird. Sie ist optional, wenn die Validierungsprotokolle deaktiviert wurden;
- Zeile 13: die Bibliothek [Unobtrusive Ajax] von Microsoft. Diese Bibliothek ermöglicht es manchmal, auf das Schreiben von JavaScript zu verzichten;
- Zeile 16: eine Datei jS für unsere eigenen Zwecke;
- Zeilen 17–22: Zur clientseitigen Verwaltung der Kulturen [fr-FR] und [en-US]. Wir sind diesem Code bereits begegnet;
- Zeile 27: eine parametrisierte Meldung. Wir haben diese in Abschnitt 5.18 behandelt;
- Zeilen 36–38: das Formular, auf das wir noch zurückkommen werden;
- Zeile 40: das Feld im Dokument, in das das JavaScript die Antwort des Servers einfügt;
7.2.4. Das Formular
![]() |
In der Ansicht [vue-01.xml] sieht das Formular wie folgt aus:
<form id="formulaire" name="formulaire" th:action="@{/ajax-02.html}" method="post" th:object="${actionModel01}" th:attr="data-ajax='true',data-ajax-loading='#loading',data-ajax-loading-duration='0',data-ajax-method='post',data-ajax-mode='replace',data-ajax-update='#resultats', data-ajax-begin='beforeSend',data-ajax-complete='afterComplete' ">
<table>
<thead>
<tr>
<th>
<span th:text="#{valeur.a}"></span>
</th>
<th>
<span th:text="#{valeur.b}"></span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="text" th:field="*{a}" th:value="*{a}" data-val="true"
th:attr="data-val-required=#{NotNull},data-val-number=#{typeMismatch},data-val-min=#{actionModel01.a.min},data-val-min-value=#{actionModel01.a.min.value}" />
</td>
<td>
<input type="text" th:field="*{b}" th:value="*{b}" data-val="true"
th:attr="data-val-required=#{NotNull},data-val-number=#{typeMismatch},data-val-min=#{actionModel01.b.min},data-val-min-value=#{actionModel01.b.min.value}" />
</td>
</tr>
<tr>
<td>
<span class="field-validation-valid" data-valmsg-for="a" data-valmsg-replace="true"></span>
<span th:if="${#fields.hasErrors('a')}" th:errors="*{a}" class="error">Donnée
erronée
</span>
</td>
<td>
<span class="field-validation-valid" data-valmsg-for="b" data-valmsg-replace="true"></span>
<span th:if="${#fields.hasErrors('b')}" th:errors="*{b}" class="error">Donnée
erronée
</span>
</td>
</tr>
</tbody>
</table>
<p>
<input type="submit" th:value="#{action.calculer}" value="Calculer"></input>
<img id="loading" style="display: none" src="/images/loading.gif" />
<a href="javascript:postForm()" th:text="#{action.calculer}">Calculer</a>
</p>
</form>
das das folgende HTML erzeugt:
<form id="formulaire" name="formulaire" method="post" data-ajax-update="#resultats" data-ajax-complete="afterComplete" data-ajax-begin="beforeSend" data-ajax-loading-duration="0" data-ajax-mode="replace" data-ajax="true" data-ajax-method="post" data-ajax-loading="#loading" action="/ajax-02.html">
<table>
<thead>
<tr>
<th>
<span>valeur de A</span>
</th>
<th>
<span>valeur de B</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="text" data-val="true" data-val-min="Le nombre doit être supérieur ou égal à 0" data-val-number="Format invalide" data-val-min-value="0" data-val-required="Le champ est obligatoire" value="" id="a" name="a" />
</td>
<td>
<input type="text" data-val="true" data-val-min="Le nombre doit être supérieur ou égal à 0" data-val-number="Format invalide" data-val-min-value="0" data-val-required="Le champ est obligatoire" value="" id="b" name="b" />
</td>
</tr>
<tr>
<td>
<span class="field-validation-valid" data-valmsg-for="a" data-valmsg-replace="true"></span>
</td>
<td>
<span class="field-validation-valid" data-valmsg-for="b" data-valmsg-replace="true"></span>
</td>
</tr>
</tbody>
</table>
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="/images/loading.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
</form>
- Zeile 16: Dem Feld [a] sind die Validatoren [required], [number] und [min] zugeordnet;
- Zeile 19: dasselbe gilt für das Feld [b];
Die verschiedenen Meldungen finden sich in den Dateien [messages.properties] des Projekts:
![]() |
[messages_fr.properties]
NotNull=Le champ est obligatoire
typeMismatch=Format invalide
actionModel01.a.min=Le nombre doit être supérieur ou égal à 0
DecimalMin.actionModel01.a=Le nombre doit être supérieur ou égal à 0
DecimalMax.actionModel01.b=Le nombre doit être supérieur ou égal à 0
actionModel01.b.min=Le nombre doit être supérieur ou égal à 0
valeur.a=valeur de A
valeur.b=valeur de B
actionModel01.a.min.value=0
actionModel01.b.min.value=0
labelHeureCalcul=Heure de calcul :
LabelErreur=Une erreur s''est produite : [{0}]
labelAplusB=A+B=
labelAmoinsB=A-B=
labelAfoisB=A*B=
labelAdivB=A/B=
titre.part1=Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls
labelHeureGetCulture=Heure de chargement : [{0}], culture : [{1}]
action.calculer=Calculer
erreur.aleatoire=erreur aléatoire
resultats=Résultats
resultats.erreur=Une erreur s''est produite : [{0}]
resultats.titre=Résultats
message.zone=Nombre d'accès :
[messages_en.properties]
NotNull=Required field
typeMismatch=Invalid format
actionModel01.a.min=The number must be greater or equal to 0
DecimalMin.actionModel01.a=The number must be greater or equal to 0
DecimalMax.actionModel01.b=The number must be greater or equal to 0
actionModel01.b.min=The number must be greater or equal to 0
valeur.a=A value
valeur.b=B value
actionModel01.a.min.value=0
actionModel01.b.min.value=0
labelHeureCalcul=Computing hour:
LabelErreur=There was an error: [{0}]
labelAplusB=A+B=
labelAmoinsB=A-B=
labelAfoisB=A*B=
labelAdivB=A/B=
titre.part1=Arithmetic operations on two positive or equal to zero real numbers
labelHeureGetCulture=Loading hour: [{0}], culture: [{1}]
action.calculer=Calculate
erreur.aleatoire=randomly generated error
resultats=Results
resultats.erreur=Some error occurred : [{0}]
resultats.titre=Results
message.zone=Number of hits:
Betrachten wir nun die Attribute des Tags [form]:
<form id="formulaire" name="formulaire" method="post" data-ajax-update="#resultats" data-ajax-complete="afterComplete" data-ajax-begin="beforeSend" data-ajax-loading-duration="0" data-ajax-mode="replace" data-ajax="true" data-ajax-method="post" data-ajax-loading="#loading" action="/ajax-02.html">
Man erkennt die klassischen Attribute des Tags [form]:
<form id="formulaire" name="formulaire" method="post" action="/ajax-02.html">
Es fällt sofort auf, dass das Formular an das URL [/ajax-02.html] gesendet wird, wenn im Browser, der die Seite anzeigt, JavaScript deaktiviert ist. Betrachten wir nun die anderen Attribute:
<form ... data-ajax-update="#resultats" data-ajax-complete="afterComplete" data-ajax-begin="beforeSend" data-ajax-loading-duration="0" data-ajax-mode="replace" data-ajax="true" data-ajax-method="post" data-ajax-loading="#loading">
Die Attribute [data-ajax-xxx] werden von der Bibliothek jS [unobtrusive-ajax] verwaltet, die von der Ansicht [vue-01.xml] importiert wurde:
<script type="text/javascript" src="/js/jquery/jquery.unobtrusive-ajax.js"></script>
Wenn die Attribute [data-ajax-xxx] vorhanden sind, wird das [submit] des Formulars durch einen Ajax-Aufruf der Bibliothek [unobtrusive-ajax] ausgeführt. Die Parameter haben folgende Bedeutung:
- [data-ajax="true"]: Das Vorhandensein dieses Attributs bewirkt, dass das [submit] des Formulars per Ajax aufgerufen wird;
- [data-ajax-method="post"]: Die Methode des [submit]. Das URL des POST-Aufrufs entspricht dem des Attributs [action="/ajax-02.html"];
- [data-ajax-loading="#loading"]: Die ID eines Feldes, das angezeigt werden soll, während auf die Antwort des Servers gewartet wird. Das durch [loading] in der Ansicht [vue-01.xml] identifizierte Feld ist das folgende:
<img id="loading" style="display: none" src="/images/loading.gif" />
Es handelt sich um ein animiertes Ladebild, das so lange angezeigt wird, bis die Antwort vom Server eingegangen ist;
- [data-ajax-loading-duration="0"]: Die Wartezeit in Millisekunden, bevor das Feld [data-ajax-loading="#loading"] angezeigt wird. Hier wird es angezeigt, sobald die Wartezeit beginnt;
- [data-ajax-begin="beforeSend"]: Die Funktion jS, die vor der Ausführung von [submit] ausgeführt werden soll;
- [data-ajax-complete="afterComplete"]: Die Funktion jS, die ausgeführt werden soll, sobald die Antwort empfangen wurde;
- [data-ajax-update="#resultats"]: Die Kennung des Feldes, in dem das vom Server gesendete Ergebnis abgelegt wird. Die Ansicht [vue-01.xml] enthält das folgende Feld:
<div id="resultats" />
- [data-ajax-mode="replace"]: Der Modus für das Einfügen des Ergebnisses in das vorherige Feld. Im Modus [replace] wird das Ergebnis den zuvor im Feld mit der ID [resultats] enthaltenen Wert „überschreiben“;
Es ist zu beachten, dass das JavaScript „[submit]“ nur ausgeführt wird, wenn die Validatoren die getesteten Werte als gültig bestätigt haben.
Die Bibliothek jS [unobtrusive-ajax] verfolgt zwei Ziele:
- sicherzustellen, dass sich das Formular korrekt an beide Möglichkeiten anpasst: ob JavaScript im Browser aktiviert ist oder nicht;
- das Schreiben von JavaScript zu vermeiden. Wir werden sehen, dass sich dies hier nicht vermeiden ließ.
7.2.5. Die Aktion [/ajax-02]
Wir haben gesehen, dass die übermittelten Werte an die Aktion [/ajax-02] gesendet wurden. Diese lautet wie folgt:
@RequestMapping(value = "/ajax-02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String ajax02(ActionModel01 formulaire, Locale locale, Model modèle, HttpSession session) throws InterruptedException {
// Tempo?
Integer tempo = (Integer) session.getAttribute("tempo");
if (tempo != null && tempo > 0) {
Thread.sleep(tempo);
}
// Die Vorlage für die nächste Ansicht wird vorbereitet
Resultats résultats = new Resultats();
modèle.addAttribute("resultats", résultats);
// Die Ortszeit wird festgelegt
setLocale(locale, modèle, résultats);
// Uhrzeit
résultats.setHeurePost(new SimpleDateFormat("hh:mm:ss").format(new Date()));
...
}
- Wir werden zunächst vereinfachen: Wir gehen davon aus, dass die Aktion POST tatsächlich durch das JavaScript der Ansicht [vue-01.xml] ausgelöst wurde. Wir werden später auf diese Annahme zurückkommen;
- Zeile 2: Die übermittelten Werte von [a,b] werden in das Modell [ActionModel01] übernommen;
- Zeilen 4–7: Falls der Benutzer bei einem vorherigen GET eine Verzögerung festgelegt hatte, wird diese aus der Sitzung abgerufen und die Verzögerung durchgeführt (Zeile 6). Der Zweck dieser Verzögerung besteht darin, dem Benutzer zu ermöglichen, die Auswirkung des Attributs [data-ajax-loading="#loading"] im Formular zu sehen;
- Zeilen 9–10: Ein Attribut [resultats] wird in die Vorlage eingefügt;
- Zeile 12: Die Kultur „[fr-FR]“ oder „[en-US]“ wird in die Vorlage eingefügt;
- Zeile 14: Die Uhrzeit von POST wird in die Vorlage eingefügt;
Zur Erinnerung: Der Typ [Resultats], der in die Vorlage eingefügt wurde:
public class Resultats {
// Daten
private String aplusb;
private String amoinsb;
private String amultiplieparb;
private String adiviseparb;
private String heureGet;
private String heurePost;
private String erreur;
private String vue;
private String culture;
// Getter und Setter
...
}
Der Aktionscode [/ajax-02] setzt sich wie folgt fort:
@RequestMapping(value = "/ajax-02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String ajax02(ActionModel01 formulaire, BindingResult result, Locale locale, Model modèle, HttpSession session) throws InterruptedException {
...
résultats.setHeurePost(new SimpleDateFormat("hh:mm:ss").format(new Date()));
// Es wird bei jedem zweiten Versuch ein Fehler generiert
int val = new Random().nextInt(2);
if (val == 0) {
// Es wird eine Fehlermeldung zurückgegeben
résultats.setErreur("erreur.aleatoire");
return "vue-03";
}
...
}
- Zeilen 6–11: Im Beispiel wird gezeigt, wie eine Fehlerseite an den Client jS zurückgegeben wird. In der Hälfte der Fälle wird die folgende Ansicht [vue-03.xml] zurückgegeben:
![]() |
Beachten Sie in Zeile 9, dass es sich nicht um eine Nachricht handelt, die in die Vorlage eingefügt wird, sondern um einen Nachrichtenschlüssel:
[messages_fr.properties]
erreur.aleatoire=erreur aléatoire
[messages_fr.properties]
erreur.aleatoire=randomly generated error
Der Code der Ansicht [vue-03.xml] lautet wie folgt:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h4>Résultats</h4>
<p>
<strong>
<span th:text="#{labelHeureCalcul}">Heure de calcul :</span>
<span id="heureCalcul" th:text="${resultats.heurePost}"></span>
</strong>
</p>
<p style="color: red;">
<span th:text="#{LabelErreur(#{${resultats.erreur}})}">Une erreur s'est produite :</span>
<!-- <span id="Fehler" th:text="${resultats.erreur}"></span> -->
</p>
</body>
</html>
- In Zeile 12 ist eine Meldung zu beachten, die durch einen Meldungsschlüssel parametrisiert ist, der wiederum berechnet wird. Wir haben dieses Konzept in Abschnitt 5.18 auf Seite 171 eingeführt.
Der Code der Aktion [/ajax-02] lautet wie folgt:
@RequestMapping(value = "/ajax-02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String ajax02(ActionModel01 formulaire, BindingResult result, Locale locale, Model modèle, HttpSession session) throws InterruptedException {
...
// die übermittelten Werte werden abgerufen
double a = formulaire.getA();
double b = formulaire.getB();
// Das Modell wird erstellt
résultats.setAplusb(String.valueOf(a + b));
résultats.setAmoinsb(String.valueOf(a - b));
résultats.setAmultiplieparb(String.valueOf(a * b));
try {
résultats.setAdiviseparb(String.valueOf(a / b));
} catch (RuntimeException e) {
résultats.setAdiviseparb("NaN");
}
// die Ansicht wird angezeigt
return "vue-02";
}
- Zeilen 5–15: Die vier arithmetischen Operationen werden auf die Zahlen [a,b] angewendet und in die Instanz [Resultats] des Modells gekapselt;
- Zeile 17: Es wird die folgende Ansicht [vue-02.xml] zurückgegeben:
![]() |
Die Ansicht [vue-02.xml] lautet wie folgt:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h4>Résultats</h4>
<p>
<strong>
<span th:text="#{labelHeureCalcul}">Heure de calcul :</span>
<span id="heureCalcul" th:text="${resultats.heurePost}"></span>
</strong>
</p>
<p>
<span th:text="#{labelAplusB}">A+B=</span>
<span id="aplusb" th:text="${resultats.aplusb}"></span>
</p>
<p>
<span th:text="#{labelAmoinsB}">A-B=</span>
<span id="amoinsb" th:text="${resultats.amoinsb}"></span>
</p>
<p>
<span th:text="#{labelAfoisB}">A*B=</span>
<span id="amultiplieparb" th:text="${resultats.amultiplieparb}"></span>
</p>
<p>
<span th:text="#{labelAdivB}">A/B=</span>
<span id="adiviseparb" th:text="${resultats.adiviseparb}"></span>
</p>
</body>
</html>
Unabhängig davon, ob das Ergebnis die Ansicht [vue-02.xml] oder die Ansicht [vue-03.xml] ist, wird dieses Ergebnis HTML in das Feld eingefügt, das in der Ansicht [vue-01.xml] durch [resultats] gekennzeichnet ist; dies geschieht aufgrund des Attributs [data-ajax-update="#resultats"] des Formulars.
7.2.6. Das POST der eingegebenen Werte
Hier gibt es eine Schwierigkeit mit den übermittelten Werten. Wir arbeiten mit zwei Kulturen, [fr-FR] und [en-US], die reelle Zahlen unterschiedlich darstellen. Wir hatten uns bereits mit dieser Schwierigkeit befasst, als es in Abschnitt 6.3, Seite 191, darum ging, reelle Zahlen in zwei verschiedenen Kulturen zu übermitteln. Wir werden hier die damals verwendeten Werkzeuge wieder aufgreifen. Es gibt jedoch eine zusätzliche Schwierigkeit: Wir haben keinen Zugriff auf die Methode, die die Umwandlung der eingegebenen Werte in das Format POST vornimmt. Aus diesem Grund haben wir dem Formular-Tag die folgenden Attribute hinzugefügt:
- [data-ajax-begin="beforeSend"]: Die Funktion jS, die vor der Ausführung von [submit] ausgeführt werden soll;
- [data-ajax-complete="afterComplete"]: die Funktion jS, die ausgeführt werden soll, sobald die Antwort eingegangen ist;
Wir haben keinen Zugriff auf die Funktion jS, die die eingegebenen Werte übermittelt, aber wir können zwei Funktionen jS schreiben:
- [beforeSend]: eine Funktion jS, die vor der Funktion POST ausgeführt wird;
- [afterComplete]: eine Funktion jS, die nach Erhalt der Antwort auf POST ausgeführt wird;
Diese beiden Funktionen sind in einer Datei namens [local1.js] enthalten:
![]() |
Die Datei [local1.js] initialisiert die Umgebung jS der Ansicht [vue-01.xml] wie folgt:
// globale Daten
var loading;
var formulaire;
var résultats;
var a, b;
// beim Laden des Dokuments
$(document).ready(function() {
// Die Referenzen der verschiedenen Komponenten der Seite werden abgerufen
loading = $("#loading");
formulaire = $("#formulaire");
resultats = $('#Ergebnisse');
a = $("#a");
b = $("#b");
// bestimmte Elemente werden ausgeblendet
loading.hide();
// Die Validatoren des Formulars werden geparst
$.validator.unobtrusive.parse(formulaire);
// Es werden zwei Lokalisierungen verwaltet: [fr_FR, en_US]
// Die tatsächlichen Daten [a,b] werden vom Server im angelsächsischen Format gesendet
// sie werden bei Bedarf in das französische Format umgewandelt
checkCulture(2);
});
- Zeile 22: Die Funktion [checkCulture] wird etwas weiter unten vorgestellt;
Die Funktion jS [beforeSend] sieht wie folgt aus:
function beforeSend(jqXHR, settings) {
// vor dem POST
// müssen die Zahlen im angelsächsischen Format übermittelt werden
var culture = Globalize.culture().name;
if (culture === 'fr-FR') {
checkCulture(1);
settings.data = formulaire.serialize();
}
}
function afterComplete(jqXHR, settings) {
...
}
function checkCulture(mode) {
if (mode == 1) {
// Die Zahlen [a,b] werden ins angelsächsische Format umgewandelt
var value1 = a.val().replace(",", ".");
a.val(value1);
var value2 = b.val().replace(",", ".");
b.val(value2);
}
if (mode == 2) {
...
}
}
- Zeilen 4–6: Es wird geprüft, ob die Ansichtskultur [fr-FR] ist. In diesem Fall müssen die übermittelten Werte geändert werden. Hat der Benutzer nämlich [1,6] eingegeben, muss der Wert [1.6] übermittelt werden, andernfalls wird der Wert [1,6] serverseitig abgelehnt. Dazu reicht es aus, das Komma in den übermittelten Werten durch einen Dezimalpunkt zu ersetzen (Zeilen 18–21);
- damit ist es jedoch noch nicht getan. Denn wenn die Funktion [beforeSend] aufgerufen wird, ist die Zeichenkette der gesendeten Werte [a=val1&b=valB] bereits aufgebaut. Wir müssen sie daher ändern. Dies geschieht mithilfe des zweiten Parameters [settings] der Funktion;
- Zeile 7: [settings.data] (settings ist ein Parameter der Funktion) stellt die übermittelte Zeichenkette dar. Diese Zeichenfolge wird mit dem Ausdruck [formulaire.serialize()] neu erstellt. Dieser Ausdruck durchsucht das Formular nach den zu übermittelnden Werten und bildet die Zeichenfolge von POST. Anschließend übernimmt er die neuen Werte von [a,b] mit Dezimalpunkten;
Wenn man nichts weiter unternimmt, sendet der Server seine Antwort, die korrekt angezeigt wird. Nur dass nun die Werte von [a,b] mit dem Dezimalpunkt versehen sind, während man sich immer noch in der Kultur [fr-FR] befindet. Wenn der Benutzer dies also nicht bemerkt und erneut auf [Calculer] klickt, melden die Validatoren, dass die Werte [a,b] ungültig sind. Was richtig ist. An dieser Stelle kommt die Funktion [afterComplete] ins Spiel, die beim Empfang des Ergebnisses ausgeführt wird:
function beforeSend(jqXHR, settings) {
// vor dem POST
...
}
function afterComplete(jqXHR, settings) {
// nach dem POST
// Die Zahlen müssen bei Bedarf wieder ins französische Format umgewandelt werden
var culture = Globalize.culture().name;
if (culture === 'fr-FR') {
checkCulture(2);
}
}
function checkCulture(mode) {
if (mode == 1) {
...
}
if (mode == 2) {
// Die Zahlen werden in das französische Format umgewandelt
var value1 = a.val().replace(".", ",");
a.val(value1);
var value2 = b.val().replace(".", ",");
b.val(value2);
}
}
- Zeilen 9–12: Wenn die Darstellungsart [fr-FR] ist, werden die Zahlen [a,b] in das französische Format umgewandelt.
7.2.7. Tests
Hier sind einige Screenshots von Tests:
![]() |
- in [1], die Antwort des Servers;
![]() |
- in [2] die Antwort des Servers mit einer Fehlermeldung;
![]() |
- in [3] wird eine Verzögerung von 5 Sekunden festgelegt. Das bedeutet, dass der Server 5 Sekunden wartet, bevor er seine Antwort sendet. Im Tag [form] haben wir das Attribut [data-ajax-loading='#loading'] verwendet. Der Parameter [loading] ist die Kennung eines Bereichs, der:
- während der gesamten Wartezeit angezeigt wird;
- nach dem Empfang der Antwort vom Server ausgeblendet wird;
Hier ist [loading] die Kennung eines animierten Bildes, das unter [4] zu sehen ist.
7.2.8. Deaktivierung von JavaScript mit dem Code [en-US]
Was passiert, wenn man JavaScript im Browser deaktiviert?
Die POST der eingegebenen Werte erfolgt gemäß dem Tag [form], dessen Attribute [data-ajax-attr] nicht verwendet werden. Es verhält sich so, als hätte man das folgende Tag [form]:
<form id="formulaire" name="formulaire" method="post" action="/ajax-02.html">
Die eingegebenen Werte werden daher an die Aktion [/ajax-02] übermittelt. Sie wurden auf der Client-Seite nicht überprüft. Es sind also die serverseitigen Validatoren, die zum Einsatz kommen. Diese waren bereits zuvor aktiv, jedoch bei Werten, die auf der Client-Seite bereits validiert und somit korrekt waren. Dies ist nun nicht mehr der Fall.
Wir ändern die Aktion [/ajax-02] wie folgt:
@RequestMapping(value = "/ajax-02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String ajax02(@Valid ActionModel01 formulaire, BindingResult result, Locale locale, Model modèle, HttpSession session, HttpServletRequest request) throws InterruptedException {
// Ajax-Anfrage?
boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
...
}
- Zeile 4: Die Aktion [/ajax-02] kann nun also über ein Ajax-POST oder über ein klassisches POST aufgerufen werden. Wir müssen diese beiden Fälle unterscheiden können. Dies geschieht anhand der vom Client-Browser gesendeten HTTP-Header;
Wenn man den Netzwerkverkehr in der Chrome-Entwicklerkonsole (Strg-Umschalt-I) bei aktiviertem JavaScript betrachtet, sieht man, dass der Client zum Zeitpunkt des POST die folgenden Header sendet:
![]() |
Aus der obigen Darstellung geht hervor, dass:
- ein Header „[X-Requested-With]“ gesendet wurde;
- ein Parameter [X-Requested-With] wurde zu den übermittelten Werten [2] hinzugefügt;
Bei einem klassischen POST ist dies nicht der Fall. Es gibt also zwei Möglichkeiten, die Informationen abzurufen: entweder aus den Kopfzeilen HTTP oder aus den übermittelten Werten. In Zeile 4 der Aktion [/ajax-02] wurde die erste Lösung gewählt.
Fahren wir mit dem Code dieser Aktion fort:
@RequestMapping(value = "/ajax-02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String ajax02(@Valid ActionModel01 formulaire, BindingResult result, Locale locale, Model modèle, HttpSession session, HttpServletRequest request) throws InterruptedException {
// Ajax-Anfrage?
boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
// Tempo?
Integer tempo = (Integer) session.getAttribute("tempo");
if (tempo != null && tempo > 0) {
Thread.sleep(tempo);
}
// Die Vorlage für die nächste Ansicht wird vorbereitet
Resultats résultats = new Resultats();
modèle.addAttribute("resultats", résultats);
// Die Ländereinstellung wird festgelegt
setLocale(locale, modèle, résultats);
// Uhrzeit
String heure = new SimpleDateFormat("hh:mm:ss").format(new Date());
résultats.setHeurePost(heure);
résultats.setHeureGet(heure);
// Gültige Anfrage?
if (!isAjax && result.hasErrors()) {
return "vue-01";
}
...
- Zeile 2: Der Parameter [@Valid ActionModel01 formulaire] löst die serverseitigen Validatoren aus;
- Zeilen 20–22: Wenn es sich nicht um einen Ajax-Aufruf handelt und die Validierung fehlgeschlagen ist, wird die Ansicht [vue-01.xml] mit den Fehlermeldungen zurückgegeben.
Hier ein Beispiel:
![]() | ![]() |
Setzen wir die Untersuchung der Aktion [/ajax-02] fort:
@RequestMapping(value = "/ajax-02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String ajax02(@Valid ActionModel01 formulaire, BindingResult result, Locale locale, Model modèle, HttpSession session, HttpServletRequest request) throws InterruptedException {
// Ajax-Anfrage?
boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
...
// Gültige Anfrage?
if (!isAjax && result.hasErrors()) {
return "vue-01";
}
// Es wird in jedem zweiten Fall ein Fehler generiert
int val = new Random().nextInt(2);
if (val == 0) {
// Es wird eine Fehlermeldung zurückgegeben
résultats.setErreur("erreur.aleatoire");
if (isAjax) {
return "vue-03";
} else {
résultats.setVue("vue-03");
return "vue-01";
}
}
...
- Zeile 14: Es wird ein zufälliger Fehler generiert;
- Zeile 16: Im Falle eines Ajax-Aufrufs wird die Ansicht [vue-03.xml] zurückgegeben, die in den durch [resultats] identifizierten Bereich eingefügt wird;
- Zeile 18: Bei einem Nicht-Ajax-Aufruf wird die anzuzeigende Ansicht in das Modell vom Typ [Resultats] eingefügt;
- Zeile 19: Die Ansicht [vue-01.xml] wird erneut zurückgegeben;
Die Ansicht [vue-01.xml] wird wie folgt geändert:
<div id="resultats" />
<div th:if="${resultats.vue}=='vue-02'" th:include="vue-02" />
<div th:if="${resultats.vue}=='vue-03'" th:include="vue-03" />
- Zeile 3: Die Ansicht [vue-03.xml] wird unterhalb des Bereichs [resultats] eingefügt;
Hier ein Beispiel:
![]() |
Es ist zu beachten, dass die Zeiten [1] und [2] nun identisch sind.
Setzen wir die Untersuchung der Aktion [/ajax-02] fort:
@RequestMapping(value = "/ajax-02", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String ajax02(@Valid ActionModel01 formulaire, BindingResult result, Locale locale, Model modèle, HttpSession session, HttpServletRequest request) throws InterruptedException {
// Ajax-Anfrage?
boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
...
// Die übermittelten Werte werden abgerufen
double a = formulaire.getA();
double b = formulaire.getB();
// Das Modell wird erstellt
résultats.setAplusb(String.valueOf(a + b));
résultats.setAmoinsb(String.valueOf(a - b));
résultats.setAmultiplieparb(String.valueOf(a * b));
try {
résultats.setAdiviseparb(String.valueOf(a / b));
} catch (RuntimeException e) {
résultats.setAdiviseparb("NaN");
}
// Die Ansicht wird angezeigt
if (isAjax) {
return "vue-02";
} else {
résultats.setVue("vue-02");
return "vue-01";
}
}
- Zeilen 7–17: Die Ergebnisse der vier arithmetischen Operationen werden in das Modell übernommen;
- Zeilen 22–23: Die Ansicht [vue-01.xml] (Zeile 22) wird durch Einfügen der Ansicht [vue-02.xml] (Zeile 22) erstellt;
Dieses Einfügen erfolgt in [vue-01.xml] wie folgt:
<div id="resultats" />
<div th:if="${resultats.vue}=='vue-02'" th:include="vue-02" />
<div th:if="${resultats.vue}=='vue-03'" th:include="vue-03" />
- Zeile 2: Die Ansicht [vue-02.xml] wird unterhalb des Bereichs [resultats] eingefügt;
Hier ein Ausführungsbeispiel:
![]() |
7.2.9. Deaktivierung von JavaScript mit der Kultur [fr-FR]
Bei der Kultur [fr-FR] tritt folgendes Problem auf:
![]() | ![]() |
Die im französischen Format eingegebenen Werte wurden als ungültig gemeldet. Der Server erwartet nämlich reelle Zahlen im angelsächsischen Format. Die Lösung ist recht komplex. Wir werden einen Filter erstellen, der:
- die Anfrage abfängt;
- die Kommas in den übermittelten Werten [a] und [b] durch Dezimalpunkte ersetzt;
- und anschließend die neue Anfrage an die Aktion weiterleitet, die sie verarbeiten soll;
Zunächst fügen wir ein verstecktes Feld in die Ansicht [vue-01.xml] ein:
<form ...>
...
</p>
<!-- versteckte Felder -->
<input type="hidden" id="culture" name="culture" th:value="${resultats.culture}"></input>
</form>
- Zeile 5: Der Wert von [fr-FR] oder [en-US] wird in das Attributfeld [name=culture] geschrieben. Da sich das Tag [input] im Formular befindet, wird sein Wert zusammen mit den Werten von [a] und [b] übermittelt. Man erhält dann eine übermittelte Zeichenkette der Form:
Es ist wichtig, diesen Punkt zu verstehen.
Anschließend fügen wir einen Filter in die Anwendungskonfiguration ein:
![]() |
Die Datei [Config] wird wie folgt geändert:
@Configuration
@ComponentScan({ "istia.st.springmvc.controllers", "istia.st.springmvc.models" })
@EnableAutoConfiguration
public class Config extends WebMvcConfigurerAdapter {
...
@Bean
public Filter cultureFilter() {
return new CultureFilter();
}
}
- Zeile 7: Da die Bean [cultureFilter] einen Typ [Filter] zurückgibt, handelt es sich um einen Filter. Die Bean selbst kann einen beliebigen Namen tragen;
Der nächste Schritt besteht darin, den Filter selbst anzulegen:
![]() |
package istia.st.springmvc.config;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
public class CultureFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// nächster Handler
filterChain.doFilter(new CultureRequestWrapper(request), response);
}
}
- Zeile 12: Wir erweitern die Klasse [OncePerRequestFilter], bei der es sich um eine Spring-Klasse handelt, und müssen die Methode [doFilterInternal] dieser Klasse neu definieren;
- Zeile 15: Die Methode [doFilterInternal] erhält drei Parameter:
- [HttpServletRequest request]: die zu filternde Anfrage. Diese kann nicht geändert werden,
- [HttpServletResponse response]: die Antwort, die an den Server gesendet wird. Der Filter kann entscheiden, diese selbst zu erstellen,
- [FilterChain filterChain]: die Filterkette. Sobald die Methode [doFilterInternal] ihre Arbeit beendet hat, muss sie die Anfrage an den nächsten Filter in der Filterkette weiterleiten;
- Zeile 18: Aus der empfangenen Anfrage [new CultureRequestWrapper(request)] wird eine neue Anfrage erstellt und an den nächsten Filter weitergeleitet. Da die ursprüngliche Anfrage [HttpServletRequest request] nicht geändert werden kann, wird eine neue erstellt;
Die Klasse [CultureRequestWrapper] sieht wie folgt aus:
![]() |
package istia.st.springmvc.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class CultureRequestWrapper extends HttpServletRequestWrapper {
public CultureRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues(String name) {
// Übermittelte Werte a und b
if (name != null && (name.equals("a") || name.equals("b"))) {
String[] values = super.getParameterValues(name);
String[] newValues = values.clone();
newValues[0] = newValues[0].replace(",", ".");
return newValues;
}
// andere Fälle
return super.getParameterValues(name);
}
}
- Zeile 6: Die Klasse [CultureRequestWrapper] erweitert die Klasse [HttpServletRequestWrapper] und wird einige ihrer Methoden neu definieren;
- Zeilen 8–10: Der Konstruktor, der die zu filternde Anfrage entgegennimmt und an die übergeordnete Klasse weiterleitet;
- Hierbei ist zu beachten, dass die gefilterte Anfrage letztendlich als Eingabeparameter an eine Klasse namens „Servlet“ übergeben wird. Bei Spring MVC ist dieses Servlet vom Typ [DispatcherServlet]. Diese Klasse verfügt über verschiedene Methoden zum Abrufen der Anfrageparameter: [getParameter, getParameterMap, getParameterNames, getParameterValues, ...]. Die vom Servlet verwendete Methode muss neu definiert werden. Dazu müsste man den Code der Klasse [DispatcherServlet] lesen. Ich habe dies nicht getan und verschiedene Methoden neu definiert. Letztendlich wurde die Methode [getParameterValues] neu definiert;
- Zeile 13: Die Methode [getParameterValues] erhält als Parameter den Namen eines der von der Methode [getParameterNames] zurückgegebenen Parameter und muss das Array seiner Werte zurückgeben. Tatsächlich ist bekannt, dass ein Parameter in einer Anfrage mehrfach vorkommen kann;
- Zeile 18: Das Komma wird durch einen Dezimalpunkt ersetzt;
Hier ein Ausführungsbeispiel:
![]() |
- In [1] werden die Werte von [a,b] im französischen Format eingegeben;
- in [2] die Ergebnisse;
- in [3] hat der Server eine Seite mit Zahlen im angelsächsischen Format zurückgegeben.
Dieses letzte Problem lässt sich mit Thymeleaf in der Ansicht [vue-01.xml] wie folgt beheben
<tr>
<td>
<input type="text" id="a" name="a" th:value="${resultats.culture}=='fr-FR' and ${actionModel01.a}!=null? ${#strings.replace(actionModel01.a,'.',',')} : ${actionModel01.a}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-number=#{typeMismatch},data-val-min=#{actionModel01.a.min},data-val-min-value=#{actionModel01.a.min.value}" />
</td>
<td>
<input type="text" id="b" name="b" th:value="${resultats.culture}=='fr-FR' and ${actionModel01.b}!=null? ${#strings.replace(actionModel01.b,'.',',')} : ${actionModel01.b}" data-val="true" th:attr="data-val-required=#{NotNull},data-val-number=#{typeMismatch},data-val-min=#{actionModel01.b.min},data-val-min-value=#{actionModel01.b.min.value}" />
</td>
</tr>
In den Zeilen 3 und 6 sind mehrere Änderungen vorzunehmen. Betrachten wir Zeile 3:
- Wir hatten [th:field="*{a}"] geschrieben. Der Parameter [th:field] legt die Attribute [id, name, value] des generierten Tags HTML [input] fest. Hier möchten wir das Attribut [value] selbst verwalten. Daher legen wir auch die Attribute [id, name] selbst fest;
- das Attribut [th:value] wertet einen Ausdruck aus, der den ternären Operator ? verwendet. Wir prüfen den Ausdruck [${resultats.culture}=='fr-FR' and ${actionModel01.b}!=null]. Ist er wahr, weisen wir dem Attribut [value] den Wert von [actionModel01.a] zu, wobei der Dezimalpunkt durch ein Komma ersetzt wird. Ist er falsch, wird dem Attribut [value] der Wert von [actionModel01.a] ohne Änderungen zugewiesen;
- Zeile 6: Das Gleiche wird für das Feld [b] wiederholt;
Hier ein Ausführungsbeispiel:
![]() |
- In [1] haben die Zahlen in [a,b] die französische Schreibweise beibehalten. Dies ist in [2] nicht der Fall;
Dieses neue Problem lässt sich auf die gleiche Weise wie das vorherige beheben. Die Ansicht [vue-03.xml] wird wie folgt geändert:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h4 th:text="#{resultats}">Résultats</h4>
<p>
<strong>
<span th:text="#{labelHeureCalcul}">Heure de calcul :</span>
<span id="heureCalcul" th:text="${resultats.heurePost}"></span>
</strong>
</p>
<p>
<span th:text="#{labelAplusB}">A+B=</span>
<span id="aplusb" th:text="${resultats.culture}=='fr-FR' and ${resultats.aplusb}!=null? ${#strings.replace(resultats.aplusb,'.',',')} : ${resultats.aplusb}"></span>
</p>
<p>
<span th:text="#{labelAmoinsB}">A-B=</span>
<span id="amoinsb" th:text="${resultats.culture}=='fr-FR' and ${resultats.amoinsb}!=null? ${#strings.replace(resultats.amoinsb,'.',',')} : ${resultats.amoinsb}"></span>
</p>
<p>
<span th:text="#{labelAfoisB}">A*B=</span>
<span id="amultiplieparb" th:text="${resultats.culture}=='fr-FR' and ${resultats.amultiplieparb}!=null? ${#strings.replace(resultats.amultiplieparb,'.',',')} : ${resultats.amultiplieparb}"></span>
</p>
<p>
<span th:text="#{labelAdivB}">A/B=</span>
<span id="adiviseparb" th:text="${resultats.culture}=='fr-FR' and ${resultats.adiviseparb}!=null? ${#strings.replace(resultats.adiviseparb,'.',',')} : ${resultats.adiviseparb}"></span>
</p>
</body>
</html>
Hier ein Beispiel:
![]() | ![]() |
Nun verfügen wir über eine Anwendung, die zwei Kulturen in einer Umgebung, in der JavaScript verwendet wird oder nicht, korrekt verarbeitet. Dazu musste der serverseitige Code erheblich komplexer gestaltet werden. Im weiteren Verlauf gehen wir stets davon aus, dass JavaScript im Browser aktiviert ist. Dies ermöglicht Funktionen, die im reinen Servermodus nicht möglich wären.
7.2.10. Verwaltung des Links [Calculer]
Betrachten wir den Link [Calculer] auf der Hauptseite [vue-01.xml]:
![]() | ![]() |
Der Code des Links [Calculer] in der Ansicht [vue-01.xml] lautet wie folgt:
<a href="javascript:postForm()" th:text="#{action.calculer}">Calculer</a>
Die Funktion jS [postForm] ist in der Datei [local1.js] wie folgt definiert:
// Gesamtdaten
var loading;
var formulaire;
var résultats;
var a, b;
function postForm() {
// Formular gültig?
if (!formulaire.validate().form()) {
// Formular ungültig – abgeschlossen
return;
}
// Es werden zwei Standorte verwaltet: [fr_FR, en_US]
// Die Ist-Werte [a,b] müssen in jedem Fall im angelsächsischen Format übermittelt werden
// Dies geschieht über den Filter [CultureFilter]
// Es wird manuell ein Ajax-Aufruf durchgeführt
$.ajax({
url : '/ajax-02',
headers : {
'„X-Requested-With“: „XMLHttpRequest“
},
type : 'POST',
data : formulaire.serialize(),
dataType : 'html',
beforeSend : function() {
loading.show();
},
success : function(data) {
resultats.html(data);
},
complete : function() {
loading.hide();
},
error : function(jqXHR) {
résultats.html(jqXHR.responseText);
}
})
}
- Zeilen 2–5: Zur Erinnerung: Diese Elemente wurden durch die Funktion [$(document).ready] initialisiert;
- Zeilen 9–12: Die Validatoren jS des Formulars werden ausgeführt. Ist einer der Werte ungültig, gibt der Ausdruck [formulaire.validate().form()] den Wert false zurück. In diesem Fall wird der [submit] des Formulars abgebrochen;
- Zeilen 18–38: Es wird manuell ein Ajax-Aufruf durchgeführt;
- Zeile 19: der URL als Ziel des Ajax-Aufrufs;
- Zeilen 20–22: Ein Array von Headern HTTP, die zu den standardmäßig in der Anfrage HTTP vorhandenen hinzugefügt werden sollen. Hier wird der Header HTTP hinzugefügt, der dem Server mitteilt, dass ein Ajax-Aufruf erfolgt;
- Zeile 23: Die verwendete Methode HTTP;
- Zeile 24: die übermittelten Daten. [formulaire.serialize] erstellt die zu übermittelnde Zeichenfolge [culture=fr-FR&a=12,7&b=20,89] des Formulars mit der ID [formulaire]. Hier taucht das zuvor untersuchte Problem wieder auf: Die Werte [a,b] müssen im angelsächsischen Format gesendet werden. Wir wissen, dass dieses Problem nun mit der Erstellung des Filters [cultureFilter] gelöst wurde;
- Zeile 25: Der erwartete Rückgabedatentyp. Wir wissen, dass der Server einen Datenstrom HTML zurückgibt;
- Zeile 26: Die Methode, die beim Start der Anfrage ausgeführt werden soll. Hier wird angegeben, dass die Komponente mit der ID [loading] angezeigt werden soll. Dabei handelt es sich um das animierte Ladebild;
- Zeile 29: Die Methode, die bei erfolgreichem Abschluss der Ajax-Anfrage ausgeführt werden soll. Der Parameter [data] ist die vollständige Antwort des Servers. Wir wissen, dass es sich um einen Datenstrom HTML handelt;
- Zeile 30: Die Komponente mit der ID [résultats] wird mit dem Wert HTML aus dem Parameter [data] aktualisiert.
- Zeile 33: Das Wartezuegezeichen wird ausgeblendet;
- Zeile 35: Diese Funktion wird ausgeführt, sobald die Antwort des Servers empfangen wurde, unabhängig davon, ob es sich um einen Erfolg oder einen Fehler handelt;
- Zeilen 35–37: Im Fehlerfall (der Server hat eine Antwort HTTP mit einem Status zurückgesendet, der auf einen Fehler auf Serverseite hinweist) wird die Antwort HTML des Servers im Feld [resultats] angezeigt;
Hier ein Ausführungsbeispiel:
![]() | ![]() |
7.3. Aktualisierung einer Seite HTML mit einem Feed jSON
Im vorherigen Beispiel antwortete der Webserver auf die Ajax-Anfrage HTTP mit einem Datenstrom HTML. Dieser Datenstrom enthielt Daten, die mit einer Formatierung HTML versehen waren. Wir möchten das vorherige Beispiel nun fortsetzen, diesmal jedoch mit Antworten vom Typ jSON (JavaScript Object Notation), die ausschließlich die Daten enthalten. Der Vorteil dabei ist, dass auf diese Weise weniger Bytes übertragen werden. Wir gehen davon aus, dass JavaScript im Browser aktiviert ist.
7.3.1. Die Aktion [/ajax-04]
Die Aktion [/ajax-04] ist identisch mit der Aktion [/ajax-01], mit dem Unterschied, dass die Ansicht [vue-04.xml] anstelle der Ansicht [vue-01.xml] angezeigt wird:
@RequestMapping(value = "/ajax-04", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax04(Locale locale, Model modèle, HttpSession session, String tempo) {
...
// Ansicht
return "vue-04";
}
7.3.2. Die Ansicht [vue-04.xml]
![]() |
Die Ansicht [vue-04.xml] übernimmt den Hauptteil der Ansicht [vue-01.xml] mit folgenden Unterschieden:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
...
<script type="text/javascript" src="/js/local4.js"></script>
<script th:inline="javascript">
/*<![CDATA[*/
var culture = [[${resultats.culture}]];
Globalize.culture(culture);
/*]]>*/
</script>
</head>
<body>
<h2>Ajax - 04</h2>
...
<form id="formulaire" name="formulaire" th:object="${actionModel01}">
...
<p>
<img id="loading" style="display: none" src="/images/loading.gif" />
<a href="javascript:postForm()" th:text="#{action.calculer}">Calculer</a>
</p>
<!-- versteckte Felder -->
<input type="hidden" id="culture" name="culture" th:value="${resultats.culture}"></input>
</form>
<hr />
<div id="entete">
<h4 id="titre">Résultats</h4>
<p>
<strong>
<span id="labelHeureCalcul">Heure de calcul :</span>
<span id="heureCalcul">12:10:87</span>
</strong>
</p>
</div>
<div id="résultats">
<p>
A+B=
<span id="aplusb">16,7</span>
</p>
<p>
A-B=
<span id="amoinsb">16,7</span>
</p>
<p>
A*B=
<span id="afoisb">16,7</span>
</p>
<p>
A/B=
<span id="adivb">16,7</span>
</p>
</div>
<div id="erreur">
<p style="color: red;">
<span id="msgErreur">xx</span>
</p>
</div>
</body>
</html>
- Zeile 5: Das JavaScript der Ansicht befindet sich nun in der Datei [local4.js];
- Zeile 16: Das Tag [form] enthält nicht mehr die Parameter [data-ajax-attr] aus der Bibliothek [Unobtrusive Ajax]. Wir werden es hier nicht verwenden. Das Tag [form] verfügt ebenfalls nicht über die Attribute [method] und [action], die angeben, wie und wohin die im Formular eingegebenen Werte gesendet werden sollen. Dies liegt daran, dass es von der Funktion jS (Zeile 20) übermittelt wird;
- Zeilen 26–57: Das ID-Feld [resultats], das zuvor leer war, enthält nun den Code HTML zur Anzeige der Ergebnisse;
- Zeilen 26–34: Die Kopfzeile der Ergebnisse, in der der Berechnungszeitpunkt angezeigt wird;
- Zeilen 35–52: die Ergebnisse der vier arithmetischen Operationen;
- Zeilen 53–57: eine eventuelle Fehlermeldung, die vom Server gesendet wird;
Der Code „jS“, der beim Laden der Ansicht „[vue-04.xm]“ ausgeführt wird, befindet sich in der Datei „[local4.js]“. Er lautet wie folgt:
// globale Daten
var loading;
var formulaire;
var résultats;
var titre;
var labelHeureCalcul;
var heureCalcul;
var aplusb;
var amoinsb;
var afoisb;
var adivb;
var msgErreur;
// beim Laden des Dokuments
$(document).ready(function() {
// werden die Referenzen der verschiedenen Komponenten der Seite abgerufen
loading = $("#loading");
formulaire = $("#formulaire");
résultats = $('#Ergebnisse');
titre=$("#titre");
labelHeureCalcul=$("#labelHeureCalcul");
heureCalcul=$("#heureCalcul");
aplusb=$("#aplusb");
amoinsb=$("#amoinsb");
afoisb=$("#afoisb");
adivb=$("#adivb");
msgErreur=$("#msgErreur");
// bestimmte Elemente werden ausgeblendet
résultats.hide();
erreur.hide();
loading.hide();
});
- Zeilen 17–27: Die jQuery-Referenzen aller Elemente der Seite werden abgerufen;
- Zeile 29: Der Ergebnisbereich wird ausgeblendet;
- Zeile 30: ebenso wie der Fehlerbereich;
- Zeile 31: ebenso wie das animierte Ladebild;
- Zeilen 2–12: Die abgerufenen Referenzen werden als globale Variablen deklariert, damit andere Funktionen darauf zugreifen können;
7.3.3. Die Funktion jS [postForm]
Der Link [Calculer] lautet wie folgt:
<p>
<img id="loading" style="display: none" src="/images/loading.gif" />
<a href="javascript:postForm()" th:text="#{action.calculer}">Calculer</a>
</p>
Die Funktion jS [postForm] ist in der Datei [local.js] wie folgt definiert:
function postForm() {
// Formular gültig?
if (!formulaire.validate().form()) {
// Formular ungültig – abgeschlossen
return;
}
// Man führt einen manuellen Ajax-Aufruf durch
$.ajax({
url : '/ajax-05',
headers : {
'Accept: 'application/json'
},
type : 'POST',
data : formulaire.serialize(),
dataType : 'json',
beforeSend : onBegin,
success : onSuccess,
error : onError,
complete : onComplete
})
}
// vor dem Ajax-Aufruf
function onBegin() {
...
}
// beim Empfang der Antwort vom Server
// im Erfolgsfall
function onSuccess(data) {
...
}
// beim Empfang der Serverantwort
// im Falle eines Fehlers
function onError(jqXHR) {
...
}
// nach [onSuccess, onError]
function onComplete() {
...
}
- Zeilen 3–6: Bevor die eingegebenen Werte übermittelt werden, werden sie überprüft. Sind sie fehlerhaft, wird die Aktion POST des Formulars nicht ausgeführt;
- Zeile 9: Die eingegebenen Werte werden an die Aktion [/ajax-05] gesendet, auf die wir weiter unten näher eingehen;
- Zeilen 10–12: Ein Header HTTP, um dem Server mitzuteilen, dass eine Antwort im Format jSON erwartet wird;
- Zeile 13: Die eingegebenen Werte werden gesendet;
- Zeile 14: Serialisierung der eingegebenen Werte in eine zum Senden bereite Zeichenkette [a=1,6&b=2,4&culture=fr-FR];
- Zeile 15: Der Typ der vom Server gesendeten Antwort. Dies wird jSON sein;
- Zeile 16: Die Funktion, die vor dem POST ausgeführt werden soll;
- Zeile 17: Die Funktion, die nach Erhalt der Serverantwort ausgeführt werden soll, sofern diese erfolgreich ist. Der „Erfolg“ einer Anfrage HTTP wird anhand des Status der Serverantwort HTTP gemessen. Eine Antwort [HTTP/1.1 200 OK ] ist eine erfolgreiche Antwort. Eine Antwort [HTTP/1.1 500 Internal Server Error] ist eine fehlgeschlagene Antwort. Der sogenannte Status einer Antwort HTTP ist der Code [200] oder [500]. Einige dieser Codes stehen für „Erfolg“, während andere Codes für „Fehlschlag“ stehen;
- Zeile 18: Die Funktion, die bei Empfang der Antwort vom Server ausgeführt werden soll, wenn der Status HTTP dieser Antwort einen Fehlerstatus darstellt;
- Zeile 18: Die Funktion, die zuletzt ausgeführt werden soll, nach den vorhergehenden Funktionen [onSuccess, onError];
Die Funktion [onBegin] lautet wie folgt:
// vor dem Ajax-Aufruf
function onBegin() {
console.log("onBegin");
// das animierte Bild wird angezeigt
loading.show();
// bestimmte Elemente der Ansicht werden ausgeblendet
entete.hide();
résultats.hide();
erreur.hide();
}
Bevor wir uns mit den anderen Funktionen jS des Ajax-Aufrufs befassen, müssen wir die von der Aktion [/ajax-05] gesendete Antwort kennen.
7.3.4. Die Aktion [/ajax-05]
Die Aktion [/ajax-05] lautet wie folgt:
@RequestMapping(value = "/ajax-05", method = RequestMethod.POST)
@ResponseBody()
// verarbeitet das POST aus der Ansicht [vue-04]
public JsonResults ajax05(@Valid ActionModel01 formulaire, BindingResult result, Locale locale, HttpServletRequest request, HttpSession session) throws InterruptedException {
if(result.hasErrors()){
// Ausnahmefall – es wird nichts gerendert
return null;
}
...
}
- Zeile 2: Das Attribut [ResponseBody] gibt an, dass die Aktion [/ajax-05] die Antwort selbst an den Client zurückgibt. Da sich eine Bibliothek vom Typ jSON in den Projektabhängigkeiten befindet, konfiguriert Spring Boot diesen Aktionstyp automatisch so, dass er jSON zurückgibt. Es ist also die Kette jSON vom Typ [JsonResults] (Zeile 4), die an den Client gesendet wird;
- Zeile 2: Die gesendeten Werte [a, b, culture] werden in einen Typ [ActionModel01] gekapselt, für den die Validierung [@Valid ActionModel01] angefordert wird. Das dient nur der Form. Wir sind davon ausgegangen, dass JavaScript im Browser des Kunden aktiviert ist und die gesendeten Werte daher bei ihrem Eintreffen bereits auf der Client-Seite überprüft wurden. Dennoch kann der Fall eines „wilden“ POST vorgesehen werden, der unseren Client jS nicht verwendet. In diesem Fall kann die Validierung fehlschlagen;
- Zeilen 5–7: Im Fehlerfall wird ein leerer jSON-Datenstrom zurückgegeben;
Setzen wir die Untersuchung der Aktion [/ajax-05] fort:
@RequestMapping(value = "/ajax-05", method = RequestMethod.POST)
@ResponseBody()
// verarbeitet das POST aus der Ansicht [vue-04]
public JsonResults ajax05(@Valid ActionModel01 formulaire, BindingResult result, Locale locale,
HttpServletRequest request, HttpSession session) throws InterruptedException {
...
// der Spring-Anwendungskontext
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
// Tempo?
Integer tempo = (Integer) session.getAttribute("tempo");
if (tempo != null && tempo > 0) {
Thread.sleep(tempo);
}
...
// Das Ergebnis wird zurückgegeben
return résultats;
}
- Zeile 8: Der Kontext [ctx] wird aus der Spring-Anwendung abgerufen. Dieser wird benötigt, um die Nachrichten aus den Dateien [messages.properties] anhand eines Nachrichten-Schlüssels und einer Locale abzurufen. Dies erfolgt mit folgender Syntax:
ctx.getMessage(clé_message, tableau_de_paramètres, locale)
- [clé_message]: der Schlüssel der gesuchten Nachricht;
- [locale]: die verwendete Locale. Wenn diese Locale also [en_US] lautet, wird die Datei [messages_en.properties] verwendet;
- [tableau_de_paramètres]: Die erhaltene Nachricht kann so konfiguriert werden wie in [clé=message {0} {1}]. In dieser Nachricht gibt es zwei Parameter [{0} {1}]. Als zweiten Parameter von [ctx.getMessage] muss ein Array mit zwei Werten angegeben werden;
- Zeilen 10–13: Wenn in der Sitzung eine Wartezeit vorgesehen ist, wird der aktuelle Thread für die Dauer dieser Wartezeit angehalten;
Die Aktion [/ajax-05] wird wie folgt fortgesetzt:
// das Modell für die nächste Ansicht wird vorbereitet
JsonResults résultats = new JsonResults();
...
}
- Zeile 2: Erstellung der Vorlage für die Zeichenkette jSON, die an den Client gesendet wird;
Die Vorlage [JsonResults] lautet wie folgt:
![]() |
package istia.st.springmvc.models;
public class JsonResults {
// Daten
private String titre;
private String labelHeureCalcul;
private String heureCalcul;
private String aplusb;
private String amoinsb;
private String afoisb;
private String adivb;
private String msgErreur;
// Getter und Setter
...
}
- Zeilen 6–13: Jedes Feld der Klasse [JsonResult] entspricht einem Feld derselben Klasse [id] in der Ansicht [vue-04.xml]:
Die Aktion [/ajax-05] läuft wie folgt ab:
// Das Modell für die nächste Ansicht wird vorbereitet
JsonResults résultats = new JsonResults();
// Kopfzeile
résultats.setTitre(ctx.getMessage("resultats.titre", null, locale));
résultats.setLabelHeureCalcul(ctx.getMessage("labelHeureCalcul", null, locale));
résultats.setHeureCalcul(new SimpleDateFormat("hh:mm:ss").format(new Date()));
// Es wird bei jedem zweiten Versuch ein Fehler generiert
int val = new Random().nextInt(2);
if (val == 0) {
// Es wird eine Fehlermeldung zurückgegeben
résultats.setMsgErreur(ctx.getMessage("resultats.erreur",
new Object[] { ctx.getMessage("erreur.aleatoire", null, locale) }, locale));
return résultats;
}
- Zeile 2: Erstellung der Vorlage für die an den Kunden gesendete Zeichenfolge jSON;
- Zeilen 4–6: Die Nachrichten des Ergebnis-Headers werden erstellt;
- Zeilen 8–14: Im Durchschnitt wird in jedem zweiten Fall eine Fehlermeldung generiert. In diesem Fall wird der Vorgang nicht fortgesetzt und die Zeichenfolge jSON an den Kunden zurückgegeben (Zeile 13);
- Zeile 11: Hier sehen wir ein Beispiel für eine parametrisierte Meldung:
erreur.aleatoire=erreur aléatoire
resultats.erreur=Une erreur s''est produite : [{0}]
Die Aktion [/ajax-05] wird wie folgt fortgesetzt:
// Die übermittelten Werte werden abgerufen
double a = formulaire.getA();
double b = formulaire.getB();
// Das Modell wird erstellt
résultats.setAplusb(String.valueOf(a + b));
résultats.setAmoinsb(String.valueOf(a - b));
résultats.setAfoisb(String.valueOf(a * b));
try {
résultats.setAdivb(String.valueOf(a / b));
} catch (RuntimeException e) {
résultats.setAdivb("NaN");
}
// Das Ergebnis wird zurückgegeben
return résultats;
- Zeilen 2–3: Die Werte von [a] und [b] werden abgerufen;
- Zeilen 5–12: Die vier Ergebnisse werden gebildet;
- Zeile 14: Die Zeichenfolge jSON [JsonResults] wird an den Client gesendet;
Schauen wir uns an, wie das mit dem Client [Advanced Rest Client] aussieht:
![]() |
- Bei [1-2] wird eine Anfrage POST an die Aktion [/ajax-05] gesendet;
- Bei [3] werden falsche Werte gesendet;
- In [4] hat der Server einen leeren Datenstrom zurückgegeben;
![]() |
- in [1] werden korrekte Werte gesendet;
- in [2] wird das vom Server zurückgegebene Objekt jSON angezeigt, hier mit einer Fehlermeldung;
![]() |
- In [1] werden korrekte Werte übermittelt;
- in [2] das vom Server zurückgegebene Objekt jSON, hier mit den vier Ergebnissen;
![]() |
- In [1] werden korrekte Werte übermittelt;
- In [2] wurde eine serverseitige Ausnahme ausgelöst. Man sieht, dass der Server erneut ein Objekt jSON sendet. In dieser Meldung ist zu erkennen, dass der Status HTTP der Antwort [500] lautet, was darauf hinweist, dass ein Fehler auf der Serverseite aufgetreten ist;
7.3.5. Die Funktion jS [postForm] – 2
Da wir nun das vom Server zurückgegebene Objekt jSON kennen, können wir es im JavaScript auswerten. Die Methode [onSuccess], die ausgeführt wird, wenn der Server eine Antwort mit dem Status HTTP [200] sendet, lautet wie folgt:
// nach Erhalt der Antwort vom Server
// im Erfolgsfall
function onSuccess(data) {
console.log("onSuccess");
// wird der Ergebnisbereich ausgefüllt
titre.text(data.titre);
labelHeureCalcul.text(data.labelHeureCalcul);
heureCalcul.text(data.heureCalcul);
entete.show();
// Ergebnisse ohne Fehler
if (!data.msgErreur) {
aplusb.text(data.aplusb);
amoinsb.text(data.amoinsb);
afoisb.text(data.afoisb);
adivb.text(data.adivb);
résultats.show();
return;
}
// Ergebnisse mit Fehler
msgErreur.text(data.msgErreur);
erreur.show();
}
- Zeile 3: Der Parameter [data] ist das vom Server zurückgegebene Objekt jSON:
![]() |
Die Methode [onError], die ausgeführt wird, wenn der Status der Antwort HTTP [500] ist, lautet wie folgt:
// nach Erhalt der Antwort vom Server
// im Falle eines Fehlers
function onError(jqXHR) {
console.log("onError");
// Systemfehler
msgErreur.text(jqXHR.responseText);
erreur.show();
}
- Zeile 3: Das Objekt JQuery [jqXHR] weist unter seinen Eigenschaften Folgendes auf:
- responseText: der Text der Serverantwort,
- status: der vom Server zurückgegebene Fehlercode,
- statusText: der diesem Fehlercode zugeordnete Text;
- Zeile 6: Das Objekt [jqXHR.responseText] ist das folgende Objekt jSON:
![]() |
7.3.6. Tests
Sehen wir uns einige Screenshots der Ausführung der Webanwendung an:
![]() |
![]() |
![]() |
7.4. Einseitige Webanwendung
7.4.1. Einleitung
Mit der Ajax-Technologie lassen sich Single-Page-Anwendungen erstellen:
- Die erste Seite wird durch eine herkömmliche Browseranfrage geladen;
- die folgenden Seiten werden über Ajax-Aufrufe abgerufen. Somit wechselt der Browser letztendlich nie die URL und lädt nie eine neue Seite. Diese Art von Anwendung wird als Single-Page-Anwendung (APU) oder auf Englisch als Single Page Application (SPA) bezeichnet.
Hier ist ein einfaches Beispiel für eine solche Anwendung. Die neue Anwendung wird zwei Ansichten haben:
![]() |
![]() |
- In [1] ermöglicht uns die Aktion [/ajax-06], die erste Seite, also Seite 1, aufzurufen;
- In [2] ermöglicht uns ein Link, mittels eines Ajax-Aufrufs zu Seite 2 zu wechseln;
- In [3] hat sich URL nicht geändert. Die angezeigte Seite ist Seite 2;
- In [4] ermöglicht uns ein Link, mittels eines Ajax-Aufrufs zu Seite 1 zurückzukehren;
- In [5] hat sich URL nicht geändert. Die angezeigte Seite ist Seite 1.
7.4.2. Die Aktion [/ajax-06]
Der Code der Aktion [/ajax-06] lautet wie folgt:
@RequestMapping(value = "/ajax-06", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax06() {
return "vue-06";
}
- Zeilen 1–4: Die Aktion [/ajax-06] gibt lediglich die Ansicht [vue-06.xml] zurück;
7.4.3. Die Ansicht [vue-06.xml]
Die Ansicht [vue-06.xml] lautet wie folgt:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-06</title>
<link rel="stylesheet" href="/css/ajax01.css" />
<script type="text/javascript" src="/js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/local6.js"></script>
</head>
<body>
<h3>Ajax - 06 - Navigation dans une Application à Page Unique</h3>
<div id="content" th:include="vue-07" />
</body>
</html>
- Zeile 8: Die Ansicht verwendet ein Skript [local6.js];
- Zeile 12: Die Ansicht [vue-07.xml] wird in den ID-Bereich [content] der Ansicht [vue-06.xml] eingebunden;
7.4.4. Die Ansicht [vue-07.xml]
Die Ansicht [vue-07.xml] lautet wie folgt:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h4>Page 1</h4>
<p>
<a href="javascript:gotoPage(2)">Page 2</a>
</p>
</body>
</html>
7.4.5. Die Funktion jS [gotoPage]
Der Link [Page 2] der Ansicht [vue-07.xml] verwendet die Funktion jS [gotoPage], die in der folgenden Datei [local6.js] definiert ist:
// globale Daten
var content;
function gotoPage(num) {
// Es wird manuell ein Ajax-Aufruf durchgeführt
$.ajax({
url : '/ajax-07',
type : 'POST',
data : 'num=' + num,
dataType : 'html',
beforeSend : function() {
},
success : function(data) {
content.html(data)
},
complete : function() {
},
error : function(jqXHR) {
// Systemfehler
content.html(jqXHR.responseText);
}
})
}
// beim Laden des Dokuments
$(document).ready(function() {
// Die Referenzen der verschiedenen Komponenten der Seite werden abgerufen
content = $("#content");
});
- Zeile 28: Beim Laden der Seite wird das Feld mit der ID [content] gespeichert und als globale Variable definiert (Zeile 2);
- Zeile 4: Die Funktion [gotoPage] erhält als Parameter die Nummer der Seite (1 oder 2), die in der aktuellen Ansicht angezeigt werden soll;
- Zeile 7: Die Funktion URL ist das Ziel der Funktion POST;
- Zeile 8: Die Funktion URL aus Zeile 7 wird über eine POST aufgerufen;
- Zeile 9: Die gesendete Zeichenkette. Es wird ein Parameter namens [num] gesendet. Sein Wert ist die Seitenzahl (Zeile 4), die in der aktuellen Ansicht angezeigt werden soll;
- Zeile 10: Der Server sendet HTML zurück, den Parameter der anzuzeigenden Seite;
- Zeilen 13–15: Bei Erfolg (Status HTTP gleich 200) wird der vom Server gesendete Wert HTML in das ID-Feld [content] geschrieben;
- Zeilen 18–20: Bei einem Fehler (Status HTTP gleich 500) wird der vom Server gesendete Wert HTML in das ID-Feld [content] geschrieben;
7.4.6. Die Aktion [/ajax-07]
Der Aktionscode [/ajax-07] lautet wie folgt:
@RequestMapping(value = "/ajax-07", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
public String ajax07(int num) {
// num: Seitenzahl
switch (num) {
case 1:
return "vue-07";
case 2:
return "vue-08";
default:
return "vue-07";
}
}
- Zeile 2: Der über POST übermittelte Parameter mit dem Namen [num] wird abgerufen. Es sei daran erinnert, dass der Parameter in Zeile 2 den Namen des über POST übermittelten Parameters tragen muss, in diesem Fall [num]. [num] ist eine Seiten- oder Ansichtsnummer;
- Zeilen 5–6: Falls [num==1], wird die Ansicht [vue-07.xml] zurückgegeben;
- Zeilen 7–8: Ist [num==2] der Fall, wird die Ansicht [vue-08.xml] zurückgegeben;
- Zeilen 9–10: In allen anderen Fällen (was normalerweise nicht möglich ist) wird die Ansicht [vue-07.xml] zurückgegeben;
7.4.7. Die Ansicht [vue-08.xml]
Die Ansicht [vue-08.xml] bildet Seite 2 der Anwendung:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h4>Page 2</h4>
<p>
<a href="javascript:gotoPage(1)">Page 1</a>
</p>
</body>
</html>
7.5. Mehrere Datenströme HTML in eine Antwort jSON einbinden
7.5.1. Einleitung
Wir betrachten die folgende Anwendung:
![]() |
Die Seite [1] enthält vier Bereiche:
- [Zone 1, Zone 3] sind Bereiche, die beim Klicken auf die Schaltfläche [Rafraîchir] ein- bzw. ausgeblendet werden. Wir zählen die Anzahl der Erscheinungen jedes dieser beiden Bereiche [2]. Der Bereich [Zone 1] verwendet die französische Sprache, während der Bereich [Zone 3] die englische Sprache verwendet;
- der Bereich [Zone 2] ist ständig vorhanden;
- der Bereich [Saisies] ist ständig vorhanden;
Der Link [Valider] zeigt die folgende Seite [3] an:
![]() |
- Der Link [Retour à la page 1] versetzt Seite Nr. 1 wieder in den Zustand, in dem sie sich zuvor befand ([4]);
Die Anwendung besteht aus einer einzigen Seite. Die erste Seite wird vom Browser beim Server angefordert. Die folgenden Seiten werden über Ajax-Aufrufe vom Server abgerufen.
7.5.2. Die Aktie [/ajax-09]
![]() |
Die Aktion [/ajax-09] lautet wie folgt:
@RequestMapping(value = "/ajax-09", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax09() {
return "vue-09";
}
Sie zeigt lediglich die Ansicht [vue-09.xml] an.
7.5.3. Die Ansichten XML
![]() |
Die Ansicht [vue-09.xml] ist die Startseite der Anwendung:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-09</title>
<link rel="stylesheet" href="/css/ajax01.css" />
<script type="text/javascript" src="/js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/json3.js"></script>
<script type="text/javascript" src="/js/local9.js"></script>
</head>
<body>
<h3>Ajax - 09 - Navigation dans une Application à Page Unique</h3>
<h3>avec des flux HTML embarqués dans des chaînes jSON</h3>
<hr />
<div id="content" th:include="vue-09-page1" />
<img id="loading" src="/images/loading.gif" />
<div id="erreur" style="background-color:lightgrey"></div>
</body>
</html>
- Zeile 9: die in der Anwendung verwendete Datei JS;
- Zeile 15: der Inhalt der Master-Seite;
- Zeile 16: ein animiertes Ladebild:
- Zeile 17: Bereich zur Anzeige eines eventuellen Fehlers;
Die Ansicht [vue-09-page1.xml] ist Seite 1 der Anwendung:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h2>Page 1</h2>
<!-- Bereich 1 -->
<fieldset id="zone1" style="background-color:pink">
<legend>Zone 1</legend>
<span id="zone1-content" th:text="xx">xx</span>
</fieldset>
<!-- Bereich 2 -->
<fieldset id="zone2" style="background-color:lightgreen">
<legend>Zone 2</legend>
<span>Ce texte reste toujours présent</span>
</fieldset>
<!-- Bereich 3 -->
<fieldset id="zone3" style="background-color:yellow">
<legend>Zone 3</legend>
<span id="zone3-content" th:text="zz">zz</span>
</fieldset>
<br />
<p>
<button onclick="javascript:postForm()">Rafraîchir</button>
</p>
<hr />
<div id="saisies" th:include="vue-09-saisies">
</div>
</body>
</html>
- Zeilen 6–9: der Bereich [Zone 1]. Sein Inhalt wird in die Komponente [id="zone1-content"] eingefügt;
- Zeilen 11–14: das Feld [Zone 2], das sich nicht ändert;
- Zeilen 16–19: das Feld [Zone 3]. Sein Inhalt wird in die Komponente [id="zone3-content"] übernommen;
- Zeile 22: die Funktion JS, die das Formular übermittelt;
- Zeile 25: Einbindung des Eingabefelds;
Es ist zu beachten, dass Seite 1 kein [form]-Tag enthält. Die gesamte Verarbeitung erfolgt in JavaScript.
Die Ansicht [vue-09-saisies.xml] sieht wie folgt aus:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<div id="saisies">
<h4>Saisies :</h4>
<p>
Chaîne de caractères :
<input type="text" id="text1" size="30" th:value="${value1}" />
</p>
<p>
Nombre entier :
<input type="text" id="text2" size="10" th:value="${value2}" />
</p>
<p>
<a href="javascript:valider()">Valider</a>
</p>
</div>
</html>
- Zeilen 5–8: Eingabe einer Zeichenkette;
- Zeilen 13–16: Eingabe einer ganzen Zahl;
- Zeile 14: Die Funktion JS, die die eingegebenen Werte übermittelt;
Auch hier ist zu beachten, dass der Eingabebereich kein Tag [form] enthält.
Insgesamt bietet Seite Nr. 1 zwei Funktionen:
- [Rafraîchir]: Diese aktualisiert die Felder 1 und 3. Diese Aktion wird vom Server verarbeitet, der nach dem Zufallsprinzip Folgendes zurückgibt:
- Bereich 1 mit seinem Zugriffszähler und nichts für Bereich 3,
- Bereich 3 mit seinem Zugriffszähler und nichts für Bereich 1,
- beide Bereiche mit ihren Zugriffszählern;
- [Valider]: Zeigt Seite 2 mit den eingegebenen Werten an oder eine Fehlermeldung, wenn die eingegebenen Daten ungültig sind;
Wir werden uns zunächst mit der Schaltfläche [Rafraîchir] befassen.
7.5.4. Der Code JS zur Verwaltung der Schaltfläche [Rafraîchir]
![]() |
Der Code der Datei [local9.js] lautet wie folgt:
// globale Variablen
var content;
var loading;
var erreur;
// beim Laden des Dokuments
$(document).ready(function() {
// Die Referenzen der verschiedenen Komponenten der Seite werden abgerufen
loading = $("#loading");
loading.hide();
erreur = $("#erreur");
erreur.hide();
content = $("#content");
});
- Zeilen 9–13: Beim Laden der Master-Seite werden die Referenzen der drei durch [loading, erreur, content] identifizierten Komponenten gespeichert;
- Zeilen 2–4: Die Referenzen dieser drei Komponenten werden in globalen Variablen gespeichert. Sie bleiben unverändert, da die drei betreffenden Bereiche zu jedem Zeitpunkt auf der angezeigten Seite vorhanden sind. Da sie unverändert bleiben, können sie in [$(document).ready] berechnet und an die anderen Funktionen der Datei JS weitergegeben werden;
Die Funktion [postForm] verarbeitet den Klick auf die Schaltfläche [Rafraîchir]:
function postForm() {
console.log("postForm");
// Es wird manuell ein Ajax-Aufruf durchgeführt
$.ajax({
url : '/ajax-10',
headers : {
'Accept: 'application/json'
},
type : 'POST',
dataType : 'json',
beforeSend : onBegin,
success : onSuccess,
error : onError,
complete : onComplete
})
}
- Zeilen 4–15: der Ajax-Aufruf an den Server;
- Zeile 5: Die Aktion [ajax-10] verarbeitet die Aktion POST;
- Zeilen 6–8: Die Antwort lautet jSON. Der Client JS gibt an, dass er die Dokumente jSON akzeptiert;
- Zeile 9: Die Aktion [ajax-10] wird mit einer Operation POST aufgerufen;
- Zeile 10: Es wird eine Antwort von jSON empfangen;
- Zeile 11: Die vor dem Ajax-Aufruf ausgeführte Funktion;
- Zeile 12: Die Funktion, die beim Empfang der Serverantwort ausgeführt wird, wenn diese erfolgreich ist: [200 OK];
- Zeile 13: Die Funktion, die beim Empfang der Serverantwort ausgeführt wird, wenn diese fehlgeschlagen ist: [500 Internal server error, ...];
- Zeile 14: Die Funktion, die nach dem Empfang der Antwort ausgeführt wird;
Die Funktion [onBegin] lautet wie folgt:
// vor dem Ajax-Aufruf
function onBegin() {
console.log("onBegin");
// Ladebild
loading.show();
}
Sie sorgt lediglich dafür, dass das animierte Bild für das Warten auf das Serverergebnis gestartet wird.
7.5.5. Die Aktion [/ajax-10]
![]() |
Die Aktion [/ajax-10] hat folgende Funktion:
// Die Sitzung
@Autowired
private SessionModel1 session;
// die Thymeleaf-/Spring-Engine
@Autowired
private SpringTemplateEngine engine;
@RequestMapping(value = "/ajax-10", method = RequestMethod.POST)
@ResponseBody()
public JsonResult10 ajax10(HttpServletRequest request, HttpServletResponse response) {
...
}
- Zeile 3: Die Sitzung wird eingefügt. Diese hat den folgenden Typ [SessionModel1]:
![]() |
package istia.st.springmvc.models;
import java.io.Serializable;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionModel1 implements Serializable {
private static final long serialVersionUID = 1L;
// zwei Zähler
private int cpt1 = 0;
private int cpt3 = 0;
// die drei Bereiche
private String zone1 = "xx";
private String zone3 = "zz";
private String saisies;
private boolean zone1Active = true;
private boolean zone3Active = true;
// Getter und Setter
...
}
Die Sitzung [SessionModel1] speichert folgende Elemente:
- Zeile 15: die Anzahl der Aufrufe von [cpt1], bei denen das Feld [Zone 1] angezeigt wird;
- Zeile 16: die Anzahl der Fälle, in denen das Feld [Zone 3] angezeigt wird;
- Zeilen 18–20: die Datenströme HTML der Felder [Zone 1], [Zone 3] und [Saisies]. Dies ist in der Reihenfolge [Page 1] --> [Page 2] --> [Page 1] erforderlich. Beim Übergang von [Page 2] zu [Page 1] müssen [Page 1] und damit dessen drei Felder wiederhergestellt werden;
- Zeilen 21–22: zwei Boolesche Werte, die angeben, ob die Felder [Zone 1] und [Zone 3] angezeigt (sichtbar) werden;
Das andere Element, das in den Controller [AjaxController] eingefügt wurde, lautet wie folgt:
// die Thymeleaf-/Spring-Engine
@Autowired
private SpringTemplateEngine engine;
Die Bean vom Typ [SpringTemplateEngine] ist in der Konfigurationsdatei [Config] definiert:
![]() |
Sie ist wie folgt definiert:
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".xml");
templateResolver.setTemplateMode("HTML5");
templateResolver.setCacheable(true);
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}
@Bean
SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
- Zeilen 2–10: Wir kennen die Bean vom Typ [SpringResourceTemplateResolver], mit der wir bestimmte Eigenschaften der Ansichten definieren können;
- Zeilen 13–17: Mit der Bean vom Typ [SpringTemplateEngine] können wir die „Engine“ der Ansichten definieren, also die Klasse, die für die Generierung der Antworten [Thymeleaf] an die Clients zuständig ist. [Thymeleaf] verfügt über eine Standard-„Engine“ und eine weitere, wenn es in einer [Spring]-Umgebung verwendet wird. Letztere verwenden wir hier;
Die Signatur der Aktion [/ajax-10] lautet wie folgt:
@RequestMapping(value = "/ajax-10", method = RequestMethod.POST)
@ResponseBody()
public JsonResult10 ajax10(HttpServletRequest request, HttpServletResponse response) {
...
}
- Zeile 1: Die Aktion [/ajax-10] akzeptiert nur ein POST;
- Zeile 2: Die Aktion [/ajax-10] gibt die Antwort selbst an den Client zurück. Diese wird automatisch in jSON umgewandelt;
- Zeile 3: Die Antwort hat den Typ [JsonResult10] wie folgt:
![]() |
package istia.st.springmvc.models;
public class JsonResult10 {
// Daten
private String content;
private String zone1;
private String zone3;
private String erreur;
private String saisies;
private boolean zone1Active;
private boolean zone3Active;
public JsonResult10() {
}
// Getter und Setter
...
}
- Zeile 6: Der Inhalt HTML des durch [content] identifizierten Feldes;
- Zeile 7: den Inhalt HTML des Feldes [Zone 1];
- Zeile 8: den Inhalt HTML des Feldes [Zone 3];
- Zeile 9: den Inhalt HTML aus dem Feld [Erreur];
- Zeile 10: den Inhalt HTML des Feldes [Saisies];
- Zeile 11: Boolescher Wert, der angibt, ob das Feld [Zone 1] angezeigt werden soll;
- Zeile 12: Boolescher Wert, der angibt, ob das Feld [Zone 3] angezeigt werden soll;
Der Aktionscode [/ajax-10] lautet wie folgt:
@RequestMapping(value = "/ajax-10", method = RequestMethod.POST)
@ResponseBody()
public JsonResult10 ajax10(HttpServletRequest request, HttpServletResponse response) {
// Thymeleaf-Kontext
WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
// Antwort
JsonResult10 result = new JsonResult10();
// Sitzung
session.setZone1(null);
session.setZone3(null);
session.setZone1Active(false);
session.setZone3Active(false);
// Es wird eine zufällige Antwort zurückgegeben
int cas = new Random().nextInt(3);
switch (cas) {
case 0:
// Zone 1 aktiv
setZone1(thymeleafContext, result);
return result;
case 1:
// Zone 3 aktiv
setZone3(thymeleafContext, result);
return result;
case 2:
// Zonen 1 und 3 aktiv
setZone1(thymeleafContext, result);
setZone3(thymeleafContext, result);
return result;
}
return null;
}
- Zeile 5: Wir rufen den Kontext [Thymeleaf] ab. Wir werden später sehen, wozu er uns dienen wird;
- Zeile 7: Wir erstellen vorerst eine leere Antwort;
- Zeilen 9–12: Wir übertragen die beiden in der Sitzung enthaltenen Felder in [null] und geben an, dass sie nicht angezeigt werden sollen. Diese beiden Felder werden in Kürze generiert, es ist jedoch möglich, dass nur eines davon generiert wird;
- Zeilen 14–29: Die beiden Felder werden generiert;
- Zeilen 17–19: Es wird nur das Feld [Zone 1] generiert;
- Zeilen 21–23: Nur das Feld [Zone 3] wird generiert;
- Zeilen 25–28: Die beiden Felder [Zone 1] und [Zone 3] werden generiert;
Der Datenstrom HTML des Feldes [Zone 1] wird nach folgendem Schema generiert:
private void setZone1(WebContext thymeleafContext, JsonResult10 result) {
// Zone 1 aktiv
// Feed HTML
int cpt1 = session.getCpt1() + 1;
thymeleafContext.setVariable("cpt1", cpt1);
thymeleafContext.setLocale(new Locale("fr", "FR"));
String zone1 = engine.process("vue-09-zone1", thymeleafContext);
result.setZone1(zone1);
result.setZone1Active(true);
// Sitzung
session.setCpt1(cpt1);
session.setZone1(zone1);
session.setZone1Active(true);
}
- Zeile 1: Die Parameter lauten:
- der Kontext [Thymeleaf] vom Typ [WebContext],
- die gerade erstellte Antwort an den Kunden vom Typ [JsonResult10];
- Zeile 3: Der Zähler [cpt1] der Sitzung, der die Anzahl der Anzeigen des Feldes [Zone 1] zählt, wird erhöht;
- Zeile 4: Der Kontext [Thymeleaf] vom Typ [WebContext] verhält sich in etwa wie das Spring-Modell [Model] (MVC). Um ein Element zum Modell hinzuzufügen, wird [WebContext.setVariable] verwendet. Hier wird also der Zähler [cpt1] in das Modell [Thymeleaf] eingefügt. Dadurch kann der Thymeleaf-Ausdruck [${cpt1}] ausgewertet werden
- Zeile 5: Der Kontext [Thymeleaf] verfügt über eine Locale. Dadurch kann er Ausdrücke vom Typ [#{clé_msg}] auswerten. Hier wird dem Thymeleaf-Kontext eine französische Locale zugewiesen;
- Zeile 6: Dies ist die interessanteste Anweisung. Die Thymeleaf-Engine verarbeitet die Ansicht [vue-09-zone1.xml] mit der soeben berechneten Vorlage und der Locale und gibt den resultierenden Datenstrom HTML nicht an den Client weiter, sondern gibt ihn als Zeichenkette zurück;
- Zeilen 7–9: Der soeben berechnete Datenstrom HTML aus dem Bereich [Zone 1] wird in der Sitzung und im Ergebnis gespeichert, das an den Client gesendet wird. Außerdem wird festgelegt, dass das Feld [Zone 1] angezeigt werden soll;
- Zeilen 11–13: Die Informationen zum Bereich [Zone 1] werden in der Sitzung gespeichert, um ihn neu generieren zu können;
Zeile 7 verarbeitet die folgende Ansicht [vue-09-zone1.xml]:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<span th:text="#{message.zone}"></span>
<span th:text="${cpt1}"></span>
</html>
- Zeile 3: Der Ausdruck [#{message.zone}] wird mithilfe der lokalen Variable ausgewertet;
- Zeile 4: Der Ausdruck [${cpt1}] wird mithilfe des Thymeleaf-Templates ausgewertet;
Die Schlüsselmeldung [message.zone] ist in den Meldungsdateien [messages_fr.properties] und [messages_en.properties] definiert:
![]() |
[messages_fr.properties]
message.zone=Nombre d'accès :
[messages_en.properties]
message.zone=Number of hits:
Der Datenstrom HTML aus dem Bereich [Zone 3] wird auf ähnliche Weise generiert:
private void setZone3(WebContext thymeleafContext, JsonResult10 result) {
// Zone 3 aktiv
// Datenstrom HTML
int cpt3 = session.getCpt3() + 1;
thymeleafContext.setVariable("cpt3", cpt3);
thymeleafContext.setLocale(new Locale("en", "US"));
String zone3 = engine.process("vue-09-zone3", thymeleafContext);
result.setZone3(zone3);
result.setZone3Active(true);
// Sitzung
session.setCpt3(cpt3);
session.setZone3(zone3);
session.setZone3Active(true);
}
- Zeile 6: Die Sprache des Feldes [Zone 3] ist Englisch;
7.5.6. Verarbeitung der Antwort der Aktion [/ajax-10]
Kehren wir zum Code JS von [local9.js] zurück, der die Antwort des Servers verarbeitet:
// nach Erhalt der Antwort vom Server
// bei Erfolg
function onSuccess(data) {
console.log("onSuccess");
// Inhalt
if (data.content) {
content.html(data.content);
}
// Zone 1
if (data.zone1Active) {
$("#zone1").show();
if (data.zone1) {
$("#zone1-content").html(data.zone1);
}
} else {
$("#zone1").hide();
}
// Zone 3 aktiv?
if (data.zone3Active) {
$("#zone3").show();
if (data.zone3) {
$("#zone3-content").html(data.zone3);
}
} else {
$("#zone3").hide();
}
// Eingaben?
if (data.saisies) {
$("#saisies").html(data.saisies);
}
// Fehler?
if (data.erreur) {
erreur.text(data.erreur);
erreur.show();
} else {
erreur.hide();
}
}
Erinnern wir uns an die Java-Struktur der in Zeile 3 in der Variablen [data] empfangenen Antwort:
public class JsonResult10 {
// Daten
private String content;
private String zone1;
private String zone3;
private String erreur;
private String saisies;
private boolean zone1Active;
private boolean zone3Active;
}
- Zeilen 6–8: Wenn [data.content!=null], dann wird das Feld [id=content] damit initialisiert. Dieses Feld repräsentiert [Page 1] oder [Page 2] in seiner Gesamtheit. In der vorliegenden Demonstration haben wir [data.content==null], daher wird das Feld [id=content] nicht geändert und zeigt weiterhin [Page 1] an;
- Zeilen 10–17: Anzeige von [Zone 1], wenn [data.zone1Active==true]. Ist zudem [data.zone1!=null], wird der Inhalt von [Zone 1] geändert, andernfalls bleibt er unverändert;
- Zeilen 19–26: dasselbe gilt für [Zone 3];
- Zeilen 28–30: Wenn [data.saisies!=null] vorhanden ist, wird der Bereich [Saisies] neu generiert. In der vorliegenden Demonstration haben wir [data.saisies==null], daher bleibt der Bereich [Saisies] unverändert;
- Zeilen 32–37: Analoge Überlegung für das Feld [Erreur] mit folgenden Nuancen:
- Zeile 33: [data.erreur] wird eine Fehlermeldung im Textformat sein;
- Zeile 36: Wenn [data.erreur==null], dann wird das Feld [Erreur] ausgeblendet. Es könnte nämlich bei der vorherigen Abfrage angezeigt worden sein;
Im Falle eines serverseitigen Fehlers (HTTP, Status vom Typ 500 Internal Server Error) wird die folgende Funktion ausgeführt:
// bei Empfang der Antwort vom Server
// im Falle eines Fehlers
function onError(jqXHR) {
console.log("onError");
// Systemfehler
erreur.text(jqXHR.responseText);
erreur.show();
}
Um einen solchen Fehler zu sehen, ändern wir die Funktion [postForm] wie folgt:
function postForm() {
console.log("postForm");
// Referenzen auf der aktuellen Seite werden abgerufen
...
// man führt einen manuellen Ajax-Aufruf durch
$.ajax({
url : '/ajax-10x',
...
})
}
- Zeile 7: Wir fügen ein nicht existierendes URL ein;
Hier sind die Ergebnisse, wenn man auf die Schaltfläche „[Rafraîchir]“ klickt:
![]() |
Interessanterweise wurde auch dieser Fehler in Form einer Zeichenkette „jSON“ gesendet.
Die Methode, die nach dem Empfang der Antwort vom Server ausgeführt wird, lautet wie folgt:
// nach [onSuccess, onError]
function onComplete() {
console.log("onComplete");
// Ladebild
loading.hide();
}
Man beschränkt sich darauf, das animierte Bild für die Wartezeit auszublenden.
7.5.7. Anzeige der Seite [Page 2]
Der Code HTML des Links [Valider] lautet wie folgt:
<a href="javascript:valider()">Valider</a>
Die Funktion JS [valider] lautet wie folgt:
// Überprüfung der eingegebenen Werte
function valider() {
// gesendeter Wert
var post = JSON3.stringify({
"value1" : $("#text1").val().trim(),
"value2" : $("#text2").val().trim()
});
// Manueller Ajax-Aufruf
$.ajax({
url : '/ajax-11A',
headers : {
'Accept: 'application/json',
'Content-Type: 'application/json'
},
type : 'POST',
data : post,
dataType : 'json',
beforeSend : onBegin,
success : onSuccess,
error : onError,
complete : onComplete
})
}
- Zeilen 4–7: Wir haben zwei Werte v1 und v2 zu übermitteln: die der Eingabekomponenten, die durch [#text1] und [#text2] identifiziert werden. Wir werden nun etwas Neues ausprobieren. Wir werden diese beiden Werte in Form einer Zeichenkette jSON {"value1":v1,"value2":v2} übermitteln;
- Zeile 10: Die gesendeten Werte werden an die Aktion [ajax-11A] weitergeleitet;
- Zeile 12: Da wir wissen, dass wir eine Antwort mit dem Namen jSON erhalten werden, geben wir an, dass wir eine Antwort mit dem Namen jSON empfangen können;
- Zeile 13: Wir teilen dem Server mit, dass wir ihm den gesendeten Wert in Form einer Zeichenkette jSON senden werden;
- Zeilen 15–16: Man wandelt den zu sendenden Wert in ein POST um;
- Zeile 17: Wir erhalten jSON;
7.5.8. Die Aktion [ajax-11A]
Die Aktion [ajax-11A], die die gesendete Zeichenfolge jSON verarbeitet, lautet wie folgt:
@RequestMapping(value = "/ajax-11A", method = RequestMethod.POST, consumes = "application/json")
@ResponseBody
public JsonResult10 ajax11A(@RequestBody @Valid PostAjax11A post, BindingResult bindingResult, Locale locale, HttpServletRequest request, HttpServletResponse response) {
...
}
- Zeile 1: Mit ["application/json"] wird angegeben, dass die Aktion ein Dokument im Format jSON erwartet. Dieses Dokument ist der vom Kunden übermittelte Wert;
- Zeile 3: Der übermittelte Wert wird aus dem folgenden Objekt [PostAjax11A post] abgerufen:
![]() |
package istia.st.springmvc.models;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Range;
public class PostAjax11A {
// Daten
@Size(min = 4, max = 6)
@NotNull
private String value1;
@Range(min = 10, max = 14)
@NotNull
private Integer value2;
// Getter und Setter
...
}
- Die Struktur des Objekts [PostAjax11A] muss die Struktur des gesendeten Objekts {"value1":v1,"value2":v2} übernehmen. Daher sind die Felder [value1] (Zeile 13) und [value2] (Zeile 16) erforderlich;
- für beide Felder wurden Integritätsbeschränkungen festgelegt;
Kommen wir zurück zum Code der Aktion [ajax-11A]:
@RequestMapping(value = "/ajax-11A", method = RequestMethod.POST, consumes = "application/json")
@ResponseBody
public JsonResult10 ajax11A(@RequestBody @Valid PostAjax11A post, BindingResult bindingResult, Locale locale, HttpServletRequest request, HttpServletResponse response) {
// Thymeleaf-Kontext
WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
// Antwort
JsonResult10 result = new JsonResult10();
// Ist der POST-Befehl gültig?
if (bindingResult.hasErrors()) {
// Seite 1 wird mit einer Fehlermeldung zurückgegeben
result.setZone1Active(session.isZone1Active());
result.setZone3Active(session.isZone3Active());
result.setErreur(getErreursForModel(bindingResult));
return result;
}
...
}
- Zeile 3: Die Anmerkung [@RequestBody] bezeichnet das vom Kunden gesendete Dokument. Es handelt sich um den Wert, den dieser in jSON übermittelt hat. Dieser wird daher zur Erstellung des Objekts [PostAjax11A] verwendet;
- Zeile 3: Die Anmerkung [@Valid] erzwingt die Validierung des übermittelten Werts;
- Zeile 9: Falls die Validierung fehlschlägt:
- Zeile 13: Es wird eine Fehlermeldung zurückgegeben,
- Zeilen 11–12: Die Felder 1 und 3 werden in ihren ursprünglichen Zustand zurückversetzt (angezeigt oder nicht);
Die Berechnung der Fehlermeldung erfolgt wie folgt:
private String getErreursForModel(BindingResult result) {
StringBuffer buffer = new StringBuffer();
for (FieldError error : result.getFieldErrors()) {
StringBuffer bufferCodes = new StringBuffer("(");
for (String code : error.getCodes()) {
bufferCodes.append(String.format("%s ", code));
}
bufferCodes.append(")");
buffer.append(String.format("[%s:%s:%s:%s]", error.getField(), error.getRejectedValue(), bufferCodes,
error.getDefaultMessage()));
}
return buffer.toString();
}
Diese Funktion ist uns bereits bekannt.
Die Aktion [ajax-11A] wird wie folgt fortgesetzt:
@RequestMapping(value = "/ajax-11A", method = RequestMethod.POST, consumes = "application/json")
@ResponseBody
public JsonResult10 ajax11A(@RequestBody @Valid PostAjax11A post, BindingResult bindingResult, Locale locale, HttpServletRequest request, HttpServletResponse response) {
// Thymeleaf-Kontext
WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
// Antwort
JsonResult10 result = new JsonResult10();
// Gültiger Post?
if (bindingResult.hasErrors()) {
...
}
// Das Eingabefeld wird gespeichert
thymeleafContext.setVariable("value1", post.getValue1());
thymeleafContext.setVariable("value2", post.getValue2());
session.setSaisies(engine.process("vue-09-saisies", thymeleafContext));
// Seite 2 wird gesendet
result.setContent(engine.process("vue-09-page2", thymeleafContext));
return result;
}
- Zeilen 13–14: Die übermittelten Werte werden in den Thymeleaf-Kontext übernommen;
- Zeile 15: Mit diesem Kontext wird die Ansicht [vue-09-saisies] berechnet und in die Sitzung gespeichert, um sie später neu generieren zu können;
- Zeile 17: Seite 2 wird in das Ergebnis eingefügt, das an den Client gesendet wird;
Die Ansicht [vue-09-page2.xml] lautet wie folgt:
![]() |
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h2>Page 2</h2>
<p>
<h4>Valeurs saisies :</h4>
<p>
Chaîne de caractères :
<span th:text="${value1}"></span>
</p>
<p>
Nombre entier :
<span th:text="${value2}"></span>
</p>
<a href="javascript:retourPage1()">Retour à la page 1</a>
</p>
</body>
</html>
- In den Zeilen 9 und 13 werden die Werte von [value1, value2] angezeigt, die die Aktion [/ajax-11A] in den Thymeleaf-Kontext eingefügt hat;
7.5.9. Verarbeitung der Antwort der Aktion [/ajax-11A]
Auf der Client-Seite wird die Antwort der Aktion [/ajax-10] von der Funktion [onSuccess] verarbeitet:
function onSuccess(data) {
console.log("onSuccess");
// Inhalt
if (data.content) {
content.html(data.content);
}
// Feld 1
if (data.zone1Active) {
$("#zone1").show();
if (data.zone1) {
$("#zone1-content").html(data.zone1);
}
} else {
$("#zone1").hide();
}
// Ist Feld 3 aktiv?
if (data.zone3Active) {
$("#zone3").show();
if (data.zone3) {
$("#zone3-content").html(data.zone3);
}
} else {
$("#zone3").hide();
}
// Eingaben?
if (data.saisies) {
$("#saisies").html(data.saisies);
}
// Fehler?
if (data.erreur) {
erreur.text(data.erreur);
erreur.show();
} else {
erreur.hide();
}
}
Wir haben diesen Code bereits kommentiert. Betrachten wir die beiden Fälle: Antwort mit oder ohne Fehler:
Mit Fehler
In diesem Fall hat die Aktion [/ajax-11A] eine Antwort jSON in der Form {"zone1":null, "zone3":null,"saisies":null,"erreur":erreur,"zone1Active":zone1Active,"zone3Active":zone3Active,"content":null}. Wenn man dem obigen Code folgt, sieht man, dass:
- sich das Feld „[content]“ nicht ändert. Es enthielt Seite Nr. 1;
- der Bereich [Erreur] wird angezeigt;
- die Felder [Zone 1], [Zone 3] und [Saisies] bleiben unverändert;
Ohne Fehler
In diesem Fall hat die Aktion [/ajax-11A] eine Antwort jSON in der Form {"Feld1":null, "Feld3":null,"Eingaben":null,"Fehler":null,"zone1Active":false,"zone3Active":false,"content":content}. Wenn man dem obigen Code folgt, sieht man, dass:
- der Bereich [content] angezeigt wird. Er enthält Seite Nr. 2;
Hier sind drei Ausführungsbeispiele:
Ein Fall mit Validierungsfehler:
![]() | ![]() |
Ein Fall mit dem Fehler POST:
![]() | ![]() |
Diese Art von Fehler ist anders. Da Spring die Zeichenfolge jSON nicht in den Typ [PostAjax11A] konvertieren konnte, hat es eine Antwort HTTP mit [status=400] zurückgegeben. Die Aktion [ajax-11A] wurde nicht ausgeführt;
Ein fehlerfreier Fall:
![]() | ![]() |
7.5.10. Zurück zu Seite Nr. 1
Der Link [Retour vers la page 1] auf Seite Nr. 2 lautet wie folgt:
<a href="javascript:retourPage1()">Retour à la page 1</a>
Die Methode JS [retourPage1] lautet wie folgt:
// Zurück zu Seite 1
function retourPage1() {
// Man führt einen Ajax-Aufruf manuell durch
$.ajax({
url : '/ajax-11B',
headers : {
'Accept: 'application/json',
},
type : 'POST',
dataType : 'json',
beforeSend : onBegin,
success : onSuccess,
error : onError,
complete : onComplete
})
}
Sie führt einen Aufruf von POST ohne übermittelten Wert an die Aktion [/ajax-11B] durch.
7.5.11. Die Aktion [/ajax-11B]
Die Aktion [/ajax-11B] lautet wie folgt:
@RequestMapping(value = "/ajax-11B", method = RequestMethod.POST)
@ResponseBody
public JsonResult10 ajax11B(HttpServletRequest request, HttpServletResponse response) {
// Thymeleaf-Kontext
WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
// Antwort
JsonResult10 result = new JsonResult10();
// Seite 1 wird in ihren ursprünglichen Zustand zurückversetzt
result.setContent(engine.process("vue-09-page1", thymeleafContext));
result.setSaisies(session.getSaisies());
result.setZone1(session.getZone1());
result.setZone3(session.getZone3());
result.setZone1Active(session.isZone1Active());
result.setZone3Active(session.isZone3Active());
return result;
}
Die Aktion muss Seite Nr. 1 mit ihren drei Feldern [Zone1, Zone3, Erreur] neu generieren:
- Zeile 9: Seite Nr. 1 wird in das Ergebnis übernommen;
- Zeile 10: Der Eingabebereich wird in das Ergebnis aufgenommen;
- Zeile 11: Der Bereich [Zone 1] wird in das Ergebnis aufgenommen;
- Zeile 12: Das Feld [Zone 3] wird in das Ergebnis aufgenommen;
- Zeilen 13–14: Der Status der Felder [Zone 1] und [Zone 3] wird in das Ergebnis aufgenommen;
7.5.12. Verarbeitung der Antwort der Aktion [/ajax-11B]
Die Antwort der Aktion [/ajax-11B] wird von der Funktion [onSuccess] verarbeitet:
function onSuccess(data) {
console.log("onSuccess");
// Inhalt
if (data.content) {
content.html(data.content);
}
// Bereich 1
if (data.zone1Active) {
$("#zone1").show();
if (data.zone1) {
$("#zone1-content").html(data.zone1);
}
} else {
$("#zone1").hide();
}
// Bereich 3 aktiv?
if (data.zone3Active) {
$("#zone3").show();
if (data.zone3) {
$("#zone3-content").html(data.zone3);
}
} else {
$("#zone3").hide();
}
// Eingaben?
if (data.saisies) {
$("#saisies").html(data.saisies);
}
// Fehler?
if (data.erreur) {
erreur.text(data.erreur);
erreur.show();
} else {
erreur.hide();
}
}
Die Aktion [/ajax-11B] hat eine Antwort jSON in der Form {"zone1":zone1, "zone3":zone3,"Eingaben":Eingaben,"Fehler":null,"zone1Active":zone1Active,"zone3Active":zone3Active,"content":content}. Wenn man dem obigen Code folgt, sieht man, dass:
- das Feld „[content]“ geändert wird. Es enthielt zuvor Seite Nr. 2. Es wird nun Seite Nr. 1 enthalten;
- der Bereich [Erreur] wird ausgeblendet;
- die Felder [Zone 1], [Zone 3] und [Saisies] werden in ihrem ursprünglichen Zustand angezeigt;
7.6. Sitzung auf der Client-Seite verwalten
7.6.1. Einleitung
Im vorigen Abschnitt haben wir eine Sitzung verwaltet, deren Struktur wie folgt aussah:
public class SessionModel1 implements Serializable {
// zwei Zähler
private int cpt1 = 0;
private int cpt3 = 0;
// alle drei Zonen
private String zone1 = "xx";
private String zone3 = "zz";
private String saisies;
private boolean zone1Active = true;
private boolean zone3Active = true;
...
}
Bei einer sehr großen Anzahl von Benutzern kann der von den Sitzungen all dieser Benutzer belegte Speicher ein Problem darstellen. Daher gilt die Regel, die Größe des Sitzungsspeichers zu minimieren. Das Modell APU (Ein-Seiten-Anwendung) ermöglicht die clientseitige Verwaltung der Sitzung und den Betrieb eines Webservers ohne Sitzungen. Die einzelne Seite wird nämlich zunächst vom Browser geladen. Zusammen mit ihr wird die dazugehörige JavaScript-Datei geladen. Da kein Seitenneuladen stattfindet, verbleibt diese Datei JS dauerhaft im Browser, so wie sie ursprünglich geladen wurde. Man kann dann ihre globalen Variablen nutzen, um Informationen über die verschiedenen Aktionen des Benutzers zu speichern. Das werden wir nun näher betrachten. Wir werden nicht nur die Sitzung auf der Client-Seite verwalten, sondern auch die Anwendung JS so umgestalten, dass der Server so wenig wie möglich belastet wird.
7.6.2. Die Aktion [/ajax-12]
![]() |
Die Aktion [/ajax-12] lautet wie folgt:
@RequestMapping(value = "/ajax-12", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax12() {
return "vue-12";
}
Die Ansicht [vue-12.xml] sieht wie folgt aus:
![]() |
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-12</title>
<link rel="stylesheet" href="/css/ajax01.css" />
<script type="text/javascript" src="/js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/json3.js"></script>
<script type="text/javascript" src="/js/local12.js"></script>
</head>
<body>
<h3>Ajax - 12 - Navigation dans une Application à Page Unique</h3>
<h3>avec des flux HTML embarqués dans une chaîne jSON</h3>
<h3>et une session gérée par le client JS</h3>
<hr />
<div id="content" th:include="vue-09-page1" />
<img id="loading" src="/images/loading.gif" />
<div id="erreur" style="background-color:lightgrey"></div>
</body>
</html>
- Diese Ansicht ist identisch mit der Ansicht [vue-09], mit dem einzigen Unterschied, dass in Zeile 9 das Skript JS verwendet wird;
Die angezeigte Ansicht lautet wie folgt:
![]() |
7.6.3. Der Code JS zur Verwaltung der Schaltfläche [Rafraîchir]
![]() |
Der Code der Datei [local12.js] lautet wie folgt:
// globale Variablen
var content;
var loading;
var erreur;
var page1;
var page2;
var value1;
var value2;
var session = {
"cpt1" : 0,
"cpt3" : 0
};
// beim Laden des Dokuments
$(document).ready(function() {
// Die Referenzen der verschiedenen Komponenten der Seite werden abgerufen
loading = $("#loading");
loading.hide();
erreur = $("#erreur");
erreur.hide();
content = $("#content");
});
- Zeilen 17–21: Beim Laden der Master-Seite werden die Referenzen der drei durch [loading, erreur, content] identifizierten Komponenten in den globalen Variablen der Zeilen 2–4 gespeichert;
- Zeilen 5–6: zum Speichern der beiden Seiten;
- Zeilen 7–8: zum Speichern der beiden über den Link [Valider] übermittelten Werte;
- Zeile 9: Die Sitzung. Hier werden auf der Client-Seite die Werte der Zähler [cpt1, cpt3] gespeichert;
Die Funktion [postForm] verarbeitet den Klick auf die Schaltfläche [Rafraîchir]:
function postForm() {
console.log("postForm");
// Die Sitzung wird gespeichert
var post = JSON3.stringify(session);
// ein Ajax-Aufruf wird manuell ausgeführt
$.ajax({
url : '/ajax-13',
headers : {
'Accept: 'application/json',
'Content-Type: 'application/json'
},
type : 'POST',
data : post,
dataType : 'json',
beforeSend : onBegin,
success : function(data) {
...
},
error : onError,
complete : onComplete
})
}
Die Unterschiede zur vorherigen Version sind folgende:
- URL in Zeile 7 ist anders;
- Zeile 4: Es wird ein Wert gesendet, während zuvor keiner gesendet wurde. Dieser Wert ist die Zeichenkette jSON der Sitzung. Das Prinzip ist wie folgt:
- Der Client sendet die Sitzung an den Server,
- dieser ändert sie und sendet sie zurück,
- der Client speichert die neue Sitzung;
- Zeile 10: Es wird ein Dokument im Format jSON (gesendeter Wert) gesendet;
- Zeile 13: Es gibt etwas zu versenden;
- Zeilen 15–20: Die Funktionen [beforeSend, error, complete] entsprechen denen der vorherigen Version. Lediglich die Funktion [success] ändert sich (Zeilen 16–18);
7.6.4. Die Aktion [/ajax-13]
![]() |
Die Aktion [/ajax-13] lautet wie folgt:
@RequestMapping(value = "/ajax-13", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
@ResponseBody()
public JsonResult13 ajax13(@RequestBody SessionModel2 session2, HttpServletRequest request, HttpServletResponse response) {
...
}
- Zeile 3: Der Parameter [@RequestBody SessionModel2 session2] ruft die vom Client übermittelte Sitzung ab. Diese hat den folgenden Typ [SessionModel2]:
![]() |
package istia.st.springmvc.models;
import java.io.Serializable;
public class SessionModel2 implements Serializable {
private static final long serialVersionUID = 1L;
// zwei Zähler
private int cpt1 = 0;
private int cpt3 = 0;
// Getter und Setter
...
}
Die Sitzung [SessionModel2] speichert folgende Elemente:
- Zeile 9: die Anzahl der Fälle, in denen [cpt1] das Feld [Zone 1] anzeigt;
- Zeile 10: die Anzahl der Fälle, in denen das Feld [Zone 3] angezeigt wird ([cpt3]);
Setzen wir die Untersuchung des Codes der Aktion [/ajax-13] fort:
@RequestMapping(value = "/ajax-13", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
@ResponseBody()
public JsonResult13 ajax13(@RequestBody SessionModel2 session2, HttpServletRequest request, HttpServletResponse response) {
...
}
- Zeile 3: Der Typ [JsonResult13] der Antwort lautet wie folgt:
![]() |
package istia.st.springmvc.models;
public class JsonResult13 {
// Daten
private String page2;
private String zone1;
private String zone3;
private String erreur;
private String value1;
private Integer value2;
// Sitzung
private SessionModel2 session;
// Getter und Setter
...
}
- Zeile 14: Die Sitzung. Der Server sendet sie zur Speicherung an den Client zurück;
- Zeile 6: Der Inhalt HTML von Seite Nr. 2;
- Zeile 7: der Inhalt HTML des Bereichs [Zone 1];
- Zeile 8: der Inhalt HTML des Bereichs [Zone 3];
- Zeile 9: die eventuelle Fehlermeldung;
- Zeilen 10–11: zwei vom Server berechnete und auf Seite 2 angezeigte Informationen;
Setzen wir die Untersuchung des Codes der Aktion [/ajax-13] fort:
@RequestMapping(value = "/ajax-13", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
@ResponseBody()
public JsonResult13 ajax13(@RequestBody SessionModel2 session2, HttpServletRequest request,
HttpServletResponse response) {
// Thymeleaf-Kontext
WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
// Antwort
JsonResult13 result = new JsonResult13();
result.setSession(session2);
// Es wird eine zufällige Antwort zurückgegeben
int cas = new Random().nextInt(3);
switch (cas) {
case 0:
// Zone 1 aktiv
setZone1B(thymeleafContext, result);
return result;
case 1:
// Zone 3 aktiv
setZone3B(thymeleafContext, result);
return result;
case 2:
// Zonen 1 und 3 aktiv
setZone1B(thymeleafContext, result);
setZone3B(thymeleafContext, result);
return result;
}
return null;
}
- Zeile 9: Die Sitzung wird in das Ergebnis der Aktion aufgenommen;
Die Methode [setZone1B], die das Feld [Zone 1] aktiviert, lautet wie folgt:
private void setZone1B(WebContext thymeleafContext, JsonResult13 result) {
// Die Sitzung wird abgerufen
SessionModel2 session = result.getSession();
// Zone 1 aktiv
// Datenstrom HTML
int cpt1 = session.getCpt1() + 1;
thymeleafContext.setVariable("cpt1", cpt1);
thymeleafContext.setLocale(new Locale("fr", "FR"));
String zone1 = engine.process("vue-09-zone1", thymeleafContext);
result.setZone1(zone1);
// Sitzung
session.setCpt1(cpt1);
}
- Zeile 3: Die Sitzung wird abgerufen. Sie wird in Zeile 12 mit dem neuen Zähler [cpt1] geändert. Es sei daran erinnert, dass diese Sitzung an den Kunden zurückgesendet wird;
- Zeile 10: das neue Feld [Zone 1];
Die Methode [setZone3B], die das Feld [Zone 3] aktiviert, funktioniert analog:
private void setZone3B(WebContext thymeleafContext, JsonResult13 result) {
// Sitzung wird abgerufen
SessionModel2 session = result.getSession();
// Zone 3 aktiv
// Datenfluss HTML
int cpt3 = session.getCpt3() + 1;
thymeleafContext.setVariable("cpt3", cpt3);
thymeleafContext.setLocale(new Locale("en", "US"));
String zone3 = engine.process("vue-09-zone3", thymeleafContext);
result.setZone3(zone3);
// Sitzung
session.setCpt3(cpt3);
}
7.6.5. Verarbeitung der Antwort der Aktion [/ajax-13]
Auf der Client-Seite wird die Antwort jSON der Aktion [/ajax-13] durch die folgende Funktion [onSuccess] verarbeitet:
function postForm() {
console.log("postForm");
// die Sitzung wird gesendet
var post = JSON3.stringify(session);
// man führt manuell einen Ajax-Aufruf durch
$.ajax({
...
success : function(data) {
// Die Sitzung wird gespeichert
session = data.session;
// die beiden Felder werden aktualisiert
if (data.zone1) {
$("#zone1-content").html(data.zone1);
$("#zone1").show();
} else {
$("#zone1").hide();
}
if (data.zone3) {
$("#zone3").show();
$("#zone3-content").html(data.zone3);
} else {
$("#zone3").hide();
}
},
...
})
}
- Zeilen 12–17: Wenn der Server etwas in das Feld [zone1] der Antwort eingetragen hat, muss das Feld [Zone 1] neu generiert und angezeigt werden, andernfalls muss es ausgeblendet werden;
- Zeilen 18–23: Gleiche Vorgehensweise für den Bereich [Zone 3];
7.6.6. Anzeige der Seite [Page 2]
Der Code HTML des Links [Valider] lautet wie folgt:
<a href="javascript:valider()">Valider</a>
Die Funktion JS [valider] lautet wie folgt:
// Validierung der eingegebenen Werte
function valider() {
// Seite 1 wird gespeichert
page1 = content.html();
// die eingegebenen Werte werden gespeichert
value1 = $("#text1").val().trim();
value2 = $("#text2").val().trim();
// Wert gebucht
var post = JSON3.stringify({
"value1" : value1,
"value2" : value2,
"pageRequired" : page2 ? false : true
});
// Wir führen manuell einen Ajax-Aufruf durch
$.ajax({
url : '/ajax-14',
headers : {
'Accept: 'application/json',
'„Content-Type“: „application/json“
},
type : 'POST',
data : post,
dataType : 'json',
beforeSend : onBegin,
success : function(data) {
...
},
error : onError,
complete : onComplete
})
}
- Wir führen nun einen Aufruf von POST durch, der uns normalerweise auf Seite Nr. 2 weiterleitet;
- Zeile 4: Wir speichern Seite Nr. 1, um später dorthin zurückkehren zu können;
- Zeilen 6–7: Der vorherige Vorgang speichert nicht die eingegebenen Werte, sondern nur den Code HTML der Seite. Daher speichern wir nun die beiden im Formular eingegebenen Werte;
- Zeilen 9–13: Die beiden eingegebenen Werte werden in eine Zeichenkette jSON eingefügt. Diese wird dann gesendet;
- Zeile 12: Ein Parameter, um dem Server mitzuteilen, ob Seite Nr. 2 benötigt wird. Wir gehen wie folgt vor: Wir rufen Seite Nr. 2 zunächst einmal ab und speichern sie dann in der Variablen JS [page2]. Danach rufen wir sie nicht mehr erneut ab. Wir werden die im Cache gespeicherte Seite verwenden. In Zeile 2 ist [pageRequired] gleich [true], wenn die Variable [page2] leer ist, andernfalls gleich [false];
- Es ist zu beachten, dass die Sitzung nicht gesendet wird. Diese speichert nämlich Zähler, die von der Aktion [/ajax-14] in Zeile 20 nicht verändert werden;
7.6.7. Die Aktion [/ajax-14]
Die Aktion [/ajax-14] lautet wie folgt:
@RequestMapping(value = "/ajax-14", method = RequestMethod.POST)
@ResponseBody
public JsonResult13 ajax14(@RequestBody @Valid PostAjax14 post, BindingResult bindingResult, Locale locale, HttpServletRequest request, HttpServletResponse response) {
...
}
- Zeile 3: Die Antwort ist immer vom Typ [JsonResult13];
- Zeile 3: Der übermittelte Wert wird in den folgenden Typ „[PostAjax14]“ gekapselt:
package istia.st.springmvc.models;
public class PostAjax14 extends PostAjax11A {
// Seite 2
private boolean pageRequired;
// Getter und Setter
...
}
- Zeile 3: Die Klasse [PostAjax14] erweitert die Klasse [PostAjax11A] aus der vorherigen Version. Sie hat daher die Struktur [value1, value2, pageRequired];
Die Aktion [/ajax-14] wird wie folgt fortgesetzt:
@RequestMapping(value = "/ajax-14", method = RequestMethod.POST)
@ResponseBody
public JsonResult13 ajax14(@RequestBody @Valid PostAjax14 post, BindingResult bindingResult, Locale locale, HttpServletRequest request, HttpServletResponse response) {
// Thymeleaf-Kontext
WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
// Antwort
JsonResult13 result = new JsonResult13();
// Ist der POST-Befehl gültig?
if (bindingResult.hasErrors()) {
// Es wird ein Fehler zurückgegeben
result.setErreur(getErreursForModel(bindingResult));
return result;
}
// Seite 2 wird gesendet
result.setValue1(post.getValue1());
result.setValue2(post.getValue2());
// Seite angefordert?
if (post.isPageRequired()) {
result.setPage2(engine.process("vue-12-page2", thymeleafContext));
}
return result;
}
- Zeilen 9–13: Sind die übermittelten Werte von [value1, value2] ungültig, wird eine Fehlermeldung zurückgegeben;
- Zeilen 15–16: Normalerweise sollte der Server eine Berechnung mit den übermittelten Werten durchführen. Hier gibt er sie lediglich zurück, um zu zeigen, dass er sie ordnungsgemäß empfangen hat;
- Zeilen 18–20: Seite Nr. 2 wird nur zurückgesendet, wenn sie vom Client angefordert wurde. Zeile 19: Die Ansicht [vue-12-page2] ist neu:
![]() |
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h2>Page 2</h2>
<p>
<h4>Valeurs saisies :</h4>
<p>
Chaîne de caractères :
<span id="value1"></span>
</p>
<p>
Nombre entier :
<span id="value2"></span>
</p>
<a href="javascript:retourPage1()">Retour à la page 1</a>
</p>
</body>
</html>
- Der Code XML enthält keine von Thymeleaf ausgewerteten Werte mehr, wie es zuvor der Fall war;
- Es wurden die Stellen identifiziert, an denen die vom Server zurückgegebenen Werte [value1, value2] eingefügt werden sollen. Zeile 9: [id='value1'] gibt die Stelle an, an der [value1] eingefügt werden soll. Zeile 13, dasselbe gilt für [value2];
7.6.8. Verarbeitung der Antwort der Aktion [/ajax-14]
Die Antwort der Aktion [/ajax-14] wird von der folgenden Funktion [success] verarbeitet:
// Validierung der eingegebenen Werte
function valider() {
...
// Es wird manuell ein Ajax-Aufruf durchgeführt
$.ajax({
...
success : function(data) {
// Fehler?
if (data.erreur) {
// Fehleranzeige
erreur.html(data.erreur);
erreur.show();
} else {
// kein Fehler
erreur.hide();
// Seite 2
if (page2) {
// Die Seite wird aus dem Cache verwendet
content.html(page2);
} else {
// Seite 2 wird gespeichert
page2 = data.page2;
// wird angezeigt
content.html(data.page2);
}
// die Seite wird mit den Informationen vom Server aktualisiert
$("#value1").text(data.value1);
$("#value2").text(data.value2);
}
},
...
})
}
- Zeilen 9–13: Wenn der Server einen Fehler zurückgegeben hat, wird dieser angezeigt;
- Zeilen 14–29: Der Fall, dass kein Fehler aufgetreten ist. In diesem Fall muss Seite Nr. 2 angezeigt werden;
- Zeile 17: Es wird geprüft, ob Seite Nr. 2 bereits in der Variablen [page2] gespeichert ist;
- Zeile 19: In diesem Fall wird die Variable [page2] verwendet, um Seite Nr. 2 anzuzeigen;
- Zeile 24: Andernfalls wird das vom Server bereitgestellte Feld [data.page2] verwendet;
- Zeile 22: Die Seite Nr. 2 wird gespeichert, damit sie später nicht erneut angefordert werden muss;
- Zeilen 27–28: Auf Seite Nr. 2 werden die beiden vom Server gesendeten Informationen [value1, value2] angezeigt;
7.6.9. Zurück zu Seite Nr. 1
Der Link [Retour vers la page 1] auf Seite Nr. 2 lautet wie folgt:
<a href="javascript:retourPage1()">Retour à la page 1</a>
Die Methode JS [retourPage1] lautet wie folgt:
// Zurück zu Seite 1
function retourPage1() {
// Seite 1 wird neu generiert
content.html(page1);
// die Eingaben werden neu generiert
$("#text1").val(value1);
$("#text2").val(value2);
}
- Es handelt sich um eine Aktion JS ohne Interaktion mit dem Server, da Seite Nr. 1 lokal in der Variablen [page1] gespeichert wurde;
- Zeile 4: Seite Nr. 1 wird neu generiert;
- Zeile 6–7: Nur der Teil HTML von Seite Nr. 1 wurde gespeichert. Nicht die Eingaben. Diese müssen daher neu generiert werden;
7.6.10. Fazit
Durch die Nutzung der Möglichkeiten des Modells APU ist es uns gelungen, den Webserver zu vereinfachen, der nun zustandslos ist (keine Sitzung) und weniger belastet wird:
- Wir haben die Interaktion mit dem Server in der Funktion JS ([retourPage1]) entfernt;
- der Server generiert Seite Nr. 2 nur einmal;
7.7. Strukturierung des JavaScript-Codes in Schichten
7.7.1. Einleitung
Der JavaScript-Code der bisherigen Anwendung wird allmählich komplex. Es ist an der Zeit, ihn in Schichten zu strukturieren. Die Anwendung bleibt unverändert. Wir werden den Server nicht verändern, außer um eine neue Startseite zu definieren. Wir werden den Code JS überarbeiten.
Die neue Architektur sieht wie folgt aus:
![]() |
7.7.2. Die Startseite
Die Aktion, die die Anwendung startet, ist die folgende Aktion [/ajax-16]:
@RequestMapping(value = "/ajax-16", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
public String ajax16() {
return "vue-16";
}
Sie zeigt die folgende Ansicht „[vue-16.xml]“ an:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-12</title>
<link rel="stylesheet" href="/css/ajax01.css" />
<script type="text/javascript" src="/js/jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/json3.js"></script>
<script type="text/javascript" src="/js/local16-dao.js"></script>
<script type="text/javascript" src="/js/local16-ui.js"></script>
</head>
<body>
<h3>Ajax - 16 - Navigation dans une Application à Page Unique</h3>
<h3>Structuration du code JS</h3>
<hr />
<div id="content" th:include="vue-09-page1" />
<img id="loading" src="/images/loading.gif" />
<div id="erreur" style="background-color:lightgrey"></div>
</body>
</html>
- Zeilen 9–10: Der Code JS wurde in zwei verschiedene Dateien eingefügt:
- [local-ui] implementiert die Schicht [présentation],
- [local-dao] implementiert die Schicht [DAO];
![]() |
7.7.3. Implementierung der Schicht [DAO]
![]() |
7.7.4. Schnittstelle
Die Schicht [DAO] in [local-dao.js] stellt der Schicht [présentation] die folgende Schnittstelle zur Verfügung:
| um Seite 1 mit der Schaltfläche [Rafraîchir] zu aktualisieren |
| um Seite 2 über die Schaltfläche [Valider] anzuzeigen |
JavaScript kennt keinen Begriff wie „Schnittstelle“. Ich habe diesen Begriff lediglich verwendet, um darauf hinzuweisen, dass die Ebene [présentation] sich verpflichtet, mit der Ebene [DAO] ausschließlich über die beiden vorgenannten Funktionen zu kommunizieren.
7.7.5. Implementierung der Schnittstelle
Das Grundgerüst der Implementierung sieht wie folgt aus:
var session = {
"cpt1" : 0,
"cpt3" : 0
};
// Seite 1 aktualisieren
function updatePage1(deferred, sendMeBack) {
...
}
// Seite 2
function getPage2(deferred, sendMeBack, value1, value2, pageRequired) {
...
}
Der Zweck der Schicht [DAO] besteht darin, die Details der an den Webserver gerichteten Anfragen HTTP vor der Schicht [présentation] zu verbergen. Die Sitzung gehört zu diesen Details. Sie wird daher nun von der Schicht [DAO] verwaltet.
7.7.5.1. Die Funktion [updatePage1]
Die Funktion [updatePage1] ist die Funktion, die von der Schicht [présentation] aufgerufen wird, um Seite 1 zu aktualisieren. Ihr Code lautet wie folgt:
// Seite 1 aktualisieren
function updatePage1(deferred, sendMeBack) {
// Abfrage HTTP
executePost(deferred, sendMeBack, '/ajax-13', session);
}
- Zeile 1: Die Funktion [updatePage1] erhält zwei Parameter:
- ein Objekt vom Typ [jQuery.Deferred]. Dieser Objekttyp speichert einen Status, der drei Werte annehmen kann: ['pending', 'resolved', 'rejected']. Wenn es in die Funktion [updatePage1] gelangt, befindet es sich im Zustand [pending];
- ein Objekt vom Typ JS, das in die Schicht [présentation] zurückgegeben werden soll;
Alle Abfragen HTTP werden von der folgenden Funktion [executePost] ausgeführt:
// Abfrage HTTP
function executePost(deferred, sendMeBack, url, post) {
// Man führt einen Ajax-Aufruf manuell durch
$.ajax({
headers : {
'„Accept“: „application/json“,
'„Content-Type“: „application/json“
},
url : url,
type : 'POST',
data : JSON3.stringify(post),
dataType : 'json',
success : function(data) {
// Die Sitzung wird gespeichert
if (data.session) {
session = data.session;
}
// Das Ergebnis wird ausgegeben
deferred.resolve({
"status" : 1,
"data" : data,
"sendMeBack" : sendMeBack
});
},
error : function(jqXHR) {
// Der Fehler wird ausgegeben
deferred.resolve({
"status" : 2,
"data" : jqXHR.responseText,
"sendMeBack" : sendMeBack
});
}
});
}
- Zeile 1: Die Funktion [executePost] führt einen Ajax-Aufruf vom Typ POST aus. Sie erwartet vier Parameter:
- ein Objekt vom Typ [jQuery.Deferred] im Status [pending];
- ein Objekt vom Typ JS, das in der Schicht [présentation] zurückgegeben werden soll;
- das URL des POST;
- den Wert, der als Objekt JS gebucht werden soll;
- Zeilen 5–8: Die Funktion „poste“ von jSON (Zeile 7) und erhält von jSON (Zeile 6);
- Zeile 11: Der zu übermittelnde Wert wird in jSON umgewandelt;
- Zeilen 13–24: Die Funktion, die bei erfolgreichem Ajax-Aufruf ausgeführt wird;
- Zeilen 19–23: Wenn der Server eine Sitzung zurückgegeben hat, wird diese gespeichert;
- Zeilen 13–18: Das Objekt [deferred] wird in den Status [resolved] versetzt, wobei zusätzlich ein Ergebnis mit den folgenden Feldern übergeben wird:
- [status]: 1 bei Erfolg, 2 bei Fehlschlag,
- [data]: die Antwort jSON des Servers,
- [sendMeBack]: der zweite Parameter der Funktion, bei dem es sich um ein Objekt handelt, das der Aufrufer abrufen möchte;
- Zeilen 17–31: Die Funktion, die bei einem Fehlschlag des Ajax-Aufrufs ausgeführt wird. Es wird dasselbe wie zuvor durchgeführt, mit zwei Unterschieden:
- [status] wird auf 2 gesetzt, um einen Fehler zu melden;
- [data] ist wiederum die Antwort jSON des Servers, die jedoch auf andere Weise abgerufen wurde;
7.7.5.2. Die Funktion [getPage2]
Die Funktion [getPage2] lautet wie folgt:
// Seite 2
function getPage2(deferred, sendMeBack, value1, value2, pageRequired) {
// Anfrage HTTP
executePost(deferred, sendMeBack, '/ajax-14', {
"value1" : value1,
"value2" : value2,
"pageRequired" : pageRequired,
});
}
- Die Funktion erhält folgende Parameter:
- [deferred]: ein Objekt vom Typ [jQuery.Deferred] im Status [pending],
- [sendMeBack]: ein Objekt vom Typ JS, das in die Schicht [présentation] zurückgegeben werden soll,
- [value1]: der erste Eintrag auf Seite 1,
- [value2]: der zweite Eintrag auf Seite 2,
- [pageRequired]: ein boolescher Wert, der dem Server mitteilt, ob er den Datenstrom HTML von Seite Nr. 2 senden soll oder nicht;
- die Funktion [executePost] wird aufgerufen, um die erforderliche Abfrage HTTP auszuführen;
7.7.6. Die Schicht [présentation]
![]() |
Die Schicht [présentation] wird durch die Datei [local-ui.js] implementiert. Letztere übernimmt den Code der Datei [local12.js], der so angepasst wurde, dass er die vorherige Schicht [DAO] nutzt. Es ändern sich lediglich zwei Funktionen: [postForm] und [valider].
7.7.6.1. Die Funktion [postForm]
Die Funktion [postForm] lautet wie folgt:
// Seite 1 aktualisieren
function postForm() {
// Seite 1 wird aktualisiert
var deferred = $.Deferred();
loading.show();
updatePage1(deferred, {
'Absender: „postForm“,
'Info: 10
});
// Anzeige der Ergebnisse
deferred.done(postFormDone);
}
- Zeile 4: Es wird ein Objekt [jQuery.Deferred] angelegt. Standardmäßig befindet es sich im Status [pending];
- Zeile 5: Das Ladebild wird angezeigt
- Zeilen 6–9: Die Funktion [updatePage1] wird ausgeführt. Es wird ein fiktives Objekt [sendMeBack] übergeben, nur um zu zeigen, wozu dies dienen kann;
- Zeile 11: Der Parameter der Funktion [deferred.done] ist selbst eine Funktion. Dies ist die Funktion, die ausgeführt werden soll, wenn der Zustand des Objekts [deferred] in den Zustand [resolved] wechselt. Wir haben gerade gesehen, dass die Funktion DAO [executePost] den Status dieses Objekts bei Empfang der Antwort vom Server an [resolved] übergibt. Das bedeutet, dass bei Ausführung der Funktion [postFormDone] die Antwort des Servers bereits empfangen wurde;
Die Funktion [postFormDone] lautet wie folgt:
function postFormDone(result) {
// Wartezeit beendet
loading.hide();
// Daten werden abgerufen
var data = result.data
// für die Demo
console.log(JSON3.stringify(result.sendMeBack));
// Status wird analysiert
switch (result.status) {
case 1:
// Beide Felder werden aktualisiert
if (data.zone1) {
$("#zone1-content").html(data.zone1);
$("#zone1").show();
} else {
$("#zone1").hide();
}
if (data.zone3) {
$("#zone3").show();
$("#zone3-content").html(data.zone3);
} else {
$("#zone3").hide();
}
break;
case 2:
// Fehleranzeige
erreur.html(data);
break;
}
}
- Zeile 1: Der empfangene Parameter [result] ist der Parameter, der in der Funktion [executePost] an die Methode [deferred.resolve] übergeben wurde, zum Beispiel:
// das Ergebnis wird ausgegeben
deferred.resolve({
"status" : 1,
"data" : data,
"sendMeBack" : sendMeBack
});
- Zeile 5: Die Antwort vom Server wird abgerufen;
- Zeilen 10–24: Hier befindet sich der Code, der in der vorherigen Version in der Funktion [onSuccess] der Funktion [postForm] enthalten war;
- Zeilen 25–28: Hier befindet sich der Code, der in der vorherigen Version in der Funktion [onError] der Funktion [postForm] enthalten war;
7.7.6.2. Die Rolle des Parameters [sendMeBack]
Wozu dient der Parameter [sendMeBack]? Sehen wir uns den Aufrufcode der Funktion [updatePage1] an:
// Seite 1 aktualisieren
function postForm() {
// Seite 1 wird aktualisiert
var deferred = $.Deferred();
loading.show();
updatePage1(deferred, {
'Absender: „postForm“,
'Info: 10
});
// Anzeige der Ergebnisse
deferred.done(postFormDone);
}
und die Signatur der Funktion [validerDone]:
function postFormDone(result) {
}
Wie kann die Funktion [postForm] Informationen an die Funktion [postFormDone] übergeben? Letztere hat nur einen Parameter: [result]. Dieser wird von der Funktion [executePost] der Ebene [DAO] angelegt. Um Informationen an die Funktion [postFormDone] zu übergeben, muss die Funktion [postForm] diese zunächst an die Funktion [updatePage1] übergeben. Dies ist die Aufgabe des Parameters [sendMeBack]. Er wird wie folgt verwendet:
function postFormDone(result) {
// Wartezeit beendet
loading.hide();
// Daten werden abgerufen
var data = result.data
// für die Demo
console.log(JSON3.stringify(result.sendMeBack));
// Status wird analysiert
switch (result.status) {
...
- Zeile 7, hat die Funktion [postFormDone] den Parameter [sendMeBack] wiedergefunden, der ursprünglich von der Funktion [postForm] an die Funktion DAO [updatePage1] übergeben wurde;
7.7.7. Die Funktion [valider]
Die Funktion [valider] lautet wie folgt:
// Eingabewerte werden überprüft
function valider() {
// Seite 1 wird gespeichert
page1 = content.html();
// die eingegebenen Werte werden gespeichert
value1 = $("#text1").val().trim();
value2 = $("#text2").val().trim();
// kein Fehler
erreur.hide();
// Seite 2 wird angefordert
var deferred = $.Deferred();
loading.show();
getPage2(deferred, {
'„Sender“: „Bestätigen“,
'„Info“: 20
}, value1, value2, page2 ? false : true);
// Anzeige der Ergebnisse
deferred.done(validerDone);
}
und die Funktion [validerDone] (Zeile 18) lautet wie folgt:
function validerDone(result) {
// Wartezeit beendet
loading.hide();
// Daten werden abgerufen
var data = result.data
// für die Demo
console.log(JSON3.stringify(result.sendMeBack));
// Status wird analysiert
switch (result.status) {
case 1:
// Fehler?
if (data.erreur) {
// Fehleranzeige
erreur.html(data.erreur);
erreur.show();
} else {
// kein Fehler
erreur.hide();
// Seite 2
if (page2) {
// Seite aus dem Cache wird verwendet
content.html(page2);
} else {
// Seite 2 wird gespeichert
page2 = data.page2;
// wird angezeigt
content.html(data.page2);
}
// die Seite wird mit den Informationen vom Server aktualisiert
$("#value1").text(data.value1);
$("#value2").text(data.value2);
}
break;
case 2:
// Fehlermeldung
erreur.html(data);
erreur.show();
break;
}
}
- Zeile 5: Die Antwort vom Server wird abgerufen;
- Zeilen 10–32: Hier befindet sich der Code, der in der vorherigen Version in der Funktion [onSuccess] der Funktion [valider] enthalten war;
- Zeilen 34–38: Hier befindet sich der Code, der in der vorherigen Version in der Funktion [onError] der Funktion [valider] enthalten war;
7.7.8. Tests
Die Anwendung funktioniert weiterhin wie bisher, und in der Chrome-Konsole sind die Parameter [sendMeBack] der Funktionen [postForm] und [valider] zu sehen:
![]() |
7.8. Conclusion
Kehren wir zum allgemeinen Schema einer Spring-Anwendung MVC zurück:
![]() |
Dank des in die Seiten eingebetteten und im Browser ausgeführten JavaScripts sowie dank des Modells APU lässt sich Code in den Browser auslagern, wodurch folgende Architektur entsteht:
![]() |
- Es handelt sich um eine Client-[2]-/Server-[1]-Architektur, bei der Client und Server über jSON kommunizieren;
- In [1] liefert die Spring-Webschicht MVC Ansichten, Ansichtsfragmente und Daten in jSON;
- in [2]: Der beim Start der Anwendung geladenen Ansicht eingebettete JavaScript-Code kann in Schichten strukturiert werden:
- Die Schicht [présentation] kümmert sich um die Interaktionen mit dem Benutzer,
- die Ebene [DAO] ist für den Datenzugriff über den Webserver [1] zuständig,
- die Schicht [métier] existiert möglicherweise nicht oder übernimmt bestimmte nicht vertrauliche Funktionen der Serverschicht [métier], um diesen zu entlasten;
- der Client [2] kann bestimmte Ansichten zwischenspeichern, um auch hier den Server zu entlasten. Er verwaltet die Sitzung;













































































