Skip to content

7. Bir Spring uygulamasının Ajax ile uyarlanması: MVC

7.1. AJAX'in bir web uygulamasındaki yeri

Şu ana kadar incelenen öğrenme örnekleri aşağıdaki mimariye sahipti:

[Vue1] görünümünden [Vue2] görünümüne geçmek için tarayıcı:

  • web uygulamasına bir istek gönderir;
  • [Vue2] görünümünü alır ve bunu [Vue1] görünümünün yerine görüntüler.

Bu klasik şemadır:

  • tarayıcıdan gelen istek;
  • web sunucusu tarafından müşteriye yanıt olarak bir görünümün oluşturulması;
  • tarayıcı tarafından bu yeni görünümün görüntülenmesi.

Son birkaç yıldır tarayıcı ile web sunucusu arasında başka bir etkileşim biçimi daha mevcuttur: AJAX (Asynchronous Javascript And Xml). Bu aslında tarayıcı tarafından görüntülenen görünüm ile web sunucusu arasındaki etkileşimlerdir. Tarayıcı, bildiği işi yapmaya devam eder, yani bir HTML görünümü görüntüler; ancak artık görüntülenen HTML görünümüne gömülü JavaScript tarafından kontrol edilir. Şema şu şekildedir:

  • [1]'te, tarayıcıda görüntülenen sayfada bir olay meydana gelir (bir düğmeye tıklama, metin değişikliği vb.). Bu olay, sayfaya gömülü JavaScript (jS) tarafından yakalanır;
  • [2] aşamasında, JavaScript kodu, tarayıcının yapacağı gibi HTTP isteğini gönderir. İstek asenkron olarak gerçekleştirilir: kullanıcı, HTTP isteğine yanıt beklerken engellenmeden sayfayla etkileşime devam edebilir. İstek, klasik işleme sürecini izler. Bu isteği klasik bir istekten ayıran hiçbir (veya çok az) fark yoktur;
  • [3]'te, jS istemcisine bir yanıt gönderilir. Tam bir HTML görünümü yerine, daha çok kısmi bir HTML görünümü, bir XML veya jSON (JavaScript Nesne Notasyonu) gönderilir;
  • [4]'te, JavaScript bu yanıtı alır ve görüntülenen HTML sayfasının bir bölümünü güncellemek için kullanır.

Kullanıcı açısından, gördüğü içerik değiştiği için bir görünüm değişikliği söz konusudur. Ancak sayfanın tamamen yeniden yüklenmesi söz konusu değildir; sadece görüntülenen sayfanın kısmi bir değişikliği gerçekleşir. Bu, sayfaya akıcılık ve etkileşim kazandırır: sayfanın tamamen yeniden yüklenmemesi sayesinde, daha önce yönetilemeyen olayları yönetmek mümkün hale gelir. Örneğin, kullanıcı bir giriş kutusuna karakterler girdikçe ona bir seçenek listesi sunmak gibi. Her yeni karakter girildiğinde, sunucuya bir AJAX isteği gönderilir ve sunucu da başka öneriler gönderir. Ajax olmadan, bu tür bir giriş yardımı eskiden imkansızdı. Her karakter girildiğinde yeni bir sayfa yeniden yüklenemezdi.

7.2. HTML akışıyla bir sayfanın güncellenmesi

7.2.1. Görünümler

Aşağıdaki uygulamayı inceleyeceğiz:

  • [1]'te, sayfanın yüklenme saati;
  • [2]'te, A ve B adlı iki gerçek sayı üzerinde dört temel aritmetik işlem gerçekleştirilir;
  • [3]'te, sunucunun yanıtı sayfanın belirli bir bölgesine yazılır;
  • [4]'te, hesaplama zamanı. Bu, sayfanın yüklenme zamanı olan [5]'ten farklıdır. Sayfanın yükleme zamanı [1] değerine eşittir; bu da [6] bölgesinin yeniden yüklenmediğini gösterir. Ayrıca, sayfanın URL ve [7] değerleri değişmemiştir.

7.2.2. [/ajax-01] eylemi

  

[Ajax.java] denetleyicisi, aşağıdaki [/ajax-01] eylemini tanımlar:


    @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) {
        // zaman aralığı geçerli mi?
        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));
            }
        }
        // [vue-01] görünüm şablonu hazırlanıyor
        ...
}
  • 2. satır: [/ajax-01] eylemi yalnızca tek bir parametre olan [tempo]'i kabul eder. Bu, sunucunun aritmetik işlemlerin sonuçlarını göndermeden önce beklemesi gereken süreyi milisaniye cinsinden belirtir;
  • 4. satır: [tempo] parametresi isteğe bağlıdır;
  • satır 5-12: [tempo] parametresinin değerinin geçerli olup olmadığı kontrol edilir;
  • satır 13-15: Eğer öyleyse, gecikme süresi değeri oturuma kaydedilir. Bu, değiştirilmediği sürece geçerli olacağı anlamına gelir;

[/ajax-01] eyleminin kodu şu şekilde devam eder:


    @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) {
        // zaman aralığı geçerli mi?
...
        // [vue-01] görünüm şablonu hazırlanıyor
        modèle.addAttribute("actionModel01", new ActionModel01());
...
        // görünüm
        return "vue-01";
}

[ActionModel01] sınıfı, esas olarak [/ajax-01] eylemi tarafından gönderilen değerleri kapsüllemek için kullanılır. Burada gönderilen hiçbir şey yoktur. [vue-01.xml] görünümü bu sınıfı kullandığı için, modele eklemek üzere boş bir sınıf oluşturulur. [ActionModel01] sınıfı şu şekildedir:


package istia.st.springmvc.models;

import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;

public class ActionModel01 {

    // gönderilen veriler
    @NotNull
    @DecimalMin(value = "0.0")
    private Double a;

    @NotNull
    @DecimalMin(value = "0.0")
    private Double b;

    // alıcı ve ayarlayıcılar
    ...
}
  • 11. ve 15. satırlar: bir form aracılığıyla gönderilecek iki [a,b] tabanlı gerçek sayı;

Yine eylem koduna dönelim:


    @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) {
...
        // [vue-01] görünüm şablonu hazırlanıyor
        modèle.addAttribute("actionModel01", new ActionModel01());
        Resultats résultats = new Resultats();
        modèle.addAttribute("resultats", résultats);
...
        // görünüm
        return "vue-01";
}
  • 6-7. satırlar: Şablona [Resultats] türünde bir örnek eklenir;

Modele eklenen [Resultats] türü şöyledir:

  

package istia.st.springmvc.models;

public class Resultats {

    // veriler
    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 ve setter'lar
    ...
}
  • 6-9. satırlar: [a,b] sayıları üzerinde yapılan dört aritmetik işlemin sonucu;
  • 10. satır: Sayfanın ilk yüklendiği saat;
  • 11. satır: Dört aritmetik işlemin gerçekleştirildiği saat;
  • 12. satır: olası bir hata mesajı;
  • 13. satır: Görüntülenecek olası görünüm;
  • satır 14: görünümün kültürü, [fr-FR] veya [en-US];

[/ajax-01] eylem kodunun devamı şöyledir:


    @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) {
        ...
        // yerel ayar
        setLocale(locale, modèle, résultats);
...
}
  • 5. satır: [setLocale] yöntemi, kullanılacak kültürü ([fr-FR] veya [en-US]) görünüm şablonuna eklemek için kullanılır. Bu kültür, görünüme gömülü JavaScript için kullanılır;

[setLocale] yöntemi şu şekildedir:


    private void setLocale(Locale locale, Model modèle, Resultats résultats) {
        // sadece fr-FR ve en-US yerel ayarları desteklenir
        String language = locale.getLanguage();
        String country = null;
        switch (language) {
        case "fr":
            country = "FR";
            break;
        default:
            language = "en";
            country = "US";
            break;
        }
        // kültür
        résultats.setCulture(String.format("%s-%s", language, country));
}

Şablonda, 'fr-FR' veya 'en-US' değerine eşit olan [${resultats.culture}] dizesi bulunur.

[/ajax-01] eylemine geri dönelim:


@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) {
...
        // yerel ayar
        setLocale(locale, modèle, résultats);
        // saat
        résultats.setHeureGet(new SimpleDateFormat("hh:mm:ss").format(new Date()));
        // görünüm
        return "vue-01";
    }
  • 7. satır: GET'in saatini şablona ekliyoruz;
  • 9. satır: [vue-01.xml] görünümünü görüntüleriz:

7.2.3. [vue-01.xml] görünümü

[vue-01.xml] görünümü şöyledir:


<!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>
  • 7-12. satırlar: jQuery doğrulama ve uluslararasılaştırma (kültürler) kütüphaneleri;
  • 15. satır: 6.3. paragrafta oluşturulan [client-validation] kütüphanesi;
  • 14. satır: [client-validation] kütüphanesi tarafından kullanılan jSON kütüphanesi. Doğrulama günlükleri devre dışı bırakılmışsa bu kütüphane isteğe bağlıdır;
  • 13. satır: Microsoft'un [Unobtrusive Ajax] kütüphanesi. Bu kütüphane, bazen JavaScript yazma ihtiyacını ortadan kaldırır;
  • 16. satır: Kendi ihtiyaçlarımız için bir jS dosyası;
  • satır 17-22: istemci tarafında [fr-FR] ve [en-US] kültürlerini yönetmek için. Bu kodla daha önce karşılaşmıştık;
  • 27. satır: parametreli bir mesaj. Bunları 5.18. paragrafta incelemiştik;
  • satır 36-38: daha sonra tekrar ele alacağımız form;
  • 40. satır: JavaScript'in sunucunun yanıtını yerleştireceği belge alanı;

7.2.4. Form

 

[vue-01.xml] görünümünde form şu şekildedir:


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

ve bu, aşağıdaki HTML'i üretir:


<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>
  • 16. satır: [a] alanına [required], [number] ve [min] doğrulayıcıları atanmıştır;
  • 19. satır: [b] alanı için de durum aynıdır;

Çeşitli mesajlar, projenin [messages.properties] dosyalarında bulunur:

  

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

Şimdi, [form] etiketinin özniteliklerini inceleyelim:


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

[form] etiketinin klasik özniteliklerini görebiliriz:


<form id="formulaire" name="formulaire" method="post" action="/ajax-02.html">

Hemen şunu fark edebiliriz: Sayfayı görüntüleyen tarayıcıda JavaScript devre dışı bırakılmışsa, form URL [/ajax-02.html] adresine gönderilecektir. Şimdi diğer öznitelikleri inceleyelim:


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

[data-ajax-xxx] öznitelikleri, jS [unobtrusive-ajax] kütüphanesi tarafından yönetilir ve bu kütüphane, [vue-01.xml] görünümü tarafından içe aktarılmıştır:


<script type="text/javascript" src="/js/jquery/jquery.unobtrusive-ajax.js"></script>

[data-ajax-xxx] öznitelikleri mevcut olduğunda, formdaki [submit], [unobtrusive-ajax] kütüphanesine yapılan bir Ajax çağrısı ile çalıştırılacaktır. Parametrelerin anlamları şöyledir:

  • [data-ajax="true"]: Bu özniteliğin varlığı, formdaki [submit]'in Ajax ile çalıştırılmasını sağlar;
  • [data-ajax-method="post"]: [submit]'in yöntemi. POST isteğindeki URL, [action="/ajax-02.html"] özniteliğindeki değer olacaktır;
  • [data-ajax-loading="#loading"]: sunucudan yanıt beklenirken görüntülenecek bir alanın kimliği. [vue-01.xml] görünümünde [loading] ile tanımlanan alan şudur:

<img id="loading" style="display: none" src="/images/loading.gif" />

Bu, sunucudan yanıt alınana kadar görüntülenecek bir bekleme animasyonudur;

  • [data-ajax-loading-duration="0"]: [data-ajax-loading="#loading"] alanının görüntülenmesinden önceki bekleme süresi (milisaniye cinsinden). Burada, bekleme başlar başlamaz görüntülenecektir;
  • [data-ajax-begin="beforeSend"]: [submit] işlemi gerçekleştirilmeden önce çalıştırılacak jS işlevi;
  • [data-ajax-complete="afterComplete"]: yanıt alındığında çalıştırılacak jS işlevi;
  • [data-ajax-update="#resultats"]: Sunucu tarafından gönderilen sonucun yerleştirileceği alanın kimliği. [vue-01.xml] görünümü aşağıdaki alana sahiptir:

<div id="resultats" />
  • [data-ajax-mode="replace"]: Sonucun önceki alana eklenme modu. [replace] modu, sonucun [resultats] kimlik numaralı alanda daha önce bulunan içeriği 'üzerine yazmasını' sağlar;

[submit] JavaScript kodunun yalnızca doğrulayıcılar test edilen değerleri geçerli olarak onayladığında çalıştırılacağına dikkat edilmelidir.

jS [unobtrusive-ajax] kütüphanesinin iki amacı vardır:

  • formun, tarayıcıda JavaScript'in etkin olup olmadığına göre her iki duruma da doğru şekilde uyum sağlamasını sağlamak;
  • Javascript yazmaktan kaçınmak. Burada bunun kaçınılmaz olduğunu göreceğiz.

7.2.5. [/ajax-02] eylemi

Gönderilen değerlerin [/ajax-02] eylemine iletildiğini gördük. Bu eylem şöyledir:


@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);
        }
        // bir sonraki görünümün şablonu hazırlanıyor
        Resultats résultats = new Resultats();
        modèle.addAttribute("resultats", résultats);
        // yerel ayarları belirliyoruz
        setLocale(locale, modèle, résultats);
        // saat
        résultats.setHeurePost(new SimpleDateFormat("hh:mm:ss").format(new Date()));
        ...
}
  • öncelikle basitleştirelim: gerçekleşen POST eyleminin, [vue-01.xml] görünümünün JavaScript'i tarafından gerçekleştirildiğini varsayalım. Bu varsayıma biraz sonra tekrar döneceğiz;
  • 2. satır: Gönderilen [a,b] değerleri, [ActionModel01] şablonuna aktarılır;
  • 4-7. satırlar: Kullanıcı önceki bir GET işleminde bir gecikme süresi belirlemişse, bu süre oturumdan alınır ve gecikme uygulanır (6. satır). Bunun amacı, kullanıcının formdaki [data-ajax-loading="#loading"] özniteliğinin etkisini görmesini sağlamaktır;
  • 9-10. satırlar: Şablona bir [resultats] özniteliği eklenir;
  • 12. satır: Şablona [fr-FR] veya [en-US] kültürünü ekliyoruz;
  • 14. satır: Şablona POST saatini girin;

Şablona eklenen [Resultats] türünü hatırlayalım:


public class Resultats {

    // veriler
    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;

    // alıcı ve ayarlayıcılar
...
}

[/ajax-02] eyleminin kodu şu şekilde devam eder:


@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()));
        // her iki seferde bir hata üretiliyor
        int val = new Random().nextInt(2);
        if (val == 0) {
            // bir hata mesajı döndürülür
            résultats.setErreur("erreur.aleatoire");
            return "vue-03";
        }
...
    }
  • 6-11. satırlar: Örnek olarak, jS müşterisine bir hata sayfası nasıl gönderileceği gösterilmektedir. Her iki seferden birinde, aşağıdaki [vue-03.xml] görünümü gönderilir:
  1. satırda dikkat edilmesi gereken nokta, şablona bir mesaj değil, bir mesaj anahtarı eklendiğidir:

[messages_fr.properties]


erreur.aleatoire=erreur aléatoire

[messages_fr.properties]


erreur.aleatoire=randomly generated error

[vue-03.xml] görünümünün kodu şöyledir:


<!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="hata" th:text="${resultats.erreur}"></span> -->
        </p>
    </body>
</html>

  • 12. satırda, kendisi de hesaplanan bir mesaj anahtarı ile parametreleştirilmiş bir mesaj göze çarpmaktadır. Bu kavramı 170. sayfadaki 5.18. paragrafta ele almıştık.

[/ajax-02] eyleminin kodu şu şekilde devam eder:


@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 {
...
        // gönderilen değerler alınır
        double a = formulaire.getA();
        double b = formulaire.getB();
        // şablon oluşturulur
        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");
        }
        // görünüm görüntüleniyor
        return "vue-02";
    }
  • 5-15. satırlar: Dört aritmetik işlem, [a,b] sayıları üzerinde gerçekleştirilir ve modelin [Resultats] örneğinde kapsüllenir;
  • 17. satır: Aşağıdaki [vue-02.xml] görünümü döndürülür:

[vue-02.xml] görünümü şöyledir:


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

Sonuç ister [vue-02.xml] görünümü ister [vue-03.xml] görünümü olsun, bu HTML sonucu, [vue-01.xml] görünümünde [resultats] ile tanımlanan alana yerleştirilir; bunun nedeni, formun [data-ajax-update="#resultats"] özniteliğidir.

7.2.6. Girilen değerlerin POST'i

Burada gönderilen değerlerle ilgili bir zorlukla karşı karşıyayız. [fr-FR] ve [en-US] olmak üzere, gerçek sayıları farklı şekilde yazan iki kültürle çalışıyoruz. Bu zorluğun üstesinden, 190. sayfanın 6.3. paragrafında iki farklı kültüre gerçek sayıları göndermemiz gerektiğinde gelmiştik. Burada o zaman kullandığımız araçları tekrar kullanacağız. Ancak ek bir zorlukla karşı karşıyayız: Girilen değerlerin POST formatına dönüştürülmesini sağlayan yönteme erişimimiz yok. Bu nedenle, form etiketine aşağıdaki öznitelikleri ekledik:

  • [data-ajax-begin="beforeSend"]: [submit] işlemi yapılmadan önce çalıştırılması gereken jS işlevi;
  • [data-ajax-complete="afterComplete"]: yanıt alındığında çalıştırılacak jS işlevi;

Girilen değerleri gönderecek olan jS işlevine erişimimiz yok, ancak iki jS işlevi yazabiliriz:

  • [beforeSend]: POST'ten önce çalıştırılan bir jS işlevi;
  • [afterComplete]: POST'e verilen yanıt alındığında yürütülen jS işlevi;

Bu iki işlev, [local1.js] adlı dosyada yer almaktadır:

  

[local1.js] dosyası, [vue-01.xml] görünümünün jS ortamını aşağıdaki şekilde başlatır:


// genel veriler
var loading;
var formulaire;
var résultats;
var a, b;

// belge yüklendiğinde
$(document).ready(function() {
    // sayfanın çeşitli bileşenlerinin referansları alınır
    loading = $("#loading");
    formulaire = $("#formulaire");
    resultats = $('#sonuçlar');
    a = $("#a");
    b = $("#b");
    // bazı öğeler gizlenir
    loading.hide();
    // formun doğrulayıcıları ayrıştırılıyor
    $.validator.unobtrusive.parse(formulaire);
    // iki yerel ayar yönetilir [fr_FR, en_US]
    // gerçek veriler [a,b] sunucu tarafından İngilizce biçiminde gönderilir
    // gerekirse Fransızca biçime dönüştürülür
    checkCulture(2);
});
  • 22. satır: [checkCulture] işlevi biraz ileride tanıtılmaktadır;

jS ve [beforeSend] işlevleri şu şekilde olacaktır:


function beforeSend(jqXHR, settings) {
    // POST'ten önce
    // sayılar İngilizce formatında gönderilmelidir
    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) {
        // [a,b] sayıları İngilizce biçime dönüştürülür
        var value1 = a.val().replace(",", ".");
        a.val(value1);
        var value2 = b.val().replace(",", ".");
        b.val(value2);
    }
    if (mode == 2) {
...
    }
}
  • 4-6. satırlar: Görünümün kültürü [fr-FR] olup olmadığı kontrol edilir. Bu durumda, gönderilen değerler değiştirilmelidir. Nitekim, kullanıcı [1,6] değerini girmişse, [1.6] değeri gönderilmelidir; aksi takdirde [1,6] değeri sunucu tarafında reddedilecektir. Bunun için gönderilen değerlerdeki virgülü ondalık noktaya değiştirmek yeterlidir (18-21. satırlar);
  • ancak bununla yetinemeyiz. Zira, [beforeSend] işlevi çağrıldığında, gönderilen değerlerin dizisi olan [a=val1&b=valB] zaten oluşturulmuş durumdadır. Bu nedenle onu değiştirmemiz gerekiyor. Bu, fonksiyonun ikinci parametresi olan [settings] kullanılarak yapılır;
  • 7. satır: [settings.data] (settings, işlevin bir parametresidir) gönderilen dizeyi temsil eder. Bu dize, [formulaire.serialize()] ifadesiyle yeniden oluşturulur. Bu ifade, formda gönderilecek değerleri tarar ve POST dizesini oluşturur. Ardından, [a,b]'in ondalık nokta içeren yeni değerlerini alır;

Başka bir işlem yapılmazsa, sunucu yanıtını gönderir ve bu yanıt doğru şekilde görüntülenir. Ancak bu durumda, [a,b]'in değerleri ondalık nokta ile gösterilirken, sistem hâlâ [fr-FR] kültüründe kalır. Dolayısıyla, kullanıcı bunu fark etmez ve [Calculer]'e tekrar tıklarsa, doğrulayıcılar ona [a,b] değerlerinin geçersiz olduğunu bildirir. Bu doğrudur. İşte burada, sonucun alınmasıyla birlikte çalıştırılan [afterComplete] işlevi devreye girer:


function beforeSend(jqXHR, settings) {
    // POST'ten önce
...
}

function afterComplete(jqXHR, settings) {
    // POST'ten sonra
    // Gerekirse sayılar Fransız formatına dönüştürülmelidir
    var culture = Globalize.culture().name;
    if (culture === 'fr-FR') {
        checkCulture(2);
    }
}

function checkCulture(mode) {
    if (mode == 1) {
...
    }
    if (mode == 2) {
        // sayılar Fransız formatına getirilir
        var value1 = a.val().replace(".", ",");
        a.val(value1);
        var value2 = b.val().replace(".", ",");
        b.val(value2);
    }
}
  • 9-12. satırlar: Görünümün dil ayarı [fr-FR] ise, [a,b] sayıları Fransızca biçimine dönüştürülür.

7.2.7. Testler

İşte testlere ait birkaç ekran görüntüsü:

  • [1]'te, sunucunun yanıtı;
  • [2]'te, hata mesajı içeren sunucu yanıtı;
  • [3]'te 5 saniyelik bir gecikme süresi belirlenir. Bu, sunucunun yanıtını göndermeden önce 5 saniye bekleyeceği anlamına gelir. [form] etiketinde, [data-ajax-loading='#loading'] özniteliğini kullandık. [loading] parametresi, şu bölgenin tanımlayıcısıdır:
    • bekleme süresi boyunca görüntülenir;
    • sunucudan yanıt alındıktan sonra gizlenir;

Burada [loading], [4]'te görülen bir animasyonlu resmin tanımlayıcısıdır.

7.2.8. [en-US] ile JavaScript'in devre dışı bırakılması

Tarayıcıda JavaScript devre dışı bırakılırsa ne olur?

Girilen değerlerin POST işlemi, [form] etiketine göre gerçekleştirilecek ve bu etiketin [data-ajax-attr] öznitelikleri kullanılmayacaktır. Her şey, sanki aşağıdaki [form] etiketi varmış gibi gerçekleşir:


<form id="formulaire" name="formulaire" method="post" action="/ajax-02.html">

Dolayısıyla, girilen değerler [/ajax-02] eylemine gönderilecektir. Bu değerler istemci tarafında doğrulanmayacaktır. Dolayısıyla, sunucu tarafındaki doğrulayıcılar devreye girecektir. Bu doğrulayıcılar daha önce de devreye giriyordu, ancak o zamanlar istemci tarafında zaten doğrulanmış, yani doğru olan değerler üzerinde çalışıyordu. Artık durum böyle değildir.

[/ajax-02] eylemini şu şekilde değiştiriyoruz:


@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 isteği mi?
        boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
        ...
    }
  • 4. satır: [/ajax-02] eylemi artık bir POST Ajax çağrısı veya klasik bir POST çağrısı yoluyla çağrılabilir. Bu iki durumu birbirinden ayırt etmemiz gerekiyor. Bunu, istemci tarayıcısı tarafından gönderilen HTTP başlıklarıyla yapıyoruz;

Javascript etkin durumdayken Chrome geliştirici konsolunda (Ctrl-Shift-I) ağ trafiğine baktığımızda, POST çağrısı sırasında istemcinin aşağıdaki başlıkları gönderdiğini görüyoruz:

Yukarıda görüldüğü gibi:

  • [X-Requested-With] başlığı, [1] olarak gönderilmiştir;
  • [2] ile gönderilen değerlere bir [X-Requested-With] parametresi eklenmiştir;

Bu işlem, klasik bir POST durumunda yapılmaz. Dolayısıyla, bilgiyi almak için iki seçenek vardır: HTTP başlıklarından veya gönderilen değerlerden almak. [/ajax-02] eyleminin 4. satırı ilk çözümü seçmiştir.

Bu işlemin koduna devam edelim:


@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 isteği mi?
        boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
        // zaman?
        Integer tempo = (Integer) session.getAttribute("tempo");
        if (tempo != null && tempo > 0) {
            Thread.sleep(tempo);
        }
        // bir sonraki görünümün şablonu hazırlanıyor
        Resultats résultats = new Resultats();
        modèle.addAttribute("resultats", résultats);
        // yerel ayarları belirliyoruz
        setLocale(locale, modèle, résultats);
        // saat
        String heure = new SimpleDateFormat("hh:mm:ss").format(new Date());
        résultats.setHeurePost(heure);
        résultats.setHeureGet(heure);
        // istek geçerli mi?
        if (!isAjax && result.hasErrors()) {
            return "vue-01";
        }
...
  • 2. satır: [@Valid ActionModel01 formulaire] parametresi, sunucu tarafındaki doğrulayıcıları çalıştırır;
  • 20-22. satırlar: Çağrı bir Ajax çağrısı değilse ve doğrulama başarısız olursa, [vue-01.xml] görünümü hata mesajlarıyla birlikte geri gönderilir.

İşte bir örnek:

[/ajax-02] eylemini incelemeye devam edelim:


@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 isteği mi?
        boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
...
        // istek geçerli mi?
        if (!isAjax && result.hasErrors()) {
            return "vue-01";
        }
        // her iki seferde bir hata oluşturuluyor
        int val = new Random().nextInt(2);
        if (val == 0) {
            // bir hata mesajı gönderiliyor
            résultats.setErreur("erreur.aleatoire");
            if (isAjax) {
                return "vue-03";
            } else {
                résultats.setVue("vue-03");
                return "vue-01";
            }
        }
...
  • 14. satır: rastgele bir hata üretilir;
  • 16. satır: Ajax çağrısı durumunda, [vue-03.xml] görünümü döndürülür ve bu görünüm, [resultats] ile tanımlanan alana yerleştirilir;
  • 18. satır: Ajax olmayan bir çağrı durumunda, görüntülenecek görünüm [Resultats] tipi modele yerleştirilir;
  • 19. satır: [vue-01.xml] görünümünü tekrar döndürür;

[vue-01.xml] görünümü şu şekilde değiştirilir:


<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" />
  • 3. satır: [vue-03.xml] görünümü, [resultats] alanının altına eklenecektir;

İşte bir örnek:

Artık [1] ve [2] saatlerinin aynı olduğu dikkatinizi çekecektir.

[/ajax-02] eylemini incelemeye devam edelim:


    @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 isteği mi?
        boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
...
        // gönderilen değerleri alıyoruz
        double a = formulaire.getA();
        double b = formulaire.getB();
        // model oluşturuluyor
        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");
        }
        // görünüm görüntüleniyor
        if (isAjax) {
            return "vue-02";
        } else {
            résultats.setVue("vue-02");
            return "vue-01";
        }
}
  • 7-17. satırlar: Dört aritmetik işlemin sonuçları şablona eklenir;
  • 22-23. satırlar: [vue-01.xml] görünümü (22. satır), [vue-02.xml] görünümü (22. satır) eklenerek oluşturulur;

Bu ekleme, [vue-01.xml] dosyasında şu şekilde gerçekleştirilir:


<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" />
  • 2. satır: [vue-02.xml] görünümü, [resultats] alanının altına eklenecektir;

İşte bir yürütme örneği:

 

7.2.9. [fr-FR] kültürüyle JavaScript'in devre dışı bırakılması

[fr-FR] kültürüyle şu sorunla karşılaşıyoruz:

Fransızca formatında girilen değerler geçersiz olarak bildirildi. Aslında, sunucu İngilizce formatında tamsayılar bekliyor. Çözüm oldukça karmaşıktır. Aşağıdakileri yapacak bir filtre oluşturacağız:

  • isteği yakalayacak;
  • [a] ve [b] değerlerindeki virgülleri ondalık noktasına dönüştürecek;
  • ardından yeni isteği, bunu işlemesi gereken eyleme iletecek;

Öncelikle, [vue-01.xml] görünümüne gizli bir alan ekliyoruz:


<form ...>
...
</p>
    <!-- gizli alanlar -->
    <input type="hidden" id="culture" name="culture" th:value="${resultats.culture}"></input>
</form>
  • 5. satır: [fr-FR] veya [en-US] kültürü, [name=culture] öznitelik alanına yerleştirilir. [input] etiketi formda bulunduğundan, değeri [a] ve [b] değerleriyle birlikte gönderilecektir. Böylece şu biçimde bir dize gönderilmiş olacaktır:
culture=fr-FR&a=12,7&b=20,78

Bu noktayı anlamak önemlidir.

Ardından, uygulama yapılandırmasına bir filtre ekliyoruz:

  

[Config] dosyası şu şekilde değiştirilir:


@Configuration
@ComponentScan({ "istia.st.springmvc.controllers", "istia.st.springmvc.models" })
@EnableAutoConfiguration
public class Config extends WebMvcConfigurerAdapter {
...
    @Bean
    public Filter cultureFilter() {
        return new CultureFilter();
    }
}
  • 7. satır: [cultureFilter] bean'inin [Filter] türünde bir değer döndürmesi, onu bir filtre haline getirir. Bean'in adı ise herhangi bir isim olabilir;

Bir sonraki adım, filtrenin kendisini oluşturmaktır:

  

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 {
        // sonraki işleyici
        filterChain.doFilter(new CultureRequestWrapper(request), response);
    }
}
  • 12. satır: Bir Spring sınıfı olan [OncePerRequestFilter] sınıfını genişletiyoruz ve yapmamız gereken, bu sınıfın [doFilterInternal] yöntemini yeniden tanımlamaktır;
  • 15. satır: [doFilterInternal] yöntemi üç bilgi alır:
    • [HttpServletRequest request]: filtrelenecek istek. Bu değiştirilemez,
    • [HttpServletResponse response]: sunucuya gönderilecek yanıt. Filtre, bunu kendisi yapmayı seçebilir,
    • [FilterChain filterChain]: filtre zinciri. [doFilterInternal] yöntemi işini tamamladığında, isteği filtre zincirindeki bir sonraki filtreye aktarmalıdır;
  • 18. satır: Alınan [new CultureRequestWrapper(request)] isteğinden yeni bir istek oluşturulur ve bir sonraki filtreye aktarılır. İlk istek olan [HttpServletRequest request] değiştirilemeyeceği için yeni bir istek oluşturulur;

[CultureRequestWrapper] sınıfı şu şekildedir:

  

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) {
        // gönderilen a ve b değerleri
        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;
        }
        // diğer durumlar
        return super.getParameterValues(name);
    }

}
  • 6. satır: [CultureRequestWrapper] sınıfı, [HttpServletRequestWrapper] sınıfını genişletir ve bu sınıfın bazı yöntemlerini yeniden tanımlar;
  • 8-10. satırlar: filtrelenecek isteği alan ve bunu üst sınıfa ileten oluşturucu;
  • burada, filtrelenmiş isteğin nihayetinde servlet adı verilen bir sınıfın giriş parametresi olarak kullanılacağı anlaşılmalıdır. Spring MVC ile bu servlet, [DispatcherServlet] türündedir. Bu sınıf, istek parametrelerini almak için çeşitli yöntemlere sahiptir: [getParameter, getParameterMap, getParameterNames, getParameterValues, ...]. Servlet tarafından kullanılan yöntemi yeniden tanımlamak gerekir. Bunun için [DispatcherServlet] sınıfının kodunu okumak gerekir. Ben bunu yapmadım ve çeşitli yöntemleri yeniden tanımladım. Sonunda [getParameterValues] yöntemi yeniden tanımlandı;
  • 13. satır: [getParameterValues] yöntemi, [getParameterNames] yöntemi tarafından döndürülen parametrelerden birinin adını parametre olarak alır ve bu parametrenin değerlerinden oluşan diziyi döndürmelidir. Nitekim, bir parametrenin bir istekte birden fazla kopya halinde bulunabileceği bilinmektedir;
  • 18. satır: virgül, ondalık nokta ile değiştirilir;

İşte bir yürütme örneği:

  • [1]'te, [a,b] değerleri Fransızca biçiminde girilmiştir;
  • [2]'te, sonuçlar;
  • [3]'te, sunucu sayıları İngilizce biçiminde içeren bir sayfa döndürmüştür.

Bu son sorun, [vue-01.xml] görünümünde Thymeleaf kullanılarak aşağıdaki şekilde çözülebilir


<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>
  1. ve 6. satırlarda yapılması gereken birkaç değişiklik var. 3. satırı ele alalım:
  • Buraya [th:field="*{a}"] yazmıştık. [th:field] parametresi, oluşturulan HTML [input] etiketinin [id, name, value] özniteliklerini belirler. Burada, [value] özniteliğini kendimiz yönetmek istiyoruz. Dolayısıyla [id, name] özniteliğini de kendimiz belirliyoruz;
  • [th:value] özniteliği, üçlü operatörü ? kullanan bir ifadeyi değerlendirir. [${resultats.culture}=='fr-FR' and ${actionModel01.b}!=null] ifadesini test ediyoruz. Eğer ifade doğruysa, [value] özniteliğine, ondalık noktasının virgülle değiştirildiği [actionModel01.a] değerini atıyoruz. Eğer yanlışsa, [value] özniteliğine, herhangi bir değişiklik yapılmadan [actionModel01.a]'in değeri atanır;
  • 6. satır: [b] alanı için de aynı işlem tekrarlanır;

İşte bir yürütme örneği:

  • [1]'te, [a,b] numaraları Fransızca yazım biçimini korumuştur. [2]'te ise durum böyle değildir;

Bu yeni sorun, öncekiyle aynı şekilde çözülür. [vue-03.xml] görünümünü şu şekilde değiştiririz:


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

İşte bir örnek:

Artık, JavaScript kullanılan veya kullanılmayan ortamlarda iki kültürü doğru şekilde yöneten bir uygulamaya sahibiz. Bunun için sunucu tarafındaki kodu önemli ölçüde karmaşıklaştırmak gerekti. Bundan sonra, tarayıcının JavaScript'inin her zaman etkin olduğunu varsayacağız. Bu, yalnızca sunucu modunda imkansız olan şeylerin yapılmasına olanak tanır.

7.2.10. [Calculer] bağlantısının yönetimi

Ana sayfa [vue-01.xml]’teki [Calculer] bağlantısını inceleyelim:

[vue-01.xml] görünümündeki [Calculer] bağlantısının kodu şöyledir:


<a href="javascript:postForm()" th:text="#{action.calculer}">Calculer</a>

jS [postForm] işlevi, [local1.js] dosyasında şu şekilde tanımlanmıştır:


// genel veriler
var loading;
var formulaire;
var résultats;
var a, b;

function postForm() {
    // form geçerli mi?
    if (!formulaire.validate().form()) {
        // form geçersiz - tamamlandı
        return;
    }
    // iki yerel ayar yönetiliyor [fr_FR, en_US]
    // gerçek değerler [a,b] her durumda İngilizce formatında gönderilmelidir
    // bunlar [CultureFilter] filtresiyle bu şekilde işlenecektir

    // Ajax çağrısı manuel olarak yapılır
    $.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);
        }
    })
}
  • 2-5. satırlar: Bu öğelerin [$(document).ready] işlevi tarafından başlatıldığını hatırlayalım;
  • 9-12. satırlar: Formdaki jS doğrulayıcıları çalıştırılır. Değerlerden biri geçersizse, [formulaire.validate().form()] ifadesi false değerini döndürür. Bu durumda, formdaki [submit] geçersiz kılınır;
  • 18-38. satırlar: Elle bir Ajax çağrısı yapılır;
  • 19. satır: Ajax çağrısının hedefi olan URL;
  • 20-22. satırlar: HTTP isteğinde varsayılan olarak bulunan başlıklara eklenecek bir HTTP başlık dizisi. Burada, sunucuya bir Ajax çağrısı yapıldığını bildirecek olan HTTP başlığı ekleniyor;
  • 23. satır: Kullanılan HTTP yöntemi;
  • 24. satır: gönderilen veriler. [formulaire.serialize], [formulaire] kimlik numaralı formun [culture=fr-FR&a=12,7&b=20,89] dizesini oluşturur. Burada daha önce incelenen sorunla tekrar karşılaşacağız: [a,b] değerlerinin Anglo-Sakson biçiminde gönderilmesi gerekiyor. Bu sorunun, [cultureFilter] filtresinin oluşturulmasıyla artık çözüldüğünü biliyoruz;
  • 25. satır: Geri dönüşte beklenen veri türü. Sunucunun bir HTML akışı döndüreceği biliniyor;
  • 26. satır: istek başlatıldığında yürütülecek yöntem. Burada, [loading] kimliğine sahip bileşenin görüntülenmesi gerektiği belirtilir. Bu, bekleme sırasında gösterilen animasyonlu görüntüdür;
  • 29. satır: Ajax isteğinin başarılı olması durumunda yürütülecek yöntem. [data] parametresi, sunucudan gelen tam yanıttır. Bunun HTML akışı olduğu bilinmektedir;
  • satır 30: [résultats] kimliğine sahip bileşen, [data] parametresindeki HTML değeriyle güncellenir.
  • 33. satır: Bekleme sinyali gizlenir;
  • satır 35: sunucudan yanıt alındığında, yanıtın başarılı ya da hata olması fark etmeksizin yürütülen işlev;
  • 35-37. satırlar: hata durumunda (sunucu, sunucu tarafında hata olduğunu belirten bir durum koduyla HTTP yanıtını gönderdiğinde), sunucunun HTML yanıtı [resultats] alanında görüntülenir;

İşte bir yürütme örneği:

7.3. HTML sayfasının jSON akışıyla güncellenmesi

Önceki örnekte, web sunucusu HTTP Ajax isteğine bir HTML akışıyla yanıt vermişti. Bu akışta, HTML biçimlendirmesiyle birlikte veriler bulunuyordu. Bu kez, yalnızca verileri içeren jSON (JavaScript Nesne Notasyonu) yanıtlarıyla önceki örneği ele alacağız. Bunun avantajı, bu şekilde daha az bayt aktarılmasıdır. Tarayıcıda JavaScript'in etkin olduğu varsayılmaktadır.

7.3.1. [/ajax-04] eylemi

[/ajax-04] eylemi, [/ajax-01] eylemiyle aynıdır; tek fark, [vue-01.xml] görünümü yerine [vue-04.xml] görünümünün görüntülenmesidir:


@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) {
        ...
        // görünüm
        return "vue-04";
    }

7.3.2. [vue-04.xml] görünümü

 

[vue-04.xml] görünümü, [vue-01.xml] görünümünün gövdesini aşağıdaki farklılıklarla devralır:


<!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>
            <!-- gizli alanlar -->
            <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>
  • 5. satır: Görünümün JavaScript kodu artık [local4.js] dosyasında yer almaktadır;
  • 16. satır: [form] etiketinde artık [Unobtrusive Ajax] kütüphanesine ait [data-ajax-attr] parametreleri bulunmamaktadır. Burada bunu kullanmayacağız. [form] etiketinde, formda girilen değerlerin nasıl ve nereye gönderileceğini belirten [method] ve [action] öznitelikleri de bulunmamaktadır. Bunun nedeni, bu etiketin jS işlevi (20. satır) tarafından gönderilecek olmasıdır;
  • 26-57. satırlar: Daha önce boş olan [resultats] kimlik alanı, artık sonuçları görüntülemek için HTML kodunu içermektedir;
  • 26-34. satırlar: Hesaplama saatinin görüntülendiği sonuç başlığı;
  • satır 35-52: dört aritmetik işlemin sonuçları;
  • satır 53-57: sunucu tarafından gönderilen olası bir hata mesajı;

[vue-04.xm] görünümünün yüklenmesi sırasında yürütülen jS kodu, [local4.js] dosyasında bulunur. Şu şekildedir:


// genel veriler
    var loading;
    var formulaire;
    var résultats;
    var titre;
    var labelHeureCalcul;
    var heureCalcul;
    var aplusb;
    var amoinsb;
    var afoisb;
    var adivb;
    var msgErreur;

// belge yüklendiğinde
$(document).ready(function() {
    // sayfanın çeşitli bileşenlerinin referansları alınır
    loading = $("#loading");
    formulaire = $("#formulaire");
    résultats = $('#sonuçlar');
    titre=$("#titre");
    labelHeureCalcul=$("#labelHeureCalcul");
    heureCalcul=$("#heureCalcul");
    aplusb=$("#aplusb");
    amoinsb=$("#amoinsb");
    afoisb=$("#afoisb");
    adivb=$("#adivb");
    msgErreur=$("#msgErreur");
    // bazı öğeler gizleniyor
    résultats.hide();
    erreur.hide();
    loading.hide();
});
  • 17-27. satırlar: Sayfadaki tüm öğelerin jQuery referansları alınır;
  • 29. satır: sonuç alanı gizlenir;
  • 30. satır: hata alanı da gizlenir;
  • 31. satır: Bekleme animasyonu da gizlenir;
  • 2-12. satırlar: Alınan referanslar, diğer fonksiyonların da kullanabilmesi için global hale getirilir;

7.3.3. jS [postForm] işlevi

[Calculer] bağlantısı şöyledir:


<p>
    <img id="loading" style="display: none" src="/images/loading.gif" />
    <a href="javascript:postForm()" th:text="#{action.calculer}">Calculer</a>
</p>

jS [postForm] fonksiyonu, [local.js] dosyasında şu şekilde tanımlanmıştır:


function postForm() {
    // form geçerli mi?
    if (!formulaire.validate().form()) {
        // form geçersiz - işlem tamamlandı
        return;
    }
    // manuel olarak bir Ajax çağrısı yapılıyor
    $.ajax({
        url : '/ajax-05',
        headers : {
            'Accept: 'application/json'
        },
        type : 'POST',
        data : formulaire.serialize(),
        dataType : 'json',
        beforeSend : onBegin,
        success : onSuccess,
        error : onError,
        complete : onComplete
    })
}

// Ajax çağrısından önce
function onBegin() {
...
}

// sunucudan yanıt alındığında
// başarılı olması durumunda
function onSuccess(data) {
...
}

// sunucudan yanıt alındığında
// başarısızlık durumunda
function onError(jqXHR) {
...
}

// [onSuccess, onError]'ten sonra
function onComplete() {
...
}
  • 3-6. satırlar: Girilen değerler gönderilmeden önce kontrol edilir. Değerler yanlışsa, formun POST işlemi gerçekleştirilmez;
  • 9. satır: Girilen değerler, biraz ileride ayrıntılı olarak ele alacağımız [/ajax-05] eylemine gönderilir;
  • satır 10-12: sunucuya jSON biçiminde bir yanıt beklendiğini bildiren bir HTTP başlığı;
  • 13. satır: Girilen değerler gönderilecektir;
  • 14. satır: Girilen değerlerin, gönderilmeye hazır bir [a=1,6&b=2,4&culture=fr-FR] dizesi olarak serileştirilmesi;
  • 15. satır: sunucu tarafından gönderilen yanıtın türü. Bu, jSON olacaktır;
  • satır 16: POST'ten önce çalıştırılacak işlev;
  • satır 17: Sunucudan gelen yanıt başarılı olursa, yanıt alındığında çalıştırılacak işlev. Bir HTTP isteğinin "başarısı", sunucunun HTTP yanıtının durumuna göre ölçülür. [HTTP/1.1 200 OK ] yanıtı, başarılı bir yanıttır. [HTTP/1.1 500 Internal Server Error] yanıtı ise başarısız bir yanıttır. HTTP yanıtının durumu olarak adlandırılan şey, [200] veya [500] kodudur. Bu kodların bir kısmı 'başarı' ile ilişkiliyken, diğerleri 'başarısızlık' ile ilişkilidir;
  • 18. satır: Sunucudan gelen yanıtın HTTP durumu bir hata durumu olduğunda, bu yanıt alındığında çalıştırılacak işlev;
  • 18. satır: önceki [onSuccess, onError] işlevlerinden sonra en son olarak yürütülecek işlev;

[onBegin] işlevi şöyledir:


// Ajax çağrısından önce
function onBegin() {
    console.log("onBegin");
    // animasyonlu resim gösterilir
    loading.show();
    // görünümdeki bazı öğeler gizleniyor
    entete.hide();
    résultats.hide();
    erreur.hide();
}

Ajax çağrısındaki diğer jS işlevlerini incelemeden önce, [/ajax-05] eylemi tarafından gönderilen yanıtı bilmemiz gerekir.

7.3.4. [/ajax-05] eylemi

[/ajax-05] eylemi şöyledir:


    @RequestMapping(value = "/ajax-05", method = RequestMethod.POST)
    @ResponseBody()
    // POST'i [vue-04] görünümünden işler
    public JsonResults ajax05(@Valid ActionModel01 formulaire, BindingResult result, Locale locale,    HttpServletRequest request, HttpSession session) throws InterruptedException {
        if(result.hasErrors()){
            // olağandışı durum - hiçbir şey görüntülenmez
            return null;
        }
        ...
}
  • 2. satır: [ResponseBody] özniteliği, [/ajax-05] eyleminin yanıtı müşteriye kendisinin döndürdüğünü belirtir. Projenin bağımlılıkları arasında jSON kütüphanesi bulunduğundan, Spring Boot bu tür eylemleri jSON döndürecek şekilde otomatik olarak yapılandırır. Dolayısıyla, [JsonResults] türündeki jSON dizisi (4. satır) müşteriye gönderilecektir;
  • 2. satır: Gönderilen [a, b, culture] değerleri, [ActionModel01] türüne kapsüllenecek ve bu tür için [@Valid ActionModel01] doğrulaması istenecektir. Bu sadece formalite icabıdır. İstemci tarayıcısında JavaScript'in etkin olduğu varsayılmıştır; dolayısıyla gönderilen değerler geldiğinde, bunlar istemci tarafında zaten doğrulanmış olacaktır. Bununla birlikte, jS istemcimizi kullanmayan, "yabancı" bir POST durumunu da öngörebiliriz. Bu durumda, doğrulama başarısız olabilir;
  • 5-7. satırlar: hata durumunda, boş bir jSON akışı döndürülür;

[/ajax-05] eyleminin incelemesine devam edelim:


    @RequestMapping(value = "/ajax-05", method = RequestMethod.POST)
    @ResponseBody()
    // [vue-04] görünümündeki POST öğesini işliyor
    public JsonResults ajax05(@Valid ActionModel01 formulaire, BindingResult result, Locale locale,
            HttpServletRequest request, HttpSession session) throws InterruptedException {
...
        // Spring uygulamasının bağlamı
        WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        // zaman?
        Integer tempo = (Integer) session.getAttribute("tempo");
        if (tempo != null && tempo > 0) {
            Thread.sleep(tempo);
        }
    ...
        // sonuç görüntülenir
        return résultats;
}
  • 8. satır: Spring uygulamasından [ctx] bağlamı alınır. Buna, bir mesaj anahtarı ve bir yerel ayar kullanarak [messages.properties] dosyalarındaki mesajları almak için ihtiyaç duyulur. Bu, aşağıdaki sözdizimi ile yapılır:

ctx.getMessage(clé_message, tableau_de_paramètres, locale)
    • [clé_message]: aranan mesajın anahtarı;
    • [locale]: kullanılan yerel ayar. Dolayısıyla, bu yerel ayar [en_US] ise, [messages_en.properties] dosyası kullanılacaktır;
    • [tableau_de_paramètres]: Elde edilen mesaj, [clé=message {0} {1}]'teki gibi ayarlanabilir. Bu mesajda iki [{0} {1}] parametresi bulunmaktadır. [ctx.getMessage]'in ikinci parametresi olarak iki değerden oluşan bir dizi sağlanmalıdır;
  • 10-13. satırlar: oturumda bir gecikme varsa, bu gecikme süresi boyunca mevcut iş parçacığı durdurulur;

[/ajax-05] eylemi şu şekilde devam eder:


        // bir sonraki görünümün şablonunu hazırlıyoruz
        JsonResults résultats = new JsonResults();
        ...
}
  • 2. satır: müşteriye gönderilen jSON dizesi şablonunun oluşturulması;

[JsonResults] şablonu şöyledir:

 

package istia.st.springmvc.models;

public class JsonResults {

    // veriler
    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 ve setter'lar
...

}
  • 6-13. satırlar: [JsonResult] sınıfındaki her bir alan, [vue-04.xml] görünümündeki aynı [id] alanına karşılık gelir:

[/ajax-05] eylemi şu şekilde devam eder:


        // bir sonraki görünümün şablonunu hazırlıyoruz
        JsonResults résultats = new JsonResults();
        // başlık
        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()));
        // her iki denemeden birinde hata üretiliyor
        int val = new Random().nextInt(2);
        if (val == 0) {
            // bir hata mesajı gönderiliyor
            résultats.setMsgErreur(ctx.getMessage("resultats.erreur",
                    new Object[] { ctx.getMessage("erreur.aleatoire", null, locale) }, locale));
            return résultats;
}
  • 2. satır: müşteriye gönderilen jSON dize şablonunun oluşturulması;
  • 4-6. satırlar: sonuç başlığı mesajları oluşturulur;
  • satır 8-14: ortalama olarak her iki seferde bir hata mesajı oluşturulur. Bu durumda işlem daha ileriye götürülmez ve jSON dizisi müşteriye döndürülür (satır 13);
  • 11. satır: Burada parametreli bir mesaj örneği verilmiştir:

erreur.aleatoire=erreur aléatoire
resultats.erreur=Une erreur s''est produite : [{0}]

[/ajax-05] eylemi şu şekilde devam eder:


        // gönderilen değerler alınıyor
        double a = formulaire.getA();
        double b = formulaire.getB();
        // model oluşturulur
        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");
        }
        // sonuç döndürülüyor
return résultats;
  • 2-3. satırlar: [a] ve [b] değerleri alınır;
  • 5-12. satırlar: dört sonuç oluşturulur;
  • 14. satır: jSON [JsonResults] dizesi istemciye gönderilir;

[Advanced Rest Client] istemcisiyle ne sonuçlandığını görelim:

  • [1-2]'te, POST eylemine [/ajax-05] isteği gönderilir;
  • [3]'te, hatalı değerler gönderilir;
  • [4]'te, sunucu boş bir akış gönderdi;
  • [1]'te doğru değerler gönderiliyor;
  • [2]'te, sunucu tarafından jSON nesnesi geri gönderildi; burada bir hata mesajı var;
  • [1]'te doğru değerler gönderilir;
  • [2]'te, sunucu tarafından geri gönderilen jSON nesnesi; burada dört sonuç yer almaktadır;
  • [1]'te doğru değerler gönderilir;
  • [2]'te, sunucu tarafında bir istisna oluşturmak için gerekli düzenlemeler yapıldı. Sunucunun yine bir jSON nesnesi gönderdiği görülüyor. Bu mesajda, yanıtın HTTP durumunun [500] olduğu görülüyor; bu da sunucu tarafında bir hata oluştuğunu gösteriyor;

7.3.5. jS [postForm] işlevi - 2

Artık sunucu tarafından döndürülen jSON nesnesini bildiğimize göre, bunu JavaScript'te kullanabiliriz. Sunucu, HTTP [200] durum kodlu bir yanıt gönderdiğinde çalıştırılan [onSuccess] yöntemi şöyledir:


// sunucudan yanıt alındığında
// işlem başarılı olursa
function onSuccess(data) {
    console.log("onSuccess");
    // sonuç alanını doldurulur
    titre.text(data.titre);
    labelHeureCalcul.text(data.labelHeureCalcul);
    heureCalcul.text(data.heureCalcul);
    entete.show();
    // hatasız sonuçlar
    if (!data.msgErreur) {
        aplusb.text(data.aplusb);
        amoinsb.text(data.amoinsb);
        afoisb.text(data.afoisb);
        adivb.text(data.adivb);
        résultats.show();
        return;
    }
    // hatalı sonuçlar
    msgErreur.text(data.msgErreur);
    erreur.show();
}
  • 3. satır: [data] parametresi, sunucu tarafından geri gönderilen jSON nesnesidir:
 

HTTP yanıtının durumu [500] olduğunda yürütülen [onError] yöntemi şöyledir:


// sunucudan yanıt alındığında
// başarısızlık durumunda
function onError(jqXHR) {
    console.log("onError");
    // sistem hatası
    msgErreur.text(jqXHR.responseText);
    erreur.show();
}
  • 3. satır: JQuery [jqXHR] nesnesinin özellikleri arasında şunlar bulunur:
    • responseText: sunucunun yanıt metni,
    • status: sunucu tarafından döndürülen hata kodu,
    • statusText: bu hata koduyla ilişkili metin;
  • 6. satır: [jqXHR.responseText] nesnesi, aşağıdaki jSON nesnesidir:
 

7.3.6. Testler

Web uygulamasının çalışırkenki birkaç ekran görüntüsüne bakalım:

 
 
 

7.4. Tek sayfalık web uygulaması

7.4.1. Giriş

Ajax teknolojisi, tek sayfalık uygulamalar oluşturulmasına olanak tanır:

  • ilk sayfa, tarayıcıdan gelen geleneksel bir istekle yüklenir;
  • sonraki sayfalar ise Ajax çağrılarıyla elde edilir. Böylece, sonuç olarak tarayıcı hiçbir zaman URL adresinden ayrılmaz ve hiçbir zaman yeni bir sayfa yüklemez. Bu tür uygulamalara Tek Sayfalı Uygulama (APU) veya İngilizce'de Single Page Application (SPA) denir.

İşte bu tür bir uygulamanın basit bir örneği. Yeni uygulama iki görünüme sahip olacak:

  • [1]'te, [/ajax-06] eylemi bize ilk sayfayı, yani Sayfa 1'i sunar;
  • [2]'te ise bir bağlantı, Ajax çağrısı sayesinde 2. sayfaya geçmemizi sağlar;
  • [3]'te, URL değişmemiştir. Görüntülenen sayfa 2'dir;
  • [4]'te, bir bağlantı sayesinde Ajax çağrısı yoluyla 1. sayfaya geri dönebiliriz;
  • [5]'te, URL değişmemiştir. Görüntülenen sayfa 1'dir.

7.4.2. [/ajax-06] eylemi

[/ajax-06] eyleminin kodu şöyledir:


    @RequestMapping(value = "/ajax-06", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String ajax06() {
        return "vue-06";
}
  • 1-4. satırlar: [/ajax-06] eylemi, yalnızca [vue-06.xml] görünümünü döndürür;

7.4.3. [vue-06.xml] görünümü

[vue-06.xml] görünümü şöyledir:


<!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>
  • 8. satır: görünüm, [local6.js] komut dosyasını kullanır;
  • 12. satır: [vue-07.xml] görünümü, [vue-06.xml] görünümünün [content] kimlik alanına eklenir;

7.4.4. [vue-07.xml] görünümü

[vue-07.xml] görünümü şöyledir:


<!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. jS işlevi [gotoPage]

[vue-07.xml] görünümündeki [Page 2] bağlantısı, aşağıdaki [local6.js] dosyasında tanımlanan jS [gotoPage] işlevini kullanır:


// genel veriler
var content;

function gotoPage(num) {
    // Ajax çağrısı manuel olarak yapılıyor
    $.ajax({
        url : '/ajax-07',
        type : 'POST',
        data : 'num=' + num,
        dataType : 'html',
        beforeSend : function() {
        },
        success : function(data) {
            content.html(data)
        },
        complete : function() {
        },
        error : function(jqXHR) {
            // sistem hatası
            content.html(jqXHR.responseText);
        }
    })
}

// belge yüklenirken
$(document).ready(function() {
    // sayfanın çeşitli bileşenlerinin referansları alınırken
    content = $("#content");
});
  • 28. satır: Sayfa yüklendiğinde, [content] kimlikli alan kaydedilir ve bu alan bir küresel değişken haline getirilir (2. satır);
  • 4. satır: [gotoPage] işlevi, geçerli görünümde görüntülenecek sayfanın numarasını (1 veya 2) parametre olarak alır;
  • 7. satır: POST işlevinin hedefi olan URL;
  • satır 8: 7. satırdaki URL, bir POST aracılığıyla çağrılır;
  • 9. satır: gönderilen dize. [num] adlı bir parametre gönderilir. Bu parametrenin değeri, mevcut görünümde görüntülenecek sayfa numarasıdır (4. satır);
  • 10. satır: sunucu, görüntülenecek sayfaya ait HTML değerini geri gönderecektir;
  • satır 13-15: İşlem başarılı olursa (HTTP durumu 200'e eşitse), sunucu tarafından gönderilen HTML, [content] kimlik alanına yerleştirilir;
  • satır 18-20: işlem başarısız olursa (HTTP durumu 500'e eşitse), sunucu tarafından gönderilen HTML, [content] kimlik alanına yerleştirilir;

7.4.6. [/ajax-07] eylemi

[/ajax-07] eyleminin kodu şöyledir:


@RequestMapping(value = "/ajax-07", method = RequestMethod.POST, produces = "text/html; charset=UTF-8")
    public String ajax07(int num) {
        // num: sayfa numarası
        switch (num) {
        case 1:
            return "vue-07";
        case 2:
            return "vue-08";
        default:
            return "vue-07";
        }
    }
  • 2. satır: [num] adlı POST parametresi alınır. 2. satırdaki parametrenin, burada [num] olan POST parametresinin adını taşıması gerektiğini hatırlatırız. [num] bir sayfa veya görünüm numarasıdır;
  • 5-6. satırlar: [num==1] olması durumunda, [vue-07.xml] görünümünü döndürür;
  • satır 7-8: [num==2] olması durumunda, [vue-08.xml] görünümü döndürülür;
  • satır 9-10: diğer durumlarda (normalde mümkün değildir), [vue-07.xml] görünümü döndürülür;

7.4.7. [vue-08.xml] görünümü

[vue-08.xml] görünümü, uygulamanın 2. sayfasını oluşturur:


<!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. Bir HTML yanıtına birden fazla akış ekleme

7.5.1. Giriş

Aşağıdaki uygulamayı ele alalım:

[1] sayfasında dört alan bulunmaktadır:

  • [Zone 1, Zone 3], [Rafraîchir] düğmesine tıklandığında görünen/kaybolan alanlardır. Bu iki alanın her birinin ([2]) kaç kez göründüğü sayılır. [Zone 1] alanı Fransızca dilini kullanırken, [Zone 3] alanı İngilizce dilini kullanır;
  • [Zone 2] bölümü her zaman mevcuttur;
  • [Saisies] bölümü her zaman mevcuttur;

[Valider] bağlantısı, aşağıdaki [3] sayfasını görüntüler:

  • [Retour à la page 1] bağlantısı, 1 numaralı sayfayı [4]'teki durumuna geri getirir;

Uygulama tek sayfalıdır. İlk sayfa, tarayıcı tarafından sunucudan istenir. Sonraki sayfalar ise Ajax çağrıları yoluyla sunucudan alınır.

7.5.2. [/ajax-09] eylemi

  

[/ajax-09] eylemi şöyledir:


    @RequestMapping(value = "/ajax-09", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String ajax09() {
        return "vue-09";
}

Bu eylem, yalnızca [vue-09.xml] görünümünü görüntüler.

7.5.3. XML

  

[vue-09.xml] görünümü, uygulamanın ana sayfasıdır:


<!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>
  • 9. satır: uygulamada kullanılan JS dosyası;
  • 15. satır: ana sayfanın içeriği;
  • 16. satır: bekleme sırasında gösterilen animasyonlu bir resim:
  • 17. satır: olası bir hatayı görüntülemek için alan;

[vue-09-page1.xml] görünümü, uygulamanın 1. sayfasıdır:


<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <h2>Page 1</h2>
        <!-- alan 1 -->
        <fieldset id="zone1" style="background-color:pink">
            <legend>Zone 1</legend>
            <span id="zone1-content" th:text="xx">xx</span>
        </fieldset>
        <!-- alan 2 -->
        <fieldset id="zone2" style="background-color:lightgreen">
            <legend>Zone 2</legend>
            <span>Ce texte reste toujours présent</span>
        </fieldset>
        <!-- alan 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>
  • 6-9. satırlar: [Zone 1] alanı. İçeriği, [id="zone1-content"] bileşenine yerleştirilir;
  • 11-14. satırlar: [Zone 2] alanı, bu alan değişmez;
  • satır 16-19: [Zone 3] alanı. İçeriği, [id="zone3-content"] bileşenine yerleştirilir;
  • 22. satır: Formu gönderen JS işlevi;
  • 25. satır: giriş alanının eklenmesi;

Sayfa 1'de [form] etiketinin bulunmadığına dikkat edilmelidir. Her şey JavaScript ile işlenecektir.

[vue-09-saisies.xml] görünümü şu şekildedir:


<!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>
  • 5-8. satırlar: bir karakter dizisinin girilmesi;
  • 13-16. satırlar: bir tamsayı girişi;
  • 14. satır: girilen değerleri gönderen JS işlevi;

Yine, giriş alanının [form] etiketine sahip olmadığına dikkat edilmelidir.

Toplamda, 1 numaralı sayfa iki işlev sunmaktadır:

  • [Rafraîchir]: 1 ve 3 numaralı alanları yeniler. Bu işlem sunucu tarafından işlenir ve sunucu rastgele şunları döndürür:
    • 1. bölgeye erişim sayacını, 3. bölgeye ise hiçbir şey göndermez;
    • 3. bölgeyi erişim sayacıyla birlikte ve 1. bölge için hiçbir şey,
    • her iki bölgeyi de erişim sayaçlarıyla birlikte;
  • [Valider]: girilen değerlerle 2. sayfayı görüntüler veya girilen veriler geçersizse bir hata mesajı gösterir;

Öncelikle [Rafraîchir] düğmesine odaklanacağız.

7.5.4. [Rafraîchir] düğmesini yöneten JS kodu

  

[local9.js] dosyasının kodu şöyledir:


// genel değişkenler
var content;
var loading;
var erreur;

// belge yüklendiğinde
$(document).ready(function() {
    // sayfanın çeşitli bileşenlerinin referansları alınır
    loading = $("#loading");
    loading.hide();
    erreur = $("#erreur");
    erreur.hide();
    content = $("#content");
});
  • 9-13. satırlar: ana sayfa yüklendiğinde, [loading, erreur, content] ile tanımlanan üç bileşenin referansları kaydedilir;
  • 2-4. satırlar: Bu üç bileşenin referansları küresel değişkenlerde saklanır. İlgili üç alan, ne zaman olursa olsun görüntülenen sayfada her zaman mevcut olduğu için bu referanslar sabit kalır. Sabit kaldıkları için, [$(document).ready] içinde hesaplanabilir ve JS dosyasındaki diğer işlevlerle paylaşılabilirler;

[postForm] fonksiyonu, [Rafraîchir] düğmesine yapılan tıklamayı yönetir:


function postForm() {
    console.log("postForm");
    // manuel olarak bir Ajax çağrısı yapılır
    $.ajax({
        url : '/ajax-10',
        headers : {
            'Accept: 'application/json'
        },
        type : 'POST',
        dataType : 'json',
        beforeSend : onBegin,
        success : onSuccess,
        error : onError,
        complete : onComplete
    })
}
  • 4-15. satırlar: sunucuya Ajax çağrısı;
  • 5. satır: [ajax-10] eylemi, POST'i işleyecektir;
  • 6-8. satırlar: yanıt jSON olacaktır. JS istemcisi, jSON belgelerini kabul ettiğini belirtir;
  • 9. satır: [ajax-10] eylemi, POST işlemiyle çağrılır;
  • satır 10: jSON'ten veri alınacak;
  • 11. satır: Ajax çağrısından önce yürütülen işlev;
  • satır 12: sunucudan yanıt alındığında, yanıt başarılı olduğunda yürütülen işlev [200 OK];
  • satır 13: sunucudan gelen yanıtın alınmasıyla birlikte, yanıtın başarısız olması durumunda yürütülen işlev [500 Internal server error, ...];
  • satır 14: yanıt alındıktan sonra çalıştırılan işlev;

[onBegin] işlevi şöyledir:


// Ajax çağrısından önce
function onBegin() {
    console.log("onBegin");
    // bekleme görüntüsü
    loading.show();
}

Bu işlev, sunucudan sonuç beklenirken gösterilecek animasyonlu görüntüyü başlatmakla yetinir.

7.5.5. [/ajax-10] eylemi

  

[/ajax-10] eylemi şöyledir:


// oturum
    @Autowired
    private SessionModel1 session;
    // Thymeleaf / Spring motoru
    @Autowired
    private SpringTemplateEngine engine;

    @RequestMapping(value = "/ajax-10", method = RequestMethod.POST)
    @ResponseBody()
    public JsonResult10 ajax10(HttpServletRequest request, HttpServletResponse response) {
    ...
    }
  • 3. satır: oturum eklenir. Bu oturumun türü, aşağıdaki gibi [SessionModel1]'tir:
  

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;
    // iki sayaç
    private int cpt1 = 0;
    private int cpt3 = 0;
    // üç alan
    private String zone1 = "xx";
    private String zone3 = "zz";
    private String saisies;
    private boolean zone1Active = true;
    private boolean zone3Active = true;

    // getter ve setter'lar
    ...
}

[SessionModel1] oturumu aşağıdaki öğeleri depolar:

  • 15. satır: [cpt1]'in, [Zone 1] alanının görüntülendiği durumların sayısı;
  • satır 16: [Zone 3] alanının görüntülendiği [cpt3] sayısını;
  • 18-20. satırlar: HTML akışları, [Zone 1], [Zone 3] ve [Saisies] alanlarından. Bu, [Page 1] --> [Page 2] --> [Page 1] dizisinde gereklidir. [Page 2]'ten [Page 1]'e geçerken, [Page 1]'i ve dolayısıyla bu dosyanın üç alanını geri yüklemek gerekir;
  • 21-22. satırlar: [Zone 1] ve [Zone 3] alanlarının görüntülenip görüntülenmediğini (görünür olup olmadığını) belirten iki boole değeri;

[AjaxController] denetleyicisine eklenen diğer öğe şudur:


    // Thymeleaf / Spring motoru
    @Autowired
private SpringTemplateEngine engine;

[SpringTemplateEngine] türündeki bean, [Config] yapılandırma dosyasında tanımlanmıştır:

  

Aşağıdaki şekilde tanımlanmıştır:


    @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;
}
  • 2-10. satırlar: Görünümlerin belirli özelliklerini tanımlamamıza olanak tanıyan [SpringResourceTemplateResolver] tipi bean'i biliyoruz;
  • 13-17. satırlar: [SpringTemplateEngine] türündeki bean, görünümlerin "motorunu", yani istemcilere [Thymeleaf] yanıtlarını oluşturmaktan sorumlu sınıfı tanımlamamıza olanak tanır. [Thymeleaf]'in varsayılan bir "motoru" vardır ve [Spring] ortamında kullanıldığında başka bir "motoru" devreye girer. Burada kullandığımız da budur;

[/ajax-10] eyleminin imzası şöyledir:


@RequestMapping(value = "/ajax-10", method = RequestMethod.POST)
    @ResponseBody()
    public JsonResult10 ajax10(HttpServletRequest request, HttpServletResponse response) {
    ...
}
  • 1. satır: [/ajax-10] eylemi yalnızca bir POST'i kabul eder;
  • 2. satır: [/ajax-10] eylemi, yanıtı müşteriye kendisi verir. Bu yanıt otomatik olarak jSON'e dönüştürülür;
  • 3. satır: Yanıt, aşağıdaki gibi [JsonResult10] türündedir:
  

package istia.st.springmvc.models;

public class JsonResult10 {

    // veriler
    private String content;
    private String zone1;
    private String zone3;
    private String erreur;
    private String saisies;
    private boolean zone1Active;
    private boolean zone3Active;

    public JsonResult10() {
    }

    // getter ve setter'lar
...
}
  • 6. satır: [content] ile tanımlanan alanın içeriği HTML;
  • 7. satır: [Zone 1] alanının içeriği olan HTML;
  • 8. satır: [Zone 3] alanının içeriği olan HTML;
  • 9. satır: [Erreur] alanının içeriği HTML;
  • 10. satır: [Saisies] alanının içeriği HTML;
  • satır 11: [Zone 1] alanının görüntülenip görüntülenmeyeceğini belirten boole değeri;
  • 12. satır: [Zone 3] alanının görüntülenip görüntülenmeyeceğini belirten boole değeri;

[/ajax-10] eyleminin kodu şöyledir:


@RequestMapping(value = "/ajax-10", method = RequestMethod.POST)
    @ResponseBody()
    public JsonResult10 ajax10(HttpServletRequest request, HttpServletResponse response) {
        // Thymeleaf bağlamı
        WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
        // yanıt
        JsonResult10 result = new JsonResult10();
        // oturum
        session.setZone1(null);
        session.setZone3(null);
        session.setZone1Active(false);
        session.setZone3Active(false);
        // rastgele bir yanıt verilir
        int cas = new Random().nextInt(3);
        switch (cas) {
        case 0:
            // alan 1 etkin
            setZone1(thymeleafContext, result);
            return result;
        case 1:
            // bölge 3 aktif
            setZone3(thymeleafContext, result);
            return result;
        case 2:
            // bölgeler 1 ve 3 aktif
            setZone1(thymeleafContext, result);
            setZone3(thymeleafContext, result);
            return result;
        }
        return null;
    }
  • 5. satır: [Thymeleaf] bağlamını alıyoruz. Bunun ne işe yarayacağını daha sonra göreceğiz;
  • 7. satır: Şimdilik boş bir yanıt oluşturuyoruz;
  • 9-12. satırlar: oturumda bulunan iki alanı [null]'e yerleştiriyoruz ve bunların görüntülenmemesi gerektiğini belirtiyoruz. Bu iki alan yakında oluşturulacak, ancak bunlardan sadece birinin oluşturulması da mümkündür;
  • satır 14-29: iki alan da oluşturulur;
  • satır 17-19: Yalnızca [Zone 1] alanı oluşturulur;
  • satır 21-23: Yalnızca [Zone 3] alanı oluşturulur;
  • 25-28. satırlar: [Zone 1] ve [Zone 3] alanlarının her ikisi de oluşturulur;

[Zone 1] alanının HTML akışı aşağıdaki yöntemle oluşturulur:


    private void setZone1(WebContext thymeleafContext, JsonResult10 result) {
        // 1. bölge aktif
        // akış 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);
        // oturum
        session.setCpt1(cpt1);
        session.setZone1(zone1);
        session.setZone1Active(true);
}
  • 1. satır: parametreler şunlardır:
    • [WebContext] türündeki [Thymeleaf] bağlamı,
    • [JsonResult10] türünde oluşturulmakta olan müşteriye verilen yanıt;
  • 3. satır: [Zone 1] alanının görüntülenme sayısını sayan oturumun [cpt1] sayacını artırır;
  • 4. satır: [WebContext] türündeki [Thymeleaf] bağlamı, Spring MVC'teki [Model] şablonuna benzer şekilde çalışır. Modele bir öğe eklemek için [WebContext.setVariable] kullanılır. Burada, [cpt1] sayacını [Thymeleaf] modeline yerleştiriyoruz. Bu, Thymeleaf ifadesi [${cpt1}]'in değerlendirilmesini sağlayacaktır
  • 5. satır: [Thymeleaf] bağlamının bir yerel ayarı vardır. Bu, [#{clé_msg}] türündeki ifadeleri değerlendirmesine olanak tanır. Burada, Thymeleaf bağlamı Fransızca yerel ayarıyla ilişkilendirilir;
  • 6. satır: Bu, en ilginç komuttur. Thymeleaf motoru, [vue-09-zone1.xml] görünümünü az önce hesapladığımız şablon ve yerel ayar ile işleyecek ve ortaya çıkan HTML akışını istemciye göndermek yerine, onu bir karakter dizisi olarak döndürecektir;
  • 7-9. satırlar: Az önce hesaplanan [Zone 1] alanından elde edilen HTML akışı, oturumda ve müşteriye gönderilecek sonuçta saklanır. Ayrıca, [Zone 1] alanının görüntülenmesi gerektiği belirtilir;
  • 11-13. satırlar: [Zone 1] alanını yeniden oluşturabilmek için bu alana ait bilgiler oturumda saklanır;
  1. satır, aşağıdaki [vue-09-zone1.xml] görünümünü işler:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <span th:text="#{message.zone}"></span>
    <span th:text="${cpt1}"></span>
</html>
  • 3. satır: [#{message.zone}] ifadesi, yerel ayar kullanılarak değerlendirilecektir;
  • 4. satır: [${cpt1}] ifadesi, Thymeleaf şablonu kullanılarak değerlendirilecektir;

[message.zone] anahtar mesajı, [messages_fr.properties] ve [messages_en.properties] mesaj dosyalarında tanımlanmıştır:

  

[messages_fr.properties]


message.zone=Nombre d'accès : 

[messages_en.properties]


message.zone=Number of hits: 

[Zone 3] alanındaki HTML akışı da benzer bir yöntemle oluşturulur:


    private void setZone3(WebContext thymeleafContext, JsonResult10 result) {
        // bölge 3 aktif
        // akış 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);
        // oturum
        session.setCpt3(cpt3);
        session.setZone3(zone3);
        session.setZone3Active(true);
}
  • 6. satır: [Zone 3] alanının yerel ayarı İngilizce'dir;

7.5.6. [/ajax-10] eyleminin yanıtının işlenmesi

Sunucunun yanıtını işleyecek olan [local9.js] kodundaki JS koduna geri dönelim:


// sunucudan yanıt alındığında
// başarılı olması durumunda
function onSuccess(data) {
    console.log("onSuccess");
    // içerik
    if (data.content) {
        content.html(data.content);
    }
    // bölge 1
    if (data.zone1Active) {
        $("#zone1").show();
        if (data.zone1) {
            $("#zone1-content").html(data.zone1);
        }
    } else {
        $("#zone1").hide();
    }
    // bölge 3 aktif mi?
    if (data.zone3Active) {
        $("#zone3").show();
        if (data.zone3) {
            $("#zone3-content").html(data.zone3);
        }
    } else {
        $("#zone3").hide();
    }
    // girişler?
    if (data.saisies) {
        $("#saisies").html(data.saisies);
    }
    // hata mı?
    if (data.erreur) {
        erreur.text(data.erreur);
        erreur.show();
    } else {
        erreur.hide();
    }
}
  1. satırda [data] değişkeninde alınan yanıtın Java yapısını hatırlayalım:

public class JsonResult10 {

    // veri
    private String content;
    private String zone1;
    private String zone3;
    private String erreur;
    private String saisies;
    private boolean zone1Active;
    private boolean zone3Active;

}
  • 6-8. satırlar: Eğer [data.content!=null] ise, [id=content] alanı bununla başlatılır. Bu alan, [Page 1] veya [Page 2]’in tamamını temsil eder. Bu örnekte [data.content==null] değerine sahibiz; dolayısıyla [id=content] alanı değiştirilmeyecek ve [Page 1] değerini göstermeye devam edecektir;
  • 10-17. satırlar: [data.zone1Active==true] ise [Zone 1] görüntülenir. Ayrıca [data.zone1!=null] ise [Zone 1]'in içeriği değiştirilir, aksi takdirde olduğu gibi kalır;
  • satır 19-26: [Zone 3] için de aynı işlem yapılır;
  • 28-30. satırlar: [data.saisies!=null] varsa, [Saisies] alanı yeniden oluşturulur. Bu örnekte, [data.saisies==null] var ve bu nedenle [Saisies] alanı olduğu gibi kalır;
  • 32-37. satırlar: [Erreur] alanı için de benzer bir mantık geçerlidir, ancak şu farklılıklar vardır:
    • 33. satır: [data.erreur], metin biçiminde bir hata mesajı olacaktır;
    • satır 36: [data.erreur==null] ise, [Erreur] alanı gizlenir. Zira bu alan, önceki istek sırasında görüntülenmiş olabilir;

Sunucu tarafında bir hata olması durumunda (HTTP, 500 Internal server error gibi bir durum), aşağıdaki işlev yürütülür:


// sunucudan yanıt alındığında
// başarısızlık durumunda
function onError(jqXHR) {
    console.log("onError");
    // sistem hatası
    erreur.text(jqXHR.responseText);
    erreur.show();
}

Böyle bir hatayı görmek için, [postForm] işlevini şu şekilde değiştirelim:


function postForm() {
    console.log("postForm");
    // geçerli sayfadan referanslar alınırken
    ...
    // manuel olarak Ajax çağrısı yapılıyor
    $.ajax({
        url : '/ajax-10x',
        ...
    })
}
  • 7. satır: mevcut olmayan bir URL işlevi ekleyelim;

[Rafraîchir] düğmesine tıklandığında elde edilen sonuçlar şunlardır:

İlginçtir ki, hata da jSON dizesi biçiminde gönderilmiştir.

Sunucudan yanıt alındıktan sonra yürütülen yöntem şöyledir:


// [onSuccess, onError]'ten sonra
function onComplete() {
    console.log("onComplete");
    // bekleme görüntüsü
    loading.hide();
}

Bekleme sırasında hareketli resim gizlenmekle yetiniyoruz.

7.5.7. [Page 2] sayfasının görüntülenmesi

[Valider] bağlantısının HTML kodu şöyledir:


<a href="javascript:valider()">Valider</a>

JS [valider] işlevi şöyledir:


// girdilen değerlerin doğrulanması
function valider() {
    // gönderilen değer
    var post = JSON3.stringify({
        "value1" : $("#text1").val().trim(),
        "value2" : $("#text2").val().trim()
    });
    // Ajax çağrısı manuel olarak yapılıyor
    $.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
    })
}
  • 4-7. satırlar: Gönderilecek v1 ve v2 olmak üzere iki değerimiz var: bunlar, [#text1] ve [#text2] ile tanımlanan giriş bileşenlerine ait değerlerdir. Şimdi yeni bir şey yapacağız. Bu iki değeri jSON {"value1":v1,"value2":v2} biçiminde bir dize olarak göndereceğiz;
  • 10. satır: Gönderilen değerler [ajax-11A] eylemine iletilecek;
  • 12. satır: jSON yanıtını alacağımızı bildiğimiz için, jSON'ten yanıt alabileceğimizi belirtiriz;
  • 13. satır: sunucuya, gönderilen değeri jSON biçiminde bir dize olarak göndereceğimizi belirtiriz;
  • 15-16. satırlar: Gönderilecek değerden bir POST oluşturulur;
  • 17. satır: jSON alacağız;

7.5.8. [ajax-11A] eylemi

Gönderilen jSON dizesini işleyen [ajax-11A] eylemi şöyledir:


@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) {
        ...
    }
  • 1. satır: ["application/json"] ile eylemin jSON biçiminde bir belge beklediği belirtilir. Bu belge, müşteri tarafından gönderilen değerdir;
  • 3. satır: Gönderilen değer, aşağıdaki [PostAjax11A post] nesnesinden alınacaktır:
  

package istia.st.springmvc.models;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Range;

public class PostAjax11A {

    // veriler
    @Size(min = 4, max = 6)
    @NotNull
    private String value1;
    @Range(min = 10, max = 14)
    @NotNull
    private Integer value2;

    // alıcı ve ayarlayıcılar
    ...
}
  • [PostAjax11A] nesnesinin yapısı, gönderilen nesnenin yapısını {"value1":v1,"value2":v2} şeklinde yansıtmalıdır. Bu nedenle, [value1] (13. satır) ve [value2] (16. satır) alanları gereklidir;
  • bu iki alana bütünlük kısıtlamaları uyguladık;

Şimdi [ajax-11A] eyleminin koduna geri dönelim:


@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 bağlamı
        WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
        // yanıt
        JsonResult10 result = new JsonResult10();
        // gönderim geçerli mi?
        if (bindingResult.hasErrors()) {
            // 1. sayfa bir hata mesajıyla geri gönderiliyor
            result.setZone1Active(session.isZone1Active());
            result.setZone3Active(session.isZone3Active());
            result.setErreur(getErreursForModel(bindingResult));
            return result;
        }
        ...
}
  • 3. satır: [@RequestBody] açıklaması, müşteri tarafından gönderilen belgeyi belirtir. Bu, müşteri tarafından jSON'e gönderilen değerdir. Dolayısıyla bu değer, [PostAjax11A] nesnesini oluşturmak için kullanılacaktır;
  • 3. satır: [@Valid] açıklaması, gönderilen değerin doğrulanmasını zorunlu kılar;
  • 9. satır: Doğrulama başarısız olursa:
    • 13. satır: bir hata mesajı gönderilir,
    • 11-12. satırlar: 1 ve 3 numaralı alanlar önceki durumlarına (görüntülenmiş veya görüntülenmemiş) geri getirilir;

Hata mesajı şu şekilde hesaplanır:


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

Bu, daha önce karşılaştığımız bir işlevdir.

[ajax-11A] eylemi şu şekilde devam eder:


@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 bağlamı
        WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
        // yanıt
        JsonResult10 result = new JsonResult10();
        // gönderim geçerli mi?
        if (bindingResult.hasErrors()) {
    ...
        }
        // giriş alanı kaydediliyor
        thymeleafContext.setVariable("value1", post.getValue1());
        thymeleafContext.setVariable("value2", post.getValue2());
        session.setSaisies(engine.process("vue-09-saisies", thymeleafContext));
        // 2. sayfa gönderiliyor
        result.setContent(engine.process("vue-09-page2", thymeleafContext));
        return result;
}
  • 13-14. satırlar: gönderilen değerler Thymeleaf bağlamına yerleştirilir;
  • 15. satır: Bu bağlam kullanılarak [vue-09-saisies] görünümü hesaplanır ve daha sonra yeniden oluşturulabilmesi için oturuma eklenir;
  • 17. satır: 2. sayfa, müşteriye gönderilecek sonuca eklenir;

[vue-09-page2.xml] görünümü şu şekildedir:

  

<!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>
  • 9. ve 13. satırlarda, [/ajax-11A] eyleminin Thymeleaf bağlamına yerleştirdiği [value1, value2] değerleri görüntülenir;

7.5.9. [/ajax-11A] eyleminin yanıtının işlenmesi

İstemci tarafında, [/ajax-10] eyleminin yanıtı, [onSuccess] işlevi tarafından işlenir:


function onSuccess(data) {
    console.log("onSuccess");
    // içerik
    if (data.content) {
        content.html(data.content);
    }
    // alan 1
    if (data.zone1Active) {
        $("#zone1").show();
        if (data.zone1) {
            $("#zone1-content").html(data.zone1);
        }
    } else {
        $("#zone1").hide();
    }
    // alan 3 etkin mi?
    if (data.zone3Active) {
        $("#zone3").show();
        if (data.zone3) {
            $("#zone3-content").html(data.zone3);
        }
    } else {
        $("#zone3").hide();
    }
    // girişler?
    if (data.saisies) {
        $("#saisies").html(data.saisies);
    }
    // hata mı?
    if (data.erreur) {
        erreur.text(data.erreur);
        erreur.show();
    } else {
        erreur.hide();
    }
}

Bu kodu daha önce açıklamıştık. Hatalı ve hatasız yanıt olmak üzere iki durumu ele alalım:

Hata içeren

Bu durumda, [/ajax-11A] eylemi, {"zone1":null, "zone3":null,"saisies":null,"erreur":erreur,"zone1Active":zone1Active,"zone3Active":zone3Active,"content":null} biçiminde bir yanıt göndermiştir. Yukarıdaki kodu incelersek şunu görürüz:

  • [content] alanı değişmiyor. Bu alanda 1 numaralı sayfa yer alıyordu;
  • [Erreur] alanı görüntülenir;
  • [Zone 1], [Zone 3] ve [Saisies] alanları olduğu gibi bırakılır;

Hata yok

Bu durumda, [/ajax-11A] eylemi, {"zone1":null, "zone3":null,"saisies":null,"erreur":null,"zone1Active":false,"zone3Active":false,"content":content} biçiminde bir yanıt göndermiştir. Yukarıdaki kodu takip edersek şunu görürüz:

  • [content] bölgesi görüntülenir. Bu bölge 2 numaralı sayfayı içerir;

İşte üç çalışma örneği:

Doğrulama hatası olan bir durum:

POST hatası içeren bir örnek:

Bu tür bir hata farklıdır. Spring, jSON dizesini [PostAjax11A] türüne dönüştüremediğinden, HTTP yanıtını [status=400] ile birlikte döndürmüştür. [ajax-11A] eylemi gerçekleştirilmedi;

Hatasız bir durum:

7.5.10. 1 numaralı sayfaya geri dön

2 numaralı sayfadaki [Retour vers la page 1] bağlantısı şöyledir:


<a href="javascript:retourPage1()">Retour à la page 1</a>

JS [retourPage1] yöntemi şöyledir:


// sayfa 1'e geri dön
function retourPage1() {
    // Ajax çağrısı manuel olarak yapılıyor
    $.ajax({
        url : '/ajax-11B',
        headers : {
            'Accept: 'application/json',
        },
        type : 'POST',
        dataType : 'json',
        beforeSend : onBegin,
        success : onSuccess,
        error : onError,
        complete : onComplete
    })
}

Bu yöntem, POST'i, gönderilen değer olmadan, [/ajax-11B] eylemine yönlendirir.

7.5.11. [/ajax-11B] eylemi

[/ajax-11B] eylemi şöyledir:


    @RequestMapping(value = "/ajax-11B", method = RequestMethod.POST)
    @ResponseBody
    public JsonResult10 ajax11B(HttpServletRequest request, HttpServletResponse response) {
        // Thymeleaf bağlamı
        WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
        // yanıt
        JsonResult10 result = new JsonResult10();
        // Sayfa 1'i orijinal haline geri getiriyoruz
        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;
}

İşlem, üç alanıyla birlikte 1 numaralı sayfayı yeniden oluşturmalıdır [Zone1, Zone3, Erreur]:

  • 9. satır: 1 numaralı sayfa sonuca eklenir;
  • 10. satır: giriş alanı sonuca eklenir;
  • 11. satır: [Zone 1] alanı sonuca eklenir;
  • 12. satır: [Zone 3] alanı sonuca eklenir;
  • 13-14. satırlar: [Zone 1] ve [Zone 3] alanlarının durumu sonuca eklenir;

7.5.12. [/ajax-11B] eyleminin yanıtının işlenmesi

[/ajax-11B] eyleminin yanıtı, [onSuccess] işlevi tarafından işlenir:


function onSuccess(data) {
    console.log("onSuccess");
    // içerik
    if (data.content) {
        content.html(data.content);
    }
    // alan 1
    if (data.zone1Active) {
        $("#zone1").show();
        if (data.zone1) {
            $("#zone1-content").html(data.zone1);
        }
    } else {
        $("#zone1").hide();
    }
    // alan 3 aktif mi?
    if (data.zone3Active) {
        $("#zone3").show();
        if (data.zone3) {
            $("#zone3-content").html(data.zone3);
        }
    } else {
        $("#zone3").hide();
    }
    // girişler?
    if (data.saisies) {
        $("#saisies").html(data.saisies);
    }
    // hata mı?
    if (data.erreur) {
        erreur.text(data.erreur);
        erreur.show();
    } else {
        erreur.hide();
    }
}

[/ajax-11B] eylemi, {"zone1":zone1, "zone3":zone3,"saisies":saisies,"erreur":null,"zone1Active":zone1Active,"zone3Active":zone3Active,"content":content} biçiminde bir yanıt gönderdi. Yukarıdaki kodu incelediğimizde şunu görüyoruz:

  • [content] alanı değiştirilmiştir. Bu alan daha önce 2 numaralı sayfayı içeriyordu. Artık 1 numaralı sayfayı içerecektir;
  • [Erreur] bölümü gizlenmiştir;
  • [Zone 1], [Zone 3] ve [Saisies] alanları önceki halleriyle görüntülenir;

7.6. İstemci tarafında oturumu yönetme

7.6.1. Giriş

Bir önceki paragrafta, yapısı aşağıdaki gibi olan bir oturumu yönettik:


public class SessionModel1 implements Serializable {

    // iki sayaç
    private int cpt1 = 0;
    private int cpt3 = 0;
    // üç bölge
    private String zone1 = "xx";
    private String zone3 = "zz";
    private String saisies;
    private boolean zone1Active = true;
    private boolean zone3Active = true;
...
}

Kullanıcı sayısı çok fazla olduğunda, tüm bu kullanıcıların oturumlarının kapladığı bellek sorunu yaratabilir. Bu nedenle, oturum boyutunu en aza indirmek gerekir. APU modeli (Tek Sayfalı Uygulama), oturumu istemci tarafında yönetmeye ve oturumsuz bir web sunucusu kullanmaya olanak tanır. Aslında, tek sayfa başlangıçta tarayıcı tarafından yüklenir. Bu sayfa ile birlikte, ona eşlik eden JavaScript dosyası da gelir. Sayfa yeniden yüklenmediği için, bu JS dosyası, ilk yüklendiği haliyle tarayıcıda kalıcı olarak kalır. Böylece, kullanıcının çeşitli eylemlerine ilişkin bilgileri depolamak için bu dosyanın küresel değişkenlerini kullanabiliriz. Şimdi bunu inceleyeceğiz. Sadece istemci tarafında oturumu yönetmekle kalmayıp, sunucuya en az yük bindirmek amacıyla JS uygulamasını yeniden tasarlayacağız.

7.6.2. [/ajax-12] eylemi

  

[/ajax-12] eylemi şöyledir:


    @RequestMapping(value = "/ajax-12", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String ajax12() {
        return "vue-12";
}

[vue-12.xml] görünümü şöyledir:

  

<!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>
  • Bu görünüm, 9. satırda kullanılan JS komut dosyası dışında [vue-09] görünümüyle aynıdır;

Görüntülenen görünüm şöyledir:

 

7.6.3. [Rafraîchir] düğmesini yöneten JS kodu

  

[local12.js] dosyasının kodu şöyledir:


// küresel değişkenler
var content;
var loading;
var erreur;
var page1;
var page2;
var value1;
var value2;
var session = {
        "cpt1" : 0,
        "cpt3" : 0
    };

// belge yüklenirken
$(document).ready(function() {
    // sayfanın çeşitli bileşenlerinin referansları alınır
    loading = $("#loading");
    loading.hide();
    erreur = $("#erreur");
    erreur.hide();
    content = $("#content");
});
  • 17-21. satırlar: ana sayfa yüklendiğinde, [loading, erreur, content] ile tanımlanan üç bileşenin referansları 2-4. satırlardaki küresel değişkenlere kaydedilir;
  • satır 5-6: iki sayfayı kaydetmek için;
  • 7-8. satırlar: [Valider] bağlantısı tarafından gönderilen iki değeri kaydetmek için;
  • 9. satır: oturum. Bu satır, [cpt1, cpt3] sayaçlarının değerlerini istemci tarafında saklar;

[postForm] işlevi, [Rafraîchir] düğmesine yapılan tıklamayı yönetir:


function postForm() {
    console.log("postForm");
    // oturum bilgisi gönderilir
    var post = JSON3.stringify(session);
    // manuel olarak bir Ajax çağrısı yapılır
    $.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
    })
}

Önceki sürümle arasındaki farklar şunlardır:

  • 7. satırdaki URL farklıdır;
  • 4. satır: Daha önce bir değer gönderilmezken, artık bir değer gönderiliyor. Bu değer, oturumun jSON dizesidir. İşleyiş şu şekildedir:
    • istemci oturumu sunucuya gönderir,
    • sunucu bunu değiştirip geri gönderir,
    • istemci yeni oturumu kaydeder;
  • 10. satır: jSON formatında bir belge gönderilir (posta değeri);
  • 13. satır: Posta ile gönderilecek bir şey var;
  • satır 15-20: [beforeSend, error, complete] işlevleri önceki sürümdekilerle aynıdır. Yalnızca [success] işlevi değişir (satır 16-18);

7.6.4. [/ajax-13] eylemi

  

[/ajax-13] eylemi şöyledir:


    @RequestMapping(value = "/ajax-13", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    @ResponseBody()
    public JsonResult13 ajax13(@RequestBody SessionModel2 session2, HttpServletRequest request,    HttpServletResponse response) {
    ...
}
  • 3. satır: [@RequestBody SessionModel2 session2] parametresi, istemci tarafından gönderilen oturumu alır. Bu oturumun türü, aşağıdaki gibi [SessionModel2]'tir:
  

package istia.st.springmvc.models;

import java.io.Serializable;

public class SessionModel2 implements Serializable {

    private static final long serialVersionUID = 1L;
    // iki sayaç
    private int cpt1 = 0;
    private int cpt3 = 0;

    // getter ve setter'lar
    ...
}

[SessionModel2] oturumu aşağıdaki öğeleri depolar:

  • 9. satır: [Zone 1] alanının görüntülendiği [cpt1] oturumlarının sayısı;
  • satır 10: [Zone 3] alanının görüntülendiği [cpt3] sayısını;

Şimdi [/ajax-13] eyleminin kodunu incelemeye devam edelim:


    @RequestMapping(value = "/ajax-13", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    @ResponseBody()
    public JsonResult13 ajax13(@RequestBody SessionModel2 session2, HttpServletRequest request,    HttpServletResponse response) {
    ...
}
  • 3. satırda, yanıtın [JsonResult13] türü şu şekildedir:
  

package istia.st.springmvc.models;

public class JsonResult13 {

    // veri
    private String page2;
    private String zone1;
    private String zone3;
    private String erreur;
    private String value1;
    private Integer value2;

    // oturum
    private SessionModel2 session;

    // alıcı ve ayarlayıcılar
    ...
}
  • 14. satır: oturum. Sunucu, bunu saklanması için istemciye geri gönderir;
  • 6. satır: 2 numaralı sayfanın HTML içeriği;
  • 7. satır: [Zone 1] alanının HTML içeriği;
  • 8. satır: [Zone 3] alanının HTML içeriği;
  • 9. satır: varsa hata mesajı;
  • 10-11. satırlar: sunucu tarafından hesaplanan ve 2 numaralı sayfada görüntülenen iki bilgi;

[/ajax-13] eyleminin kodunu incelemeye devam edelim:


@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 bağlamı
        WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
        // yanıt
        JsonResult13 result = new JsonResult13();
        result.setSession(session2);
        // rastgele bir yanıt verilir
        int cas = new Random().nextInt(3);
        switch (cas) {
        case 0:
            // alan 1 etkin
            setZone1B(thymeleafContext, result);
            return result;
        case 1:
            // bölge 3 aktif
            setZone3B(thymeleafContext, result);
            return result;
        case 2:
            // alan 1 ve 3 aktif
            setZone1B(thymeleafContext, result);
            setZone3B(thymeleafContext, result);
            return result;
        }
        return null;
    }
  • 9. satır: oturum, eylemin sonucuna eklenir;

[setZone1B] alanını etkinleştiren [setZone1B] yöntemi şöyledir:


    private void setZone1B(WebContext thymeleafContext, JsonResult13 result) {
        // oturum geri alınır
        SessionModel2 session = result.getSession();
        // bölge 1 aktif
        // HTML akışı
        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);
        // oturum
        session.setCpt1(cpt1);
}
  • 3. satır: oturum alınır. Bu oturum, 12. satırda yeni [cpt1] sayacıyla değiştirilecektir. Bu oturumun müşteriye geri gönderileceğini hatırlatırız;
  • 10. satır: yeni [Zone 1] alanı;

[Zone 3] alanını etkinleştiren [setZone3B] yöntemi de benzerdir:


private void setZone3B(WebContext thymeleafContext, JsonResult13 result) {
        // oturum geri alınır
        SessionModel2 session = result.getSession();
        // bölge 3 aktif
        // akış 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);
        // oturum
        session.setCpt3(cpt3);
    }

7.6.5. [/ajax-13] eyleminin yanıtının işlenmesi

İstemci tarafında, [/ajax-13] eyleminin jSON yanıtı, aşağıdaki [onSuccess] işlevi tarafından işlenir:


function postForm() {
    console.log("postForm");
    // oturum gönderiliyor
    var post = JSON3.stringify(session);
    // Ajax çağrısı elle yapılıyor
    $.ajax({
    ...
        success : function(data) {
            // oturum kaydediliyor
            session = data.session;
            // iki alanı da güncelliyoruz
            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();
            }
        },
...
    })
}
  • 12-17. satırlar: Sunucu yanıtın [zone1] alanına bir değer yazmışsa, [Zone 1] alanı yeniden oluşturulmalı ve görüntülenmelidir; aksi takdirde bu alan gizlenmelidir;
  • 18-23. satırlar: [Zone 3] alanı için de aynı mantık geçerlidir;

7.6.6. [Page 2] sayfasının görüntülenmesi

[Valider] bağlantısının HTML kodu şöyledir:


<a href="javascript:valider()">Valider</a>

JS [valider] işlevi şöyledir:


// girilen değerlerin doğrulanması
function valider() {
    // 1. sayfa kaydediliyor
    page1 = content.html();
    // girilen değerler kaydedilir
    value1 = $("#text1").val().trim();
    value2 = $("#text2").val().trim();
    // değer gönderiliyor
    var post = JSON3.stringify({
        "value1" : value1,
        "value2" : value2,
        "pageRequired" : page2 ? false : true
    });
    // manuel olarak Ajax çağrısı yapılır
    $.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
    })
}
  • POST komutunu çalıştıracağız; bu komut normalde bizi 2 numaralı sayfaya yönlendirecektir;
  • 4. satır: Daha sonra geri dönebilmek için 1 numaralı sayfayı kaydediyoruz;
  • 6-7. satırlar: önceki işlem, girilen değerleri kaydetmez, sadece sayfanın HTML kodunu kaydeder. Bu nedenle şimdi formda girilen iki değeri de kaydediyoruz;
  • 9-13. satırlar: Girilen iki değer, jSON adlı bir dizgeye eklenir. Gönderilecek olan bu dizgedir;
  • 12. satır: Sunucuya 2 numaralı sayfaya ihtiyacımız olup olmadığını belirtmek için bir parametre. Şu şekilde ilerleyeceğiz. 2 numaralı sayfayı ilk kez isteyeceğiz, ardından JS [page2] değişkeninde saklayacağız. Bundan sonra, bu sayfayı bir daha istemeyeceğiz. Sayfayı önbellekten kullanacağız. 2. satırda, [pageRequired] değişkeni, [page2] değişkeni boşsa [true] değerine, aksi takdirde [false] değerine eşittir;
  • oturumun gönderilmediğine dikkat edelim. Aslında oturum, 20. satırdaki [/ajax-14] eyleminin değiştirmediği sayaçları saklar;

7.6.7. [/ajax-14] eylemi

[/ajax-14] eylemi şöyledir:


@RequestMapping(value = "/ajax-14", method = RequestMethod.POST)
    @ResponseBody
    public JsonResult13 ajax14(@RequestBody @Valid PostAjax14 post, BindingResult bindingResult, Locale locale,    HttpServletRequest request, HttpServletResponse response) {
        ...
    }
  • 3. satır: yanıt her zaman [JsonResult13] türündedir;
  • 3. satır: gönderilen değer, aşağıdaki [PostAjax14] türünde kapsüllenir:

package istia.st.springmvc.models;

public class PostAjax14 extends PostAjax11A {

    // sayfa 2
    private boolean pageRequired;

    // getter ve setter'lar
    ...
}
  • 3. satır: [PostAjax14] sınıfı, önceki sürümdeki [PostAjax11A] sınıfını genişletir. Dolayısıyla, [value1, value2, pageRequired] yapısına sahiptir;

[/ajax-14] eylemi şu şekilde devam eder:


    @RequestMapping(value = "/ajax-14", method = RequestMethod.POST)
    @ResponseBody
public JsonResult13 ajax14(@RequestBody @Valid PostAjax14 post, BindingResult bindingResult, Locale locale,    HttpServletRequest request, HttpServletResponse response) {
        // Thymeleaf bağlamı
        WebContext thymeleafContext = new WebContext(request, response, request.getServletContext());
        // yanıt
        JsonResult13 result = new JsonResult13();
        // gönderim geçerli mi?
        if (bindingResult.hasErrors()) {
            // hata döndürülüyor
            result.setErreur(getErreursForModel(bindingResult));
            return result;
        }
        // 2. sayfa gönderiliyor
        result.setValue1(post.getValue1());
        result.setValue2(post.getValue2());
        // sayfa isteniyor mu?
        if (post.isPageRequired()) {
            result.setPage2(engine.process("vue-12-page2", thymeleafContext));
        }
        return result;
}
  • 9-13. satırlar: [value1, value2] ile gönderilen değerler geçersizse, bir hata mesajı gönderilir;
  • 15-16. satırlar: Normalde sunucu, gönderilen değerlerle bir hesaplama yapmalıdır. Burada ise, değerleri aldığını göstermek için sadece geri göndermekle yetinir;
  • satır 18-20: 2 numaralı sayfa, yalnızca istemci tarafından talep edildiğinde geri gönderilir. Satır 19'da, [vue-12-page2] görünümü yenidir:
 

<!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>
  • XML kodu, eskiden olduğu gibi artık Thymeleaf tarafından değerlendirilen değerler içermemektedir;
  • sunucu tarafından döndürülen [value1, value2] değerlerinin yerleştirileceği alanlar belirlendi. 9. satırda, [id='value1'], [value1]'in yerleştirileceği yeri belirtir. 13. satırda, [value2] için de durum aynıdır;

7.6.8. [/ajax-14] eyleminin yanıtının işlenmesi

[/ajax-14] eyleminin yanıtı, aşağıdaki [success] işlevi tarafından işlenir:


// girdilen değerlerin doğrulanması
function valider() {
    ...
    // Ajax çağrısı manuel olarak yapılıyor
    $.ajax({
        ...
        success : function(data) {
            // hata mı?
            if (data.erreur) {
                // hata mesajı görüntüleniyor
                erreur.html(data.erreur);
                erreur.show();
            } else {
                // hata yok
                erreur.hide();
                // 2. sayfa
                if (page2) {
                    // önbellekteki sayfa kullanılıyor
                    content.html(page2);
                } else {
                    // 2. sayfa kaydediliyor
                    page2 = data.page2;
                    // sayfa görüntüleniyor
                    content.html(data.page2);
                }
                // sunucudaki bilgilerle güncelleniyor
                $("#value1").text(data.value1);
                $("#value2").text(data.value2);
            }
        },
...
    })
}
  • 9-13. satırlar: sunucu bir hata döndürdüyse, bu hata görüntülenir;
  • 14-29. satırlar: Hata oluşmadığı durum. Bu durumda 2 numaralı sayfa görüntülenmelidir;
  • 17. satır: 2 numaralı sayfanın [page2] değişkeninde zaten kayıtlı olup olmadığına bakılır;
  • satır 19: bu durumda, 2 numaralı sayfayı görüntülemek için [page2] değişkeni kullanılır;
  • 24. satır: Aksi takdirde, sunucu tarafından sağlanan [data.page2] alanını kullanırız;
  • 22. satır: 2 numaralı sayfayı daha sonra tekrar istemek zorunda kalmamak için bu sayfayı belleğe kaydedilir;
  • 27-28. satırlar: 2 numaralı sayfada, sunucu tarafından gönderilen iki bilgi olan [value1, value2] görüntülenir;

7.6.9. 1 numaralı sayfaya geri dön

2 numaralı sayfadaki [Retour vers la page 1] bağlantısı şöyledir:


<a href="javascript:retourPage1()">Retour à la page 1</a>

JS [retourPage1] yöntemi şöyledir:


// 1. sayfaya geri dönülüyor
function retourPage1() {
    // 1. sayfa yeniden oluşturuluyor
    content.html(page1);
    // girdiler yeniden oluşturuluyor
    $("#text1").val(value1);
    $("#text2").val(value2);
}
  • Bu, 1 numaralı sayfa [page1] değişkeninde yerel olarak saklandığından sunucuyla etkileşim gerektirmeyen bir JS eylemidir;
  • 4. satır: 1 numaralı sayfa yeniden oluşturulur;
  • 6-7. satır: 1 numaralı sayfanın yalnızca HTML kısmı kaydedilmişti. Girişler kaydedilmemişti. Dolayısıyla bu girişleri yeniden oluşturmamız gerekiyor;

7.6.10. Sonuç

APU modelinin olanaklarından yararlanarak, web sunucusunu basitleştirmeyi başardık; sunucu artık durum bilgisi barındırmıyor (oturum yok) ve daha az yük altında:

  • JS ([retourPage1]) işlevindeki sunucu etkileşimini kaldırdık;
  • sunucu, 2 numaralı sayfayı yalnızca bir kez oluşturur;

7.7. Javascript kodunun katmanlara ayrılması

7.7.1. Giriş

Önceki uygulamanın JavaScript kodu giderek karmaşıklaşmaya başladı. Artık bu kodu katmanlara ayırmanın zamanı geldi. Uygulama, önceki haliyle kalacak. Yeni bir başlangıç sayfası tanımlamak dışında sunucuya dokunmayacağız. JS kodunu yeniden şekillendireceğiz.

Yeni mimari şu şekilde olacaktır:

7.7.2. Başlangıç sayfası

Uygulamayı başlatan eylem, aşağıdaki [/ajax-16] eylemidir:


    @RequestMapping(value = "/ajax-16", method = RequestMethod.GET, produces = "text/html; charset=UTF-8")
    public String ajax16() {
        return "vue-16";
}

Aşağıdaki [vue-16.xml] görünümünü görüntüler:


<!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>
  • 9-10. satırlar: JS kodu iki farklı dosyaya yerleştirilmiştir:
    • [local-ui], [présentation] katmanını uygular;
    • [local-dao], [DAO] katmanını uygular;
  

7.7.3. [DAO] katmanının uygulaması

7.7.4. Arayüz

[local-dao.js] içindeki [DAO] katmanı, [présentation] katmanına aşağıdaki arayüzü sunacaktır:


function updatePage1(deferred, sendMeBack)
[Rafraîchir] düğmesiyle 1. sayfayı güncellemek için

function getPage2(deferred, sendMeBack, value1, value2, pageRequired)
[Valider] düğmesi ile 2. sayfayı görüntülemek için

Javascript'te "arayüz" kavramı yoktur. Bu terimi, yalnızca [présentation] katmanının, [DAO] katmanıyla yalnızca yukarıdaki iki işlev aracılığıyla iletişim kuracağını belirtmek için kullandım.

7.7.5. Arayüzün Uygulanması

Uygulamanın ana yapısı şöyledir:


var session = {
    "cpt1" : 0,
    "cpt3" : 0
};

// Sayfa 1'i güncelle
function updatePage1(deferred, sendMeBack) {
...
}

// 2. sayfa
function getPage2(deferred, sendMeBack, value1, value2, pageRequired) {
...
}

[DAO] katmanının amacı, web sunucusuna yapılan HTTP isteklerinin ayrıntılarını [présentation] katmanından gizlemektir. Oturum da bu ayrıntılar arasında yer almaktadır. Dolayısıyla artık [DAO] katmanı tarafından yönetilmektedir.

7.7.5.1. [updatePage1] işlevi

[updatePage1] işlevi, [présentation] katmanı tarafından 1. sayfayı yenilemek için çağrılan işlevdir. Kod şöyledir:


// Sayfa 1 güncelleniyor
function updatePage1(deferred, sendMeBack) {
    // HTTP sorgusu
    executePost(deferred, sendMeBack, '/ajax-13', session);
}
  • 1. satır: [updatePage1] işlevi iki parametre alır:
    1. [jQuery.Deferred] türünde bir nesne. Bu nesne türü, üç farklı değer alabilen bir durumu depolar: ['pending', 'resolved', 'rejected']. [updatePage1] işlevine geldiğinde, [pending] durumundadır;
    2. [présentation] katmanında geri gönderilecek bir JS nesnesi;

Tüm HTTP istekleri, aşağıdaki [executePost] işlevi tarafından gerçekleştirilir:


// istek HTTP
function executePost(deferred, sendMeBack, url, post) {
    // Ajax çağrısı manuel olarak yapılıyor
    $.ajax({
        headers : {
            'Accept: 'application/json',
            'Content-Type: 'application/json'
        },
        url : url,
        type : 'POST',
        data : JSON3.stringify(post),
        dataType : 'json',
        success : function(data) {
            // oturum kaydediliyor
            if (data.session) {
                session = data.session;
            }
            // sonuç görüntüleniyor
            deferred.resolve({
                "status" : 1,
                "data" : data,
                "sendMeBack" : sendMeBack
            });
        },
        error : function(jqXHR) {
            // hatayı döndürür
            deferred.resolve({
                "status" : 2,
                "data" : jqXHR.responseText,
                "sendMeBack" : sendMeBack
            });
        }
    });
}
  • 1. satır: [executePost] işlevi, POST türünde bir Ajax çağrısı gerçekleştirir. Dört parametre bekler:
    1. [pending] durumundaki [jQuery.Deferred] türünde bir nesne;
    2. [présentation] katmanında geri gönderilecek bir JS nesnesi;
    3. POST'in URL'i;
    4. JS nesnesi olarak gönderilecek değer;
  • satır 5-8: jSON'in (satır 7) gönderme işlevi ve jSON'ten (satır 6) alınan değer;
  • satır 11: gönderilecek değer, jSON'e dönüştürülür;
  • satır 13-24: Ajax çağrısı başarılı olduğunda yürütülen işlev;
  • satır 19-23: sunucu bir oturum gönderdiyse, bu oturum kaydedilir;
  • 13-18. satırlar: [deferred] nesnesini [resolved] durumuna geçirirken, ayrıca aşağıdaki alanları içeren bir sonuç da aktarır:
    • [status]: başarılı olursa 1, başarısız olursa 2,
    • [data]: sunucudan gelen jSON yanıtı,
    • [sendMeBack]: işlevin ikinci parametresi; bu, çağıranın almak istediği bir nesnedir;
  • 17-31. satırlar: Ajax çağrısı başarısız olduğunda yürütülen işlev. Daha önce yaptığımızın aynısını yapıyoruz, ancak iki fark var:
    • [status], bir hatayı belirtmek için 2'ye ayarlanır;
    • [data] yine sunucudan gelen jSON yanıtıdır, ancak farklı bir şekilde elde edilmiştir;

7.7.5.2. [getPage2] işlevi

[getPage2] işlevi şöyledir:


// sayfa 2
function getPage2(deferred, sendMeBack, value1, value2, pageRequired) {
    // istek HTTP
    executePost(deferred, sendMeBack, '/ajax-14', {
        "value1" : value1,
        "value2" : value2,
        "pageRequired" : pageRequired,
    });
}
  • İşlev aşağıdaki parametreleri alır:
    1. [deferred]: [jQuery.Deferred] türünde ve [pending] durumunda bir nesne,
    2. [sendMeBack]: [présentation] katmanında geri gönderilecek bir JS nesnesi,
    3. [value1]: Sayfa 1'deki ilk giriş,
    4. [value2]: 2. sayfadaki ikinci giriş,
    5. [pageRequired]: sunucuya 2 numaralı sayfanın HTML akışını gönderip göndermemesi gerektiğini belirten bir boole değeri;
  • gerekli HTTP isteğini yürütmek için [executePost] işlevi çağrılır;

7.7.6. [présentation] katmanı

[présentation] katmanı, [local-ui.js] dosyası tarafından uygulanır. Bu dosya, önceki [DAO] katmanını kullanmak üzere yeniden düzenlenmiş [local12.js] dosyasının kodunu içerir. Yalnızca iki işlev değişmiştir: [postForm] ve [valider].

7.7.6.1. [postForm] işlevi

[postForm] işlevi şöyledir:


// Sayfa 1 güncelleniyor
function postForm() {
    // Sayfa 1 güncelleniyor
    var deferred = $.Deferred();
    loading.show();
    updatePage1(deferred, {
        'gönderen: "postForm",
        'bilgi: 10
    });
    // sonuçların görüntülenmesi
    deferred.done(postFormDone);
}
  • 4. satır: bir [jQuery.Deferred] nesnesi oluşturulur. Varsayılan olarak, bu nesne [pending] durumundadır;
  • 5. satır: Bekleme görüntüsü görüntülenir
  • 6-9. satırlar: [updatePage1] işlevi yürütülür. Bunun ne işe yarayabileceğini göstermek amacıyla, sadece örnek olarak [sendMeBack] adlı hayali bir nesne aktarılır;
  • 11. satır: [deferred.done] işlevinin parametresi, kendisi de bir işlevdir. Bu, [deferred] nesnesinin durumu [resolved] durumuna geçtiğinde çalıştırılması gereken işlevdir. Az önce, DAO [executePost] işlevinin, sunucudan yanıt alındığında bu nesnenin durumunu [resolved]'e aktardığını gördük. Bu, [postFormDone] işlevi çalıştırıldığında sunucudan yanıtın alınmış olduğu anlamına gelir;

[postFormDone] işlevi şu şekildedir:


function postFormDone(result) {
    // bekleme sonu
    loading.hide();
    // veriler alınıyor
    var data = result.data
    // demo için
    console.log(JSON3.stringify(result.sendMeBack));
    // durum analiz ediliyor
    switch (result.status) {
    case 1:
        // iki alan güncelleniyor
        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:
        // hata görüntüleniyor
        erreur.html(data);
        break;
    }
}
  • 1. satır: Alınan [result] parametresi, [executePost] işlevindeki [deferred.resolve] yöntemine aktarılan parametredir; örneğin:

            // sonuç veriliyor
            deferred.resolve({
                "status" : 1,
                "data" : data,
                "sendMeBack" : sendMeBack
});
  • 5. satır: sunucudan gelen yanıt alınır;
  • satır 10-24: önceki sürümde [postForm] işlevinin içindeki [onSuccess] işlevinde bulunan kod buradadır;
  • 25-28. satırlar: önceki sürümde [postForm] işlevinin içindeki [onError] işlevinde bulunan kod;

7.7.6.2. [sendMeBack] parametresinin rolü

[sendMeBack] parametresi ne işe yarar? [updatePage1] işlevinin çağrı koduna bakalım:


// Sayfa 1 güncelleniyor
function postForm() {
    // Sayfa 1 güncelleniyor
    var deferred = $.Deferred();
    loading.show();
    updatePage1(deferred, {
        'gönderen: "postForm",
        'bilgi: 10
    });
    // sonuçların görüntülenmesi
    deferred.done(postFormDone);
}

ve [validerDone] işlevinin imzası:


function postFormDone(result) {
}

[postForm] fonksiyonu, [postFormDone] fonksiyonuna nasıl bilgi aktarabilir? Bu fonksiyonun tek bir parametresi vardır: [result]. Bu parametre, [DAO] katmanındaki [executePost] fonksiyonu tarafından oluşturulur. [postFormDone] işlevine bilgi aktarmak için, [postForm] işlevinin önce bu bilgileri [updatePage1] işlevine aktarması gerekir. Bu, [sendMeBack] parametresinin görevidir. Bu parametre şu şekilde kullanılır:


function postFormDone(result) {
    // bekleme sonu
    loading.hide();
    // veriler alınıyor
    var data = result.data
    // demo için
    console.log(JSON3.stringify(result.sendMeBack));
    // durum analiz ediliyor
    switch (result.status) {
...
  • 7. satırda, [postFormDone] fonksiyonu, [postForm] fonksiyonu tarafından başlangıçta DAO ve [updatePage1] fonksiyonlarına aktarılan [sendMeBack] parametresini bulmuştur;

7.7.7. [valider] işlevi

[valider] işlevi şu şekildedir:


// girdilen değerlerin doğrulanması
function valider() {
    // 1. sayfa kaydediliyor
    page1 = content.html();
    // girilen değerler kaydediliyor
    value1 = $("#text1").val().trim();
    value2 = $("#text2").val().trim();
    // hata yok
    erreur.hide();
    // 2. sayfa isteniyor
    var deferred = $.Deferred();
    loading.show();
    getPage2(deferred, {
        '"sender": 'onayla',
        'bilgi: 20
    }, value1, value2, page2 ? false : true);
    // sonuçların görüntülenmesi
    deferred.done(validerDone);
}

ve [validerDone] işlevi (18. satır) şu şekildedir:


function validerDone(result) {
    // bekleme sona erdi
    loading.hide();
    // veriler alınıyor
    var data = result.data
    // demo için
    console.log(JSON3.stringify(result.sendMeBack));
    // durum analiz ediliyor
    switch (result.status) {
    case 1:
        // hata mı?
        if (data.erreur) {
            // hata mesajı görüntüleniyor
            erreur.html(data.erreur);
            erreur.show();
        } else {
            // hata yok
            erreur.hide();
            // 2. sayfa
            if (page2) {
                // önbellekteki sayfa kullanılıyor
                content.html(page2);
            } else {
                // 2. sayfa kaydediliyor
                page2 = data.page2;
                // sayfa görüntüleniyor
                content.html(data.page2);
            }
            // sunucudaki bilgilerle güncelleniyor
            $("#value1").text(data.value1);
            $("#value2").text(data.value2);
        }
        break;
    case 2:
        // hata mesajı görüntüleniyor
        erreur.html(data);
        erreur.show();
        break;
    }
}
  • 5. satır: sunucudan yanıt alınır;
  • 10-32. satırlar: önceki sürümde [valider] işlevinin içindeki [onSuccess] işlevinde bulunan kod;
  • satır 34-38: önceki sürümde [valider] işlevinin içindeki [onError] işlevinde bulunan kod;

7.7.8. Testler

Uygulama eskisi gibi çalışmaya devam ediyor ve Chrome konsolunda, [postForm] ve [valider] işlevlerinin [sendMeBack] parametreleri görülebilir:

 

7.8. Conclusion

Bir Spring uygulamasının genel şemasına geri dönelim: MVC:

Sayfalara gömülü olan ve tarayıcıda çalıştırılan JavaScript sayesinde ve APU modeli sayesinde, kodu tarayıcıya taşıyabilir ve aşağıdaki mimariye ulaşabiliriz:

  • [2] istemci / [1] sunucu mimarisi söz konusudur; burada istemci ve sunucu jSON üzerinden iletişim kurar;
  • [1]'te, Spring web katmanı MVC, jSON formatında görünümler, görünüm parçaları ve veriler sunar;
  • [2]'te: uygulamanın başlatılması sırasında yüklenen görünüme gömülü JavaScript kodu katmanlara ayrılabilir:
    • [présentation] katmanı, kullanıcı etkileşimlerini yönetir,
    • [DAO] katmanı, [1] web sunucusu üzerinden verilere erişimi sağlar;
    • [métier] katmanı mevcut olmayabilir veya sunucunun yükünü hafifletmek amacıyla [métier] katmanının gizli olmayan bazı işlevlerini üstlenebilir;
  • [2] istemcisi, yine sunucunun yükünü azaltmak amacıyla belirli görünümleri önbelleğe alabilir. Oturumu yönetir;