Skip to content

6. Örnek uygulama-03: rdvmedecins-pf-ejb

Glassfish sunucusu için geliştirilen örnek uygulamanın yapısını hatırlayalım:

Bu mimaride herhangi bir değişiklik yapmıyoruz; tek değişiklik, web katmanının burada JSF ve Primefaces kullanılarak gerçekleştirilecek olmasıdır.

6.1. NetBeans projesi

Yukarıda, [métier] ve [DAO] katmanları, 01 numaralı örnek olan JSF / EJB / Glassfish projesine aittir. Bunları yeniden kullanacağız.

  
  • [mv-rdvmedecins-ejb-dao-jpa]: Örnek 01'deki [DAO] ve [JPA] katmanlarının EJB projesi,
  • [mv-rdvmedecins-ejb-metier]: Örnek 01’deki [métier] katmanının EJB projesi,
  • [mv-rdvmedecins-pf]: [web] katmanına ait proje / Primefaces – yeni,
  • [mv-rdvmedecins-app-ear]: Uygulamayı Glassfish sunucusuna dağıtmak için kurumsal proje – yeni.

6.2. Kurumsal proje

Kurumsal proje, yalnızca [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-metier] ve [mv-rdvmedecins-pf] adlı üç modülün Glassfish sunucusuna dağıtılması için kullanılır. NetBeans projesi şöyledir:

Proje, [pom.xml] dosyasında aşağıdaki şekilde tanımlanan bu üç [1] bağımlılığı için mevcuttur:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <artifactId>mv-rdvmedecins-app</artifactId>
    <groupId>istia.st</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <groupId>istia.st</groupId>
  <artifactId>mv-rdvmedecins-app-ear</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>ear</packaging>

  <name>mv-rdvmedecins-app-ear</name>

  ...
    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
            <version>${project.version}</version>
            <type>ejb</type>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-ejb-metier</artifactId>
            <version>${project.version}</version>
            <type>ejb</type>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>mv-rdvmedecins-pf</artifactId>
            <version>${project.version}</version>
            <type>war</type>
        </dependency>
    </dependencies>
</project>
  • 10-13. satırlar: kurumsal projenin Maven artefaktı,
  • 18-37. satırlar: projenin üç bağımlılığı. Bunların türlerine dikkat edin (23., 29. ve 35. satırlar).

Web uygulamasını çalıştırmak için bu kurumsal projeyi çalıştırmanız gerekecektir.

6.3. Primefaces web projesi

Primefaces web projesi şöyledir:

  • [1], projenin sayfaları. [index.xhtml] sayfası, projenin tek sayfasıdır. Bu sayfa, [form1.xhtml], [form2.xhtml] ve [erreur.xhtml] olmak üzere üç parçaya sahiptir. Diğer sayfalar yalnızca biçimlendirme amacıyla bulunmaktadır.
  • [2]'te Java bean'leri bulunur. application kapsamındaki [Application] bean'i ve session kapsamındaki [Form] bean'i. [Erreur] sınıfı bir hatayı kapsüller. [MyDataModel] sınıfı, Primefaces’in <dataTable> etiketine şablon olarak hizmet eder,
  • [3]'te uluslararasılaştırma için mesaj dosyaları,
  • [4]'te ise bağımlılıklar yer almaktadır. Web projesi, [DAO] katmanındaki EJB projesine bağımlıdır, [métier] katmanındaki EJB projesine ve [web] katmanındaki Primefaces'e bağımlıdır.

6.4. Proje yapılandırması

Proje yapılandırması, daha önce incelediğimiz Primefaces veya JSF projelerindeki yapılandırmayla aynıdır. Yapılandırma dosyalarını tekrar açıklamadan listeliyoruz.

 

[web.xml]: web uygulamasını yapılandırır.


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>  
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Production</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/index.xhtml</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>

</web-app>
  1. satırda, [index.xhtml] sayfasının uygulamanın ana sayfası olduğu dikkat çekmektedir.

[faces-config.xml]: uygulamayı yapılandırır JSF


<?xml version='1.0' encoding='UTF-8'?>

<!-- =========== FULL CONFIGURATION FILE ================================== -->

<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">

  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

[beans.xml]: Boş olmakla birlikte @Named açıklaması için gereklidir


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

[styles.css]: uygulamanın stil sayfası


.col1{
   background-color: #ccccff
}

.col2{
   background-color: #ffcccc
}

Primefaces kütüphanesi kendi stil sayfalarıyla birlikte gelir. Yukarıdaki stil sayfası, yalnızca bir istisna durumunda görüntülenecek olan ve uygulama tarafından yönetilmeyen bir sayfa için kullanılır. Bu durumda [exception.xhtml] sayfası görüntülenir.

[messages_fr.properties]: Fransızca mesaj dosyası


# düzen
layout.entete=Les M\u00e9decins Associ\u00e9s
layout.basdepage=ISTIA, universit\u00e9 d'Angers - application propuls\u00e9e par PrimeFaces et JQuery

# istisna
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite

# form 1
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour
form1.options=Options
form1.francais=Fran\u00e7ais
form1.anglais=Anglais
form1.rafraichir=Rafra\u00eechir
form1.precedent=Jour pr\u00e9c\u00e9dent
form1.suivant=Jour suivant
form1.agenda=Affiche l'agenda du m\u00e9decin choisi pour le jour choisi
form1.today=Aujourd'hui

# form 2
form2.titre=Agenda de {0} {1} {2} le {3}
form2.titre_detail=Agenda de {0} {1} {2} le {3}
form2.creneauHoraire=Cr\u00e9neau horaire
form2.client=Client
form2.accueil=Accueil
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
form2.valider=Valider
form2.annuler=Annuler
form2.erreur=Erreur
form2.emtyMessage=Pas de cr\u00e9neaux entr\u00e9s dans la base
form2.suppression.confirmation=Etes-vous s\u00fbr(e) ?
form2.suppression.message=Suppression d'un rendez-vous
form2.supprimer.oui=Oui
form2.supprimer.non=Non
form2.erreurClient=Client [{0}] inconnu
form2.erreurClient_detail=Client {0} inconnu
form2.erreurAction=Action non autoris\u00e9e
form2.erreurAction_detail=Action non autoris\u00e9e

# hata
erreur.titre=Une erreur s'est produite.
erreur.exceptions=Cha\u00eene des exceptions
erreur.type=Type de l'exception
erreur.message=Message associ\u00e9
erreur.accueil=Accueil

[messages_en.properties]: İngilizce mesaj dosyası


# düzen
layout.entete=Associated Doctors
layout.basdepage=ISTIA, Angers university - Application powered by PrimeFaces and JQuery

# istisna
exception.header=The following exceptions occurred
exception.httpCode=Error HTTP code
exception.message=Exception message
exception.requestUri=Url targeted when error occurred
exception.servletName=Servlet targeted's name when error occurred

# form 1
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=Date
form1.options=Options
form1.francais=French
form1.anglais=English
form1.rafraichir=Refresh
form1.precedent=Previous Day
form1.suivant=Next day
form1.agenda=Show the doctor's diary for the chosen doctor and the chosen day
form1.today=Today

# form 2
form2.titre={0} {1} {2}'' diary on {3}
form2.titre_detail={0} {1} {2}'' diary on {3}
form2.creneauHoraire=Time Period
form2.client=Client
form2.accueil=Welcome Page
form2.supprimer=Delete
form2.reserver=Reserve
form2.valider=Submit
form2.annuler=Cancel
form2.erreur=Error
form2.emtyMessage=No Time periods in the database
form2.suppression.confirmation=Are-you sure ?
form2.suppression.message=Booking deletion
form2.supprimer.oui=Yes
form2.supprimer.non=No
form2.erreurClient=Unknown Client {0}
form2.erreurClient_detail=Unknown Client [{0}]
form2.erreurAction=Unauthorized action
form2.erreurAction_detail=Action non autoris\u00e9e

# hata
erreur.titre=The following exceptions occurred
erreur.exceptions=Exceptions' chain
erreur.type=Exception type
erreur.message=Associated Message
erreur.accueil=Welcome

6.5. Sayfa şablonu [layout.xhtml]

[layout.xhtml] şablonu şöyledir:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <f:view locale="#{form.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('#{request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <table style="width: 1200px">
          <tr>
            <td colspan="2" bgcolor="#ccccff">
              <ui:include src="entete.xhtml"/>
            </td>
          </tr>
          <tr>
            <td style="width: 10px;" bgcolor="#ffcccc">
              <ui:include src="menu.xhtml"/>
            </td>
            <td>
              <p:outputPanel id="contenu">
                <ui:insert name="contenu">
                  <h2>Contenu</h2>
                </ui:insert>
              </p:outputPanel>
            </td>
          </tr>
          <tr bgcolor="#ffcc66">
            <td colspan="2">
              <ui:include src="basdepage.xhtml"/>
            </td>
          </tr>         
        </table>
      </h:form>
    </h:body>
  </f:view>
</html>

Bu şablonun tek değişken kısmı 28-30. satırlardaki alandır. Bu alan, id:form:içerik (27. satır) alanında bulunur. Bunu aklımızda tutalım. Bu alanı güncelleyen AJAX çağrıları, update=":form:içerik" özniteliğine sahip olacaktır. Ayrıca, form 15. satırdan başlar. Dolayısıyla, 28-30. satırlara eklenen parça bu forma eklenir.

Bu şablonun verdiği görünüm şöyledir:

Sayfanın dinamik kısmı, yukarıda çerçeveli alana eklenecektir.

6.6. [index.xhtml] sayfası

Proje her zaman aynı sayfayı, yani aşağıdaki [index.xhtml] sayfasını görüntüler:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <ui:fragment rendered="#{form.form1Rendered}">
        <ui:include src="form1.xhtml"/>
      </ui:fragment>
      <ui:fragment rendered="#{form.form2Rendered}">
        <ui:include src="form2.xhtml"/>
      </ui:fragment>
      <ui:fragment rendered="#{form.erreurRendered}">
        <ui:include src="erreur.xhtml"/>
      </ui:fragment>
    </ui:define>
  </ui:composition>
</html>
  • 8-9. satırlar: Bu XHTML parçası, [layout.xhtml] şablonunun dinamik alanına eklenecektir,
  • Sayfa üç alt parçadan oluşmaktadır:
  • [form1.xhtml], satır 10-12;
  • [form2.xhtml], satır 13-15;
  • [erreur.xhtml], satır 16-18.

Bu parçaların [index.xhtml]'te bulunup bulunmadığı, sayfayla ilişkili [Form.java] şablonundaki boole değerleriyle kontrol edilir. Dolayısıyla bu değerler üzerinde değişiklik yapıldığında, görüntülenen sayfa da değişir.

[form1.xhtml] parçacığının görüntüsü şöyledir:

[form2.xhtml] parçası şu şekilde görüntülenir:

[erreur.xhtml] parçası şu şekilde görüntülenir:

6.7. Projenin bean'leri

[utils] paketindeki sınıf daha önce tanıtılmıştı: [Messages] sınıfı, bir uygulamanın mesajlarının uluslararasılaştırılmasını kolaylaştıran bir sınıftır. Bu sınıf, 2.8.5.7. paragrafında incelenmiştir.

6.7.1. Application bean'i

[Application.java] bean'i, application kapsamına sahip bir bean'dir. Bu tür bean'lerin, salt okunur ve uygulamanın tüm kullanıcıları tarafından erişilebilir verileri depolamak için kullanıldığını hatırlayalım. Bu bean şu şekildedir:


package beans;

import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import rdvmedecins.metier.service.IMetierLocal;

@Named(value = "application")
@ApplicationScoped
public class Application {

  // iş katmanı
  @EJB
  private IMetierLocal metier;

  public Application() {
  }
  
  // alıcılar

  public IMetierLocal getMetier() {
    return metier;
  }
  
}
  • 8. satır: bean'e application adı verilir,
  • 9. satır: uygulama kapsamındadır,
  • 13-14. satırlar: [métier] katmanının yerel arayüzüne ilişkin bir referans, uygulama sunucusundaki EJB konteyneri tarafından bu bean'e enjekte edilecektir. Uygulamanın mimarisini hatırlayalım:

JSF uygulaması ile EJB ve [Metier] uygulamaları aynı JVM (Java Sanal Makinesi) içinde çalışacaktır. Dolayısıyla [JSF] katmanı, EJB'in yerel arayüzünü kullanacaktır. Hepsi bu kadar. [Application] bean'i başka hiçbir şey içermez. [métier] katmanına erişmek için diğer bean'ler bu bean'den onu arayacaklardır.

6.7.2. [Erreur] bean'i

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

  1. package beans;
    
    
    
        public class Erreur {
        
    
    
    
          public Erreur() {
        
      }
    
    
    
    
          // alan
        
    
          private String classe;
        
    
          private String message;
        
    
    
    
          // yapıcı
        
    
          public Erreur(String classe, String message){
        
    
            this.setClasse(classe);
        
    
            this.message=message;
        
      }
    
    
    
    
          // alıcı ve ayarlayıcılar
        
    ...  
    
    }
    
  • 9. satır, bir istisna atılmışsa istisna sınıfının adı,
  • 10. satır: bir hata mesajı.

6.7.3. [Form] bean'i

Kodu şu şekildedir:


package beans;

import java.io.IOException;
...

@Named(value = "form")
@SessionScoped
public class Form implements Serializable {

  public Form() {
  }
  
// Uygulama bean'i
  @Inject
  private Application application;

  // oturum önbelleği
  private List<Medecin> medecins;
  private List<Client> clients;
  private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
  private Map<Long, Client> hClients = new HashMap<Long, Client>();
  private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();

  // model
  private Long idMedecin;
  private Date jour = new Date();
  private Boolean form1Rendered = true;
  private Boolean form2Rendered = false;
  private Boolean erreurRendered = false;
  private String form2Titre;
  private AgendaMedecinJour agendaMedecinJour;
  private Long idCreneauChoisi;
  private Medecin medecin;
  private Long idClient;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;
  private Boolean erreur = false;
  private String identiteClient;
  private String action;
  private String msgErreurClient;
  private Boolean erreurClient;
  private String msgErreurAction;
  private Boolean erreurAction;
  private String locale = "fr";

  @PostConstruct
  private void init() {
    // doktorları ve müşterileri önbelleğe alıyoruz
    try {
      medecins = application.getMetier().getAllMedecins();
      clients = application.getMetier().getAllClients();
    } catch (Throwable th) {
      // hatayı kaydediyoruz
      prepareVueErreur(th);
      return;
    }

    // sözlükler
    for (Medecin m : medecins) {
      hMedecins.put(m.getId(), m);
    }
    for (Client c : clients) {
      hClients.put(c.getId(), c);
      hIdentitesClients.put(identite(c), c);
    }
  }

  ...

  // görünüm ekranı
  private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean erreurRendered) {
    this.form1Rendered = form1Rendered;
    this.form2Rendered = form2Rendered;
    this.erreurRendered = erreurRendered;
  }

  // hazırlık vueErreur
  private void prepareVueErreur(Throwable th) {
    // hata listesi oluşturuluyor
    erreurs = new ArrayList<Erreur>();
    erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
    while (th.getCause() != null) {
      th = th.getCause();
      erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
    }
// hata görünümü görüntülenir
    setForms(true, false, true);
  }

  // alıcı ve ayarlayıcılar
  ...
}
  • 6-8. satırlar: [Form] sınıfı, adı "form" olan ve kapsamı oturum olan bir bean'dir. Bu durumda sınıfın serileştirilebilir olması gerektiğini hatırlatırız,
  • 14-15. satırlar: form bean'i, uygulama bean'ine bir referansa sahiptir. Bu referans, uygulamanın çalıştığı servlet konteyneri tarafından enjekte edilecektir (@Inject anotasyonunun varlığı).
  • satır 17-44: [form1.xhtml, form2.xhtml, erreur.xhtml] sayfalarının şablonu. Bu sayfaların görüntülenmesi, satır 27-29'daki boole değerleri tarafından kontrol edilir. Varsayılan olarak [form1.xhtml] sayfasının görüntülendiğine dikkat edilmelidir (satır 27),
  • 46-47. satırlar: init yöntemi, sınıfın örneklendirilmesinden hemen sonra yürütülür (@PostConstruct anotasyonunun varlığı),
  • 50-51. satırlar: [métier] katmanından doktorlar ve müşteriler listesi istenir,
  • 59-65. satırlar: Her şey yolunda giderse, doktorlar ve müşteriler için sözlükler oluşturulur. Bunlar numaralarına göre indekslenir. Ardından, [form1.xhtml] sayfası görüntülenir (27. satır),
  • 54. satır: Hata olması durumunda, [erreur.xhtml] sayfasının şablonu oluşturulur. Bu şablon, 36. satırdaki hata listesidir,
  • 78-88. satırlar: [prepareVueErreur] yöntemi, görüntülenecek hata listesini oluşturur. [index.xhtml] sayfası daha sonra [form1.xhtml] ve [erreur.xhtml] parçalarını görüntüler (satır 87).

[erreur.xhtml] sayfası şöyledir:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <body>
    <p:panel header="#{msg['erreur.titre']}" closable="true" >
      <hr/>
      <p:dataTable value="#{form.erreurs}" var="erreur">
        <f:facet name="header">
          <h:outputText value="#{msg['erreur.exceptions']}"/>
        </f:facet>
        <p:column>
          <f:facet name="header">
            <h:outputText value="#{msg['erreur.type']}"/>
          </f:facet>
          <h:outputText value="#{erreur.classe}"/>
        </p:column>
        <p:column>
          <f:facet name="header">
            <h:outputText value="#{msg['erreur.message']}"/>
          </f:facet>
          <h:outputText value="#{erreur.message}"/>
        </p:column>
      </p:dataTable>
    </p:panel>
  </body>
</html>

Hata listesini görüntülemek için <p:dataTable> etiketini kullanır (12-28. satırlar). Bu, aşağıdakine benzer bir hata sayfası oluşturur:

Şimdi uygulamanın yaşam döngüsünün farklı aşamalarını tanımlayacağız. Kullanıcının her bir eylemi için, ilgili görünümleri ve olay işleyicilerini inceleyeceğiz.

6.8. Ana sayfanın görüntülenmesi

Her şey yolunda giderse, görüntülenen ilk sayfa [form1.xhtml] olur. Bu, aşağıdaki görünümü verir:

[form1.xhtml] sayfası şöyledir:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <p:toolbar>
    <p:toolbarGroup align="left">  
      ...  
    </p:toolbarGroup>
    <p:toolbarGroup align="right">  
 ...  
    </p:toolbarGroup>  
  </p:toolbar>
</html>

Ekran görüntüsünde çerçeveli olarak gösterilen araç çubuğu, Primefaces Toolbar bileşenidir. Bu bileşen 8-14. satırlarda tanımlanmıştır. Her biri <toolbarGroup> etiketiyle tanımlanan iki bileşen grubu içerir; bunlar 9-11. ve 12-14. satırlarda yer alır. Gruplardan biri araç çubuğunun soluna hizalanmış (9. satır), diğeri ise sağına hizalanmıştır (12. satır).

Sol gruptaki bazı bileşenleri inceleyelim:


<p:toolbar>
    <p:toolbarGroup align="left">  
      <h:outputText value="#{msg['form1.medecin']}"/>  
      <p:selectOneMenu value="#{form.idMedecin}" effect="fade">  
        <f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>  
        <p:ajax update=":formulaire:contenu" listener="#{form.hideAgenda}" />  
      </p:selectOneMenu>              
      <p:separator/>
      <h:outputText value="#{msg['form1.jour']}"/>
      <p:calendar id="calendrier" value="#{form.jour}" readOnlyInputText="true">
        <p:ajax event="dateSelect" listener="#{form.hideAgenda}" update=":formulaire:contenu"/>  
      </p:calendar>
      <p:separator/>
      <p:commandButton id="resa-agenda" icon="ui-icon-check" actionListener="#{form.getAgenda}" update=":formulaire:contenu"/>  
      <p:tooltip for="resa-agenda" value="#{msg['form1.agenda']}"/>  
      ...  
    </p:toolbarGroup>
...
  • 4-7. satırlar: bir efekt eklenmiş doktorlar açılır menüsü (effect="fade"),
  • 6. satır: bir AJAX davranışı. Açılır menüde bir değişiklik olduğunda, [Form] yöntemihideAgenda (listener="#{form.hideAgenda}") yöntemi çalıştırılacak ve :form:içerik dinamik alanı (update=":formulaire:contenu") güncellenecektir,
  • 8. satır: araç çubuğuna bir ayırıcı ekler,
  • 10-12. satırlar: tarih giriş alanı. Burada Primefaces takvimi kullanılır. Giriş alanı salt okunurdur (readOnlyInputText="true"),
  • 11. satır: bir AJAX davranışı. Tarih değiştiğinde, [Form].hideAgenda yöntemi çalıştırılacak ve dinamik :form:içerik alanı güncellenecektir,
  • 14. satır: bir düğme. Bu düğmeye tıklandığında, AJAX çağrısı [Form].getAgenda () yöntemine yönlendirilir; ardından şablon değiştirilir ve sunucudan gelen yanıt, :form:içerik dinamik alanını güncellemek için kullanılır,
  • 15. satır: <tooltip> etiketi, bir bileşene yardım balonu eklemeye olanak tanır. Bileşenin kimliği, tooltip'in for özniteliği ile belirtilir. Burada (for="resa-agenda") ifadesi, 14. satırdaki düğmeyi belirtir:

Bu sayfa aşağıdaki şablonla oluşturulmuştur:


@Named(value = "form")
@SessionScoped
public class Form implements Serializable {

  public Form() {
  }
  
  // oturum önbelleği
  private List<Medecin> medecins;
  private List<Client> clients;
  // şablon
  private Long idMedecin;
  private Date jour = new Date();
  
  // doktor listesi
  public List<Medecin> getMedecins() {
    return medecins;
  }

  // müşteri listesi
  public List<Client> getClients() {
    return clients;
  }

  // takvim
  public void getAgenda() {
    ...
  }
  • 12. satırdaki alan, sayfanın 4. satırındaki listenin değerini okuma ve yazma işlemleriyle besler. Sayfa ilk görüntülendiğinde, açılır menüde seçili değeri sabitler. Sayfa ilk görüntülendiğinde, idMedecin, null'e eşittir; dolayısıyla ilk doktor seçilecektir,
  • 16-18. satırlardaki yöntem, doktorlar açılır menüsünün öğelerini (sayfanın 5. satırı) oluşturur. Oluşturulan her seçeneğin etiketi (itemLabel) doktorun unvanı, soyadı ve adı olacak ve değeri (itemValue) ise doktorun kimliği olacaktır,
  • 13. satırdaki alan, sayfanın 10. satırındaki giriş alanına okuma/yazma yoluyla veri sağlar. İlk görüntülemede, bu nedenle günün tarihi görüntülenir,
  • 26-28. satırlar: getAgenda yöntemi, sayfanın 14. satırındaki [Agenda] düğmesine yapılan tıklamayı yönetir. Bu yöntem, JSF sürümündeki ile neredeyse aynıdır:

  // Uygulama bean'i
  @Inject
  private Application application;
  // oturum önbelleği
  private List<Medecin> medecins;
  private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
  // şablon
  private Long idMedecin;
  private Date jour = new Date();
  private Boolean form1Rendered = true;
  private Boolean form2Rendered = false;
  private Boolean erreurRendered = false;
  private AgendaMedecinJour agendaMedecinJour;
  private Long idCreneauChoisi;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;
  private Boolean erreur = false;
  
  public void getAgenda() {
    try {
      // doktor bilgisi alınır
      medecin = hMedecins.get(idMedecin);
      // belirli bir gün için doktorun randevu takvimi
      agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
      // 2 numaralı formun görüntülenmesi
      setForms(true, true, false);
    } catch (Throwable th) {
      // hataların görünümü
      prepareVueErreur(th);
    }
    // Şu anda seçilmiş bir zaman aralığı yok
    creneauChoisi = null;
}

Bu kod hakkında yorum yapmayacağız. Bu konu daha önce ele alınmıştı.

6.9. Bir doktorun randevu takvimini görüntüleme

6.9.1. Randevu takvimine genel bakış

Kullanım senaryosu şöyledir:

  • [1]'te bir doktor ([1]) ve bir gün ([2]) seçilir, ardından seçilen gün için doktorun randevu takvimi ([3]) istenir;
  • [4]'te, bu takvim araç çubuğunun altında görünür.

[form2.xhtml] sayfasının kodu şöyledir:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:c="http://java.sun.com/jsp/jstl/core">

  <body>
    <!-- bağlam menüsü -->
    <p:contextMenu for="agenda">  
      ...
    </p:contextMenu>  
    <!-- takvim -->
    <p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
                 selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
      <!-- zaman çizelgesi sütunu -->
      <p:column style="width: 100px">  
        ...
      </p:column>  
      <!-- müşteri sütunu -->
      <p:column style="width: 300px">  
        ...
      </p:column>  
    </p:dataTable>

    <!-- silme onayı RV -->
    <p:confirmDialog id="confirmDialog" message="#{msg['form2.suppression.confirmation']}"  
                     header="#{msg['form2.suppression.message']}" severity="alert" widgetVar="confirmation">                   
      ...                
    </p:confirmDialog>  

    <!-- hata mesajı -->
    <p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >  
      ...  
    </p:dialog>
    
    <!-- sunucu yanıtı yönetimi -->
    <script type="text/javascript">  
      ...
      }  
    </script> 
  </body>
</html>
  • 16-26. satırlar: Sayfanın ana öğesi, doktorun randevu takvimini gösteren <dataTable> tablosudur,
  • 12-14. satırlar: Randevu eklemek veya silmek için bir bağlam menüsü kullanacağız:
 
  • 29-32. satırlar: Kullanıcı bir randevuyu silmek istediğinde bir onay kutusu görüntülenecektir:
 
  • satır 35-37: Bir hata bildirmek için bir iletişim kutusu kullanılacak:
 
  • satır 40-43: Biraz JavaScript eklememiz gerekecek.

6.9.2. Randevu tablosu

Burada, 327. sayfadaki 5.15 numaralı paragrafta incelenen veri tablosu modelini ele alacağız.

Sayfanın ana öğesi olan, takvimi görüntüleyen tabloyu inceleyelim:


<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
                 selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
      <!-- zaman çizelgesi sütunu -->
      <p:column style="width: 100px">  
        ...
      </p:column>  
      <!-- müşteri sütunu -->
      <p:column style="width: 300px">  
        ...
      </p:column>  
    </p:dataTable>

Görünümü şu şekildedir:

Bu, [Form].getMyDataModel() (value="#{form.myDataModel}") kaynağıyla beslenen iki sütunlu bir tablodur (4-6 ve 8-10. satırlar). Aynı anda yalnızca bir satır seçilebilir (selectionMode="single"). Her POST işleminde, seçilen öğenin referansı [Form].creneauChoisi'e atanır (selection="#{form.creneauChoisi}").

Hatırlanacağı üzere, getAgenda yöntemi şablondaki aşağıdaki alanı başlatmıştır:



// şablon
private AgendaMedecinJour agendaMedecinJour;

Tablo şablonu, aşağıdaki [Form].getMyDataModel yönteminin çağrılmasıyla elde edilir (<dataTable> etiketinin value özniteliği):


  // dataTable şablonu
  public MyDataModel getMyDataModel() {
    return new MyDataModel(agendaMedecinJour.getCreneauxMedecinJour());
}

<p:dataTable> etiketine şablon olarak hizmet eden [MyDataModel] sınıfını inceleyelim:


package beans;

import javax.faces.model.ArrayDataModel;
import org.primefaces.model.SelectableDataModel;
import rdvmedecins.metier.entites.CreneauMedecinJour;

public class MyDataModel extends ArrayDataModel<CreneauMedecinJour> implements SelectableDataModel<CreneauMedecinJour> {

  // üreticiler
  public MyDataModel() {
  }

  public MyDataModel(CreneauMedecinJour[] creneauxMedecinJour) {
    super(creneauxMedecinJour);
  }

  @Override
  public Object getRowKey(CreneauMedecinJour creneauMedecinJour) {
    return creneauMedecinJour.getCreneau().getId();
  }

  @Override
  public CreneauMedecinJour getRowData(String rowKey) {
    // zaman aralığı listesi
    CreneauMedecinJour[] creneauxMedecinJour = (CreneauMedecinJour[]) getWrappedData();
    // anahtar bir uzun tamsayıdır
    long key = Long.parseLong(rowKey);
    // seçilen zaman aralığı aranıyor
    for (CreneauMedecinJour creneauMedecinJour : creneauxMedecinJour) {
      if (creneauMedecinJour.getCreneau().getId().longValue() == key) {
        return creneauMedecinJour;
      }
    }
    // hiçbir şey
    return null;
  }
}
  • 7. satır: [MyDataModel] sınıfı, <p:dataTable> etiketinin şablonudur. Bu sınıfın amacı, gönderilen rowkey öğesini bu satırla ilişkili öğeyle ilişkilendirmektir,
  • 7. satır: Sınıf, [SelectableDataModel] arayüzünü [ArrayDataModel] sınıfı aracılığıyla uygular. Bu, yapıcı parametresinin bir dizi olduğu anlamına gelir. <dataTable> etiketini besleyen bu dizidir. Burada dizinin her satırı, [CreneauMedecinJour] türünde bir öğeyle ilişkilendirilecektir,
  • 13-15. satırlar: oluşturucu, parametresini üst sınıfına aktarır,
  • 18-20. satırlar: Dizinin her satırı bir zaman dilimine karşılık gelir ve zaman diliminin kimliği (19. satır) ile tanımlanır. Sunucuya gönderilecek olan bu kimliktir,
  • 23. satır: Bir zaman aralığının kimliği sunucuya gönderildiğinde sunucu tarafında çalıştırılacak kod. Bu yöntemin amacı, bu kimliğe ilişkin [CreneauMedecinJour] nesnesinin referansını elde etmektir. Bu referans, <dataTable> etiketinin selection özniteliğinin hedefine atanacaktır:

<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">

Dolayısıyla, [Form].creneauChoisi alanı, eklenmek veya silinmek istenen [CreneauMedecinJour] nesnesinin referansını içerecektir.

6.9.3. Zaman aralıkları sütunu

Zaman aralıkları sütunu aşağıdaki kodla elde edilir:


<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
                 selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
      <!-- zaman çizelgesi sütunu -->
      <p:column style="width: 100px">  
        <f:facet name="header">  
          <h:outputText value="#{msg['form2.creneauHoraire']}"/> 
        </f:facet>  
        <div align="center">
          <h:outputFormat value="{0,number,#00}:{1,number,#00} - {2,number,#00}:{3,number,#00}">
            <f:param value="#{creneauMedecinJour.creneau.hdebut}" />
            <f:param value="#{creneauMedecinJour.creneau.mdebut}" />
            <f:param value="#{creneauMedecinJour.creneau.hfin}" />
            <f:param value="#{creneauMedecinJour.creneau.mfin}" />
          </h:outputFormat>
        </div>
      </p:column>  
  
      <!-- müşteri sütunu -->
      <p:column style="width: 300px">  
        ...
      </p:column>  
    </p:dataTable>
  • 5-7. satırlar: sütun başlığı,
  • 8-15. satırlar: sütunun mevcut öğesi. 9. satırda, görüntülenecek öğeleri biçimlendirmeye olanak tanıyan <h:outputFormat> etiketinin kullanıldığına dikkat edilmelidir. value parametresi, görüntülenecek karakter dizisini belirtir. {i,type,format} notasyonu, i numaralı parametreyi, bu parametrenin türünü ve formatını ifade eder. Burada 0'dan 3'e kadar numaralandırılmış 4 parametre vardır; bunların türü sayısaldır ve iki basamaklı olarak görüntülenecektir,
  • 10-13. satırlar: <h:outputFormat> etiketinin beklediği dört parametre.

6.9.4. Müşteri sütunu

Müşteri sütunu aşağıdaki kodla elde edilir:


<!-- takvim -->
    <p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
                 selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
      <!-- zaman çizelgesi sütunu -->
      ...  
      <!-- müşteri sütunu -->
      <p:column style="width: 300px">  
        <f:facet name="header">  
          <h:outputText value="#{msg['form2.client']}"/>  
        </f:facet>
        <ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
          <h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}" />
        </ui:fragment>
        <ui:fragment rendered="#{creneauMedecinJour.rv==null and form.creneauChoisi!=null and form.creneauChoisi.creneau.id==creneauMedecinJour.creneau.id}">
          ...
        </ui:fragment>
      </p:column>  
    </p:dataTable>
  • 8-10. satırlar: sütun başlığı,
  • 11-13. satırlar: söz konusu zaman aralığı için bir randevu olduğunda görüntülenen öğe. Bu durumda, randevu alınan müşterinin unvanı, adı ve soyadı görüntülenir,
  • satır 14-16: daha sonra tekrar ele alacağımız başka bir bölüm.

6.10. Randevuyu silme

Bir randevunun silinmesi aşağıdaki adımlarla gerçekleştirilir:

Bu işlemle ilgili görünüm şöyledir:


<!-- bağlam menüsü -->
    <p:contextMenu for="agenda">  
...
      <p:menuitem value="#{msg['form2.supprimer']}" onclick="confirmation.show()"/>
    </p:contextMenu>  
    <!-- takvim -->
    <p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
                 selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
      ... 
    </p:dataTable>

    <!-- silme onayı RV -->
    <p:confirmDialog id="confirmDialog" message="#{msg['form2.suppression.confirmation']}"  
       header="#{msg['form2.suppression.message']}" severity="alert" widgetVar="confirmation">                   
      <p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
                       oncomplete="handleRequest(xhr, status, args); confirmation.hide()">
        <f:setPropertyActionListener value="supprimer" target="#{form.action}"/>
      </p:commandButton>
      <p:commandButton value="#{msg['form2.supprimer.non']}" onclick="confirmation.hide()" type="button" />                
    </p:confirmDialog>  
  • 2-5. satırlar: veri tablosuyla ilişkili bir bağlam menüsü (for özniteliği). Bu menüde iki seçenek bulunur: [1]:
  • 4. satır: [Supprimer] seçeneği, 13-20. satırlardaki [2] iletişim kutusunun görüntülenmesini tetikler,
  • 15. satır: [Oui] seçeneğine tıklandığında, randevuyu silecek olan [Form.action] komutunun çalıştırılması tetiklenir. Normalde, seçilen öğede randevu yoksa bağlam menüsünde [Supprimer] seçeneği görünmemeli ve seçilen öğede randevu varsa [Réserver] seçeneği görünmemelidir. Bağlam menüsünü bu kadar incelikli hale getiremedik. İlk seçilen öğe için bunu başarabiliyoruz, ancak daha sonra bağlam menüsünün bu ilk seçim için edinilen yapılandırmayı koruduğunu fark ediyoruz. Bu durumda menü hatalı hale geliyor. Bu nedenle her iki seçeneği de koruduk ve kullanıcı randevusu olmayan bir öğeyi silerse ona bir geri bildirim sunmaya karar verdik,
  • 16. satır: oncomplete özniteliği, AJAX çağrısının yürütülmesinden sonra çalıştırılacak JavaScript kodunu tanımlamaya olanak tanır. Bu kod şu şekilde olacaktır:

<!-- hata mesajı -->
    <p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >  
      <h:outputText value="#{form.msgErreur}" />  
    </p:dialog>

    <!-- sunucu yanıtı yönetimi -->
    <script type="text/javascript">  
      function handleRequest(xhr, status, args) {  
        // hata mı?
        if(args.erreur) {  
          dlgErreur.show();  
        }  
      }  
    </script> 
  • 10. satır: JavaScript kodu, args sözlüğünün erreur özniteliğine sahip olup olmadığını kontrol eder. Eğer öyleyse, 2. satırdaki iletişim kutusunu (widgetVar özniteliği) görüntüler. Bu iletişim kutusu, [Form].msgErreur şablonunu görüntüler.

Bir randevunun silinmesini yönetmek için yürütülen koda bir göz atalım:


    <p:confirmDialog ...>                   
      <p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
                       ...>
        <f:setPropertyActionListener value="supprimer" target="#{form.action}"/>
      </p:commandButton>
      ...                
</p:confirmDialog>  
  • 2. satır: [Form].action yöntemi çalıştırılacaktır,
  • 4. satır: Yürütülmeden önce, action alanına 'supprimer' değeri atanacaktır.

[action] yöntemi şöyledir:


// RV üzerinde işlem
  public void action() {
    // istenen eyleme göre
    if (action.equals("supprimer")) {
      supprimer();
    }
    ...
  }
  
  public void supprimer() {
    // bir şey yapmak gerekiyor mu?
    Rv rv = creneauChoisi.getRv();
    if (rv == null) {
      signalerActionIncorrecte();
      return;
    }
    try {
      // Randevunun silinmesi
      application.getMetier().supprimerRv(rv);
      // takvimi güncelliyoruz
      agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
      // form2'yi görüntülüyoruz
      setForms(true, true, false);
    } catch (Throwable th) {
      // hataları görüntüleme
      prepareVueErreur(th);
    }
    // seçilen zaman aralığını sıfırlama
    creneauChoisi = null;
}
  • 4. satır: eylem 'sil' ise, [supprimer] yöntemi yürütülür,
  • 12. satır: Seçilen zaman aralığındaki randevu alınır. [creneauChoisi]'in, seçilen [CreneauMedecinJour] öğesinin referansı ile başlatıldığını hatırlatırız;
  • bu randevu varsa silinir (satır 19), takvim yeniden oluşturulur (satır 21) ve ardından yeniden görüntülenir (satır 23),
  • silme işlemi başarısız olursa hata sayfası görüntülenir (satır 26),
  • seçilen öğenin randevusu yoksa (satır 13), bu durumda kullanıcı randevusu olmayan bir zaman aralığına tıklamış demektir. Bu hata şu şekilde bildirilir:
 

[signalerActionIncorrecte] yöntemi şu şekildedir:


// hatalı bir işlemi bildirme
  private void signalerActionIncorrecte() {
    // seçilen zaman aralığını sıfırla
    creneauChoisi = null;
    // hata
    msgErreur = Messages.getMessage(null, "form2.erreurAction", null).getSummary();
    RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
  }
  • 4. satır: Seçim kaldırılır,
  • 6. satır: uluslararası bir hata mesajı oluşturulur,
  • 7. satır: AJAX çağrısının args sözlüğüne ('hata', true) özniteliği eklenir.

[Oui] düğmesinin XHTML koduna geri dönelim:


<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
oncomplete="handleRequest(xhr, status, args); confirmation.hide()">
  • 2. satır: [Form].action yönteminin yürütülmesinden sonra, handleRequest JavaScript yöntemi yürütülür:

    <!-- hata mesajı -->
    <p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >  
      <h:outputText value="#{form.msgErreur}" />  
    </p:dialog>

    <!-- sunucu yanıtı yönetimi -->
    <script type="text/javascript">  
      function handleRequest(xhr, status, args) {  
        // hata mı?
        if(args.erreur) {  
          dlgErreur.show();  
        }  
      }  
</script> 
  • 10. satır: args sözlüğünde 'erreur' adlı öznitelik olup olmadığına bakılır. Varsa, 2. satırdaki iletişim kutusu görüntülenir,
  • 3. satır: şablonla oluşturulan hata mesajı görüntülenir.

6.11. Randevu Alma

Randevu alma işlemi aşağıdaki sırayla gerçekleşir:

Bu eylemle ilgili görünüm şöyledir:


<!-- bağlam menüsü -->
    <p:contextMenu for="agenda">  
      <p:menuitem value="#{msg['form2.reserver']}" update=":formulaire:contenu" action="#{form.action}" oncomplete="handleRequest(xhr, status, args)">
        <f:setPropertyActionListener value="reserver" target="#{form.action}"/>
      </p:menuitem>
      ...
    </p:contextMenu>  
    <!-- takvim -->
    <p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
   selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
      <!-- zaman çizelgesi sütunu -->
      <p:column style="width: 100px">  
...
      </p:column>  
      <!-- müşteri sütunu -->
      <p:column style="width: 300px">  
        <f:facet name="header">  
          <h:outputText value="#{msg['form2.client']}"/>  
        </f:facet>
...
        <ui:fragment rendered="#{creneauMedecinJour.rv==null and form.creneauChoisi!=null and form.creneauChoisi.creneau.id==creneauMedecinJour.creneau.id}">
          <p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>
          <p:spacer width="50px"/>
          <p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
            <f:setPropertyActionListener value="valider" target="#{form.action}"/>
          </p:commandLink>
          <p:spacer width="50px"/>
          <p:commandLink action="#{form.action()}" value="#{msg['form2.annuler']}" update=":formulaire:contenu">
            <f:setPropertyActionListener value="annuler" target="#{form.action}"/>
          </p:commandLink>
        </ui:fragment>
      </p:column>  
    </p:dataTable>
...
  • 21-31. satırlar: Aşağıdakileri görüntüler:
  • 21. satır: Randevu yoksa, seçim yapılmışsa ve seçilen zaman aralığının kimliği tablodaki geçerli öğenin kimliğiyle eşleşiyorsa görüntüleme gerçekleşir. Bu koşul eklenmezse, parça tüm zaman aralıkları için görüntülenir,
  • satır 22: giriş alanı, yardımlı bir giriş alanı olacaktır. Burada çok sayıda müşteri olabileceği varsayılmaktadır,
  • 24-26. satırlar: [Valider] bağlantısı,
  • satır 28-30: [Annuler] bağlantısı.

Yardımlı giriş alanı aşağıdaki kodla oluşturulur:


<p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>

[Form].completeClients yöntemi, giriş alanına yazılan karakterlere dayanarak kullanıcıya önerilerde bulunmakla görevlidir:

 

Teklifler [Nom prénom titre] biçimindedir. [Form].completeClients yöntem kodunun içeriği şöyledir:


  // otomatik tamamlama yöntemi
  public List<String> completeClients(String query) {
    List<String> identites = new ArrayList<String>();
    // uygun müşteriler aranıyor
    for (Client c : clients) {
      String identite = identite(c);
      if (identite.toLowerCase().startsWith(query.toLowerCase())) {
        identites.add(identite);
      }
    }
    return identites;
  }

  private String identite(Client c) {
    return c.getNom() + " " + c.getPrenom() + " " + c.getTitre();
}
  • 2. satır: query, kullanıcı tarafından girilen karakter dizisidir,
  • 3. satır: öneri listesi. Başlangıçta liste boştur,
  • 5-10. satırlar: Müşterilerin [Nom prénom titre] kimlikleri oluşturulur. Bir kimlik query ile başlıyorsa (7. satır), öneriler listesine eklenir (8. satır).

6.12. Randevunun Onaylanması

Bir randevunun onaylanması aşağıdaki sırayla gerçekleşir:

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


          <p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
            <f:setPropertyActionListener value="valider" target="#{form.action}"/>
</p:commandLink>

Dolayısıyla, bu olayı yönetecek olan yöntem [Form].action()'dır. Bu sırada, [Form].action şablonu 'valider' dizesini almış olacaktır. Kod şu şekildedir:


  // Uygulama bean'i
  @Inject
  private Application application;
  // oturum önbelleği
...
  private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
  // şablon
  private Date jour = new Date();
  private Boolean form1Rendered = true;
  private Boolean form2Rendered = false;
  private Boolean erreurRendered = false;
  private AgendaMedecinJour agendaMedecinJour;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;
  private Boolean erreur = false;
  private String identiteClient;
  private String action;
  private String msgErreur;
  
  @PostConstruct
  private void init() {
    ...
    for (Client c : clients) {
      hClients.put(c.getId(), c);
      hIdentitesClients.put(identite(c), c);
    }
  }

  // RV üzerinde işlem
  public void action() {
    // istenen eyleme göre
...
    if (action.equals("valider")) {
      validerResa();
    }
}

  // Rv onayı
  public void validerResa() {
    // rezervasyonun onaylanması
    try {
      // müşteri mevcut mu?
      Boolean erreur = !hIdentitesClients.containsKey(identiteClient);
      if (erreur) {
        msgErreur = Messages.getMessage(null, "form2.erreurClient", new Object[]{identiteClient}).getSummary();
        RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
        return;
      }
      // Rv ekleniyor
      application.getMetier().ajouterRv(jour, creneauChoisi.getCreneau(), hIdentitesClients.get(identiteClient));
      // takvimi güncelliyoruz
      agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
      // form2 görüntüleniyor
      setForms(true, true, false);
    } catch (Throwable th) {
      // hatalar ekranı
      prepareVueErreur(th);
    }
    // seçilen zaman aralığı sıfırlanıyor
    creneauChoisi = null;
    // müşteri bilgilerini sıfırla
    identiteClient = null;
}
  • 33-35. satırlar: action alanının değeri nedeniyle, [validerResa] yöntemi çalıştırılacaktır,
  • 43. satır: Öncelikle müşterinin var olup olmadığı kontrol edilir. Nitekim, yardımlı giriş alanında kullanıcı, kendisine sunulan önerilerden yararlanmadan sabit bir giriş yapmış olabilir. Yardımlı giriş, [Form].identiteClient modeliyle ilişkilidir. Bu nedenle, modelin örneklendirilmesi sırasında oluşturulan identitesClients sözlüğünde bu kimliğin mevcut olup olmadığına bakılır (satır 20). Bu sözlük, [Nom prénom titre] türündeki bir müşteri kimliğine, müşterinin kendisini eşler (satır 25),
  • 44. satır: Müşteri mevcut değilse, tarayıcıya bir hata mesajı gönderilir,
  • satır 45: uluslararasılaştırılmış bir hata mesajı,
  • satır 46: AJAX çağrısının args sözlüğüne ('hata', true) özniteliği eklenir. AJAX çağrısı şu şekilde tanımlanmıştır:

<p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
            <f:setPropertyActionListener value="valider" target="#{form.action}"/>
</p:commandLink>

Yukarıdaki 3. satırda, [Valider] bağlantısının bir oncomplete özniteliği olduğu görülmektedir. Daha önce karşılaştığımız bir teknikle hata mesajının görüntülenmesini sağlayacak olan bu özniteliktir.

  • 50. satır: [métier] katmanından, seçilen gün (jour) için bir randevu eklenmesi, seçilen zaman aralığı (creneauChoisi.getCreneau()) ve seçilen müşteri (hIdentitesClients.get(identiteClient)) için bir randevu eklemesi istenir,
  • 52. satır: [métier] katmanından doktorun takvimini yenilemesi istenir. Eklenen randevu ile birlikte, uygulamanın diğer kullanıcıları tarafından yapılmış olabilecek tüm değişiklikler de görüntülenir,
  • 54. satır: [form2.xhtml] takvimi yeniden görüntülenir,
  • 57. satır: Bir hata oluşursa hata sayfası görüntülenir.

6.13. Randevu İptali

Bu, aşağıdaki sıraya karşılık gelir:

[form2.xhtml] sayfasındaki [Annuler] düğmesi şöyledir:


<p:commandLink action="#{form.action()}" value="#{msg['form2.annuler']}" update=":formulaire:contenu">
            <f:setPropertyActionListener value="annuler" target="#{form.action}"/>
          </p:commandLink>

Böylece [Form].action yöntemi çağrılır:


// RV üzerinde işlem
  public void action() {
    // istenen işleme göre
...
    if (action.equals("annuler")) {
      annulerRv();
    }
  }
  
// Randevu iptali
  public void annulerRv() {
    // form2 görüntülenir
    setForms(true, true, false);
    // seçilen zaman aralığı sıfırlanır
    creneauChoisi = null;
    // müşteri bilgilerini sıfırla
    identiteClient = null;
  }

6.14. Takvimde gezinme

Araç çubuğu, takvimde gezinmenizi sağlar:

Yukarıdaki ekran görüntülerinde gösterilmese de, takvim seçilen yeni günün randevuları ile güncellenir.

[form1.xhtml] dosyasında ilgili üç düğmenin etiketleri şunlardır:


  <p:toolbar>
    <p:toolbarGroup align="left">  
...
      <h:outputText value="#{msg['form1.jour']}"/>
      <p:calendar id="calendrier" value="#{form.jour}" readOnlyInputText="true">
        <p:ajax event="dateSelect" listener="#{form.hideAgenda}" update=":formulaire:contenu"/>  
      </p:calendar>
      <p:separator/>
      <p:commandButton id="resa-agenda" icon="ui-icon-check" actionListener="#{form.getAgenda}" update=":formulaire:contenu"/>  
      <p:tooltip for="resa-agenda" value="#{msg['form1.agenda']}"/>  
      <p:commandButton id="resa-precedent" icon="ui-icon-seek-prev" actionListener="#{form.getPreviousAgenda}" update=":formulaire:contenu"/>  
      <p:tooltip for="resa-precedent" value="#{msg['form1.precedent']}"/>  
      <p:commandButton id="resa-suivant" icon="ui-icon-seek-next" actionListener="#{form.getNextAgenda}" update=":formulaire:contenu"/>          
      <p:tooltip for="resa-suivant" value="#{msg['form1.suivant']}"/>  
      <p:commandButton id="resa-today" icon="ui-icon-home" actionListener="#{form.today}" update=":formulaire:contenu"/>          
      <p:tooltip for="resa-today" value="#{msg['form1.today']}"/>  
    </p:toolbarGroup>
    <p:toolbarGroup align="right">  
      ...  
    </p:toolbarGroup>  
</p:toolbar>

[Form].getPreviousAgenda, [Form].getNextAgenda, [Form].today yöntemleri şunlardır:


private Date jour = new Date();

public void getPreviousAgenda() {
    // bir önceki güne geç
    Calendar cal = Calendar.getInstance();
    cal.setTime(jour);
    cal.add(Calendar.DAY_OF_YEAR, -1);
    jour = cal.getTime();
    // takvim
    if (form2Rendered) {
      getAgenda();
    }
  }

  public void getNextAgenda() {
    // bir sonraki güne geçiliyor
    Calendar cal = Calendar.getInstance();
    cal.setTime(jour);
    cal.add(Calendar.DAY_OF_YEAR, 1);
    jour = cal.getTime();
    // takvim
    if (form2Rendered) {
      getAgenda();
    }
  }

  // bugünün ajandası
  public void today() {
    jour = new Date();
    // takvim
    if (form2Rendered) {
      getAgenda();
    }
}
  • 1. satır: takvimin görüntülenme günü,
  • 5. satır: bir takvim kullanılır,
  • 6. satır: takvimin güncel tarihine göre başlatılır,
  • 7. satır: takvimden bir gün çıkarılır,
  • satır 8: ve takvimin görüntülenme günü ile yeniden başlatılır,
  • 11. satır: Takvim görüntüleniyorsa yeniden görüntülenir. Zira kullanıcı, takvim görüntülenmeden de araç çubuğunu kullanabilir.

Diğer yöntemler de buna benzer.

6.15. Görüntüleme dilini değiştirme

Dil değişikliği, araç çubuğundaki menü düğmesi aracılığıyla yönetilir:

Menü düğmesinin etiketleri şunlardır:


<p:toolbar>
    <p:toolbarGroup align="left">  
...  
    </p:toolbarGroup>
    <p:toolbarGroup align="right">  
      <p:menuButton value="#{msg['form1.options']}">  
        <p:menuitem id="menuitem-francais" value="#{msg['form1.francais']}" actionListener="#{form.setFrenchLocale}" update=":formulaire"/>  
        <p:menuitem id="menuitem-anglais" value="#{msg['form1.anglais']}" actionListener="#{form.setEnglishLocale}" update=":formulaire"/>  
        <p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>  
      </p:menuButton>  
    </p:toolbarGroup>  
  </p:toolbar>

Şablonda yürütülen yöntemler şunlardır:


private String locale = "fr";

  public void setFrenchLocale() {
    locale = "fr";
    // sayfa yeniden yükleniyor
    redirect();
  }

  public void setEnglishLocale() {
    locale = "en";
    // sayfa yeniden yükleniyor
    redirect();
  }

  private void redirect() {
    // istemci servlete yönlendiriliyor
    ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
    try {
      ctx.redirect(ctx.getRequestContextPath());
    } catch (IOException ex) {
      Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
    }
}

3. ve 9. satırlardaki yöntemler, yalnızca 1. satırdaki locale alanını başlatır ve ardından istemci tarayıcısını aynı sayfaya yönlendirir. Yönlendirme, sunucunun tarayıcıdan başka bir sayfayı yüklemesini istediği bir yanıttır. Tarayıcı daha sonra bu yeni sayfaya GET yapar.

  • 17. satır: [ExternalContext], o anda çalışmakta olan servlete erişim sağlayan bir JSF sınıfıdır,
  • 19. satır: Yönlendirme yapılır. redirect yönteminin parametresi, istemci tarayıcısının yönlendirilmesi gereken sayfanın URL değeridir. Burada, uygulamamızın adı olan [/mv-rdvmedecins-pf] sayfasına yönlendirilmek istiyoruz:
  

[getRequestContextPath] yöntemi bu adı almamızı sağlar. Böylece uygulamamızın ana sayfası olan [index.xhtml] yüklenecektir. Bu sayfa, oturum kapsamındaki [Form] şablonuyla ilişkilidir. Bu şablon, [index.xhtml] sayfasının görünümünü kontrol eden üç boole değerini yönetir:


  private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;

Şablon oturum kapsamında olduğundan, bu üç boole değeri değerlerini korumuştur. Dolayısıyla [index.xhtml] sayfası, yönlendirme öncesindeki haliyle görüntülenecektir. Bu sayfa, aşağıdaki [layout.xhtml] facelet şablonu ile biçimlendirilmiştir:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <f:view locale="#{form.locale}">
    ....
  </f:view>
</html>
  1. satırdaki etiket, local özniteliği ile sayfanın görüntülenme dilini belirler. Dolayısıyla sayfa, duruma göre Fransızca veya İngilizce olarak görüntülenecektir. Peki, neden bir yönlendirme var? Dil değiştirme seçeneklerinin etiketlerine geri dönelim:

<p:toolbar>
    <p:toolbarGroup align="left">  
...  
    </p:toolbarGroup>
    <p:toolbarGroup align="right">  
      <p:menuButton value="#{msg['form1.options']}">  
        <p:menuitem id="menuitem-francais" value="#{msg['form1.francais']}" actionListener="#{form.setFrenchLocale}" update=":formulaire"/>  
        <p:menuitem id="menuitem-anglais" value="#{msg['form1.anglais']}" actionListener="#{form.setEnglishLocale}" update=":formulaire"/>  
        <p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>  
      </p:menuButton>  
    </p:toolbarGroup>  
</p:toolbar>

Bu etiketler başlangıçta, AJAX çağrısı yoluyla form kimliği alanını güncellemek üzere yazılmıştı (7. ve 8. satırlardaki update özniteliği). Ancak testlerde dil değişikliği her seferinde işe yaramadı. Bu sorunu çözmek için yönlendirme eklendi. Belki de sayfanın yeniden yüklenmesini sağlamak için etiketlere ajax='false' özniteliği de eklenebilirdi. Bu durumda yönlendirmeye gerek kalmazdı.

6.16. Listelerin yenilenmesi

Bu, aşağıdaki eyleme karşılık gelir:

 

[Rafraîchir] seçeneğiyle ilişkili etiket şöyledir:


<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>  

[Form].refresh yöntemi şöyledir:


  public void refresh() {
    // listeler yenileniyor
    init();
}

init yöntemi, [Form] bean'inin oluşturulmasından hemen sonra yürütülen yöntemdir. Amacı, veritabanındaki verileri modele önbelleğe almaktır:


// Uygulama bean'i
  @Inject
  private Application application;
  // oturum önbelleği
  private List<Medecin> medecins;
  private List<Client> clients;
  private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
  private Map<Long, Client> hClients = new HashMap<Long, Client>();
  private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
  ...

  @PostConstruct
  private void init() {
    // doktorlar ve müşteriler önbelleğe alınır
    try {
      medecins = application.getMetier().getAllMedecins();
      clients = application.getMetier().getAllClients();
    } catch (Throwable th) {
      ...
    }
    ...
    // sözlükler
    for (Medecin m : medecins) {
      hMedecins.put(m.getId(), m);
    }
    for (Client c : clients) {
      hClients.put(c.getId(), c);
      hIdentitesClients.put(identite(c), c);
    }
  }

init yöntemi, 5-9. satırlardaki listeleri ve sözlükleri oluşturur. Bu tekniğin dezavantajı, bu öğelerin artık veritabanındaki değişiklikleri (bir müşterinin, bir doktorun eklenmesi vb.) dikkate almamasıdır. refresh yöntemi, bu listelerin ve sözlüklerin yeniden oluşturulmasını zorlar. Bu nedenle, veritabanında bir değişiklik yapıldığında (örneğin yeni bir müşterinin eklenmesi gibi) her seferinde bu yöntem kullanılacaktır.

6.17. Conclusion

Az önce oluşturduğumuz uygulamanın mimarisini hatırlayalım:

Daha önce oluşturduğumuz JSF2 sürümünden büyük ölçüde yararlandık:

  • [métier], [DAO] ve [JPA] katmanları korunmuştur,
  • web katmanındaki [Application] ve [Form] bean'leri korunmuş, ancak kullanıcı arayüzünün zenginleştirilmesi nedeniyle bunlara yeni işlevler eklenmiştir,
  • kullanıcı arayüzü kökten değiştirilmiştir. Özellikle daha zengin işlevlere sahiptir ve daha kullanıcı dostudur.

Web arayüzünü oluşturmak için JSF'ten Primefaces'e geçiş, belirli bir deneyim gerektirir; çünkü başlangıçta kullanılabilir bileşenlerin çokluğu karşısında biraz kafamız karışır ve sonuçta hangilerini kullanacağımızı tam olarak bilemeyiz. Bu durumda, arayüz için istenen ergonomiye odaklanmak gerekir.

6.18. Eclipse Testleri

Örnek uygulamanın önceki sürümlerinde yaptığımız gibi, bu 03 sürümünü Eclipse ile nasıl test edeceğimizi göstereceğiz. Öncelikle, Örnek 03 [1]'in Maven projelerini Eclipse'e içe aktarıyoruz:

  • [mv-rdvmedecins-ejb-dao-jpa]: [DAO] ve [JPA] katmanları,
  • [mv-rdvmedecins-ejb-metier]: [métier] katmanı,
  • [mv-rdvmedecins-pf]: [web] katmanı, JSF ve Primefaces ile uygulanmıştır,
  • [mv-rdvmedecins-app]: [mv-rdvmedecins-app-ear] kurumsal projesinin üst projesi. Üst proje içe aktarıldığında, alt proje otomatik olarak
  • [2]'te, [mv-rdvmedecins-app-ear] kurumsal projesi yürütülür,
  • [3]'te Glassfish sunucusu seçildi,
  • [4]'te, [Servers] sekmesinde uygulama dağıtılmıştır. Uygulama kendiliğinden çalışmaz. URL [http://localhost:8080/mv-rdvmedecins-pf/] adresini bir tarayıcıda [5] olarak girmelisiniz: