Skip to content

9. Beispielanwendung 05: rdvmedecins-pfm-ejb

Zur Erinnerung: Die Struktur der für den Glassfish-Server entwickelten Beispielanwendung 01 JSF / EJB:

An dieser Architektur ändern wir nichts, abgesehen von der Webschicht, die hier mithilfe von JSF, PrimeFaces und PrimeFaces Mobile realisiert wird. Als Zielbrowser dient ein mobiler Browser.

Wir haben zwei Anwendungen mit GlassFish entwickelt:

  • Anwendung 01 verwendete JSF / EJB und hatte eine eher einfache Benutzeroberfläche,
  • Anwendung 03 verwendete PF / EJB und verfügte über eine reichhaltige Benutzeroberfläche.

Aufgrund der geringen Anzahl verfügbarer Komponenten in PrimeFaces Mobile werden wir zur schlichten Benutzeroberfläche von Anwendung 01 zurückkehren. Außerdem müssen sich die Ansichten an die reduzierte Bildschirmgröße von Mobilgeräten anpassen können.

9.1. Die Ansichten

Zur Veranschaulichung zeigen wir einige Ansichten der Anwendung, die im Simulator eines iPhone 4 ausgeführt wird:

  • in , die Startseite. Man beachte, dass diesmal der Name des Rechners angegeben werden musste (man kann auch dessen Adresse IP angeben), da mit dem Rechner localhost nichts angezeigt wurde,
  • in [2] die Ärzteauswahl,
  • bei [3] das gewünschte Datum für den Termin,
  • in [4] die Schaltfläche zum Aufrufen des Tageskalenders,
  • bei [5] zeigt die neue Ansicht die Zeitfenster des Arztes an,
  • in [6] eine Reihe von Schaltflächen zum Navigieren im Kalender,
  • in [7], eine Meldung zur Erinnerung an den Arzt und den Tag,
  • in [8] ein Terminfenster zur Buchung. Das machen wir,
  • in [9] die Auswahlansicht für den Kunden,
  • in [10] eine Nachricht zur Erinnerung an den Arzt, den Tag und das Zeitfenster des Termins,
  • in [11] die Kundenauswahl,
  • in [12] die Bestätigungsschaltfläche,
  • in [13]: Nach der Bestätigung kehren wir zum Kalender zurück,
  • in [14] ist das belegte Zeitfenster nun reserviert. Wir werden nun die Reservierung löschen,
  • in [15], wir bleiben in derselben Ansicht,
  • aber in [16] wurde der Termin gelöscht,

Auf der Startseite kann die Sprache geändert werden: [17], [18], [19]:

Schließlich wurde eine Fehleransicht vorgesehen:

9.2. Das NetBeans-Projekt

Das NetBeans-Projekt lautet wie folgt:

  
  • [mv-rdvmedecins-ejb-dao-jpa]: Projekt EJB der Ebenen [DAO] und [JPA] aus Beispiel 01,
  • [mv-rdvmedecins-ejb-metier]: Projekt EJB der Schicht [métier] aus Beispiel 01,
  • [mv-rdvmedecins-pfmobile]: Projekt der Ebene [web] / PrimeFaces Mobile – neu,
  • [mv-rdvmedecins-pfmobile-app-ear]: Unternehmensprojekt zur Bereitstellung der Anwendung auf dem Glassfish-Server – neu.

9.3. Das Unternehmensprojekt

Das Unternehmensprojekt dient ausschließlich der Bereitstellung der drei Module [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-metier] und [mv-rdvmedecins-pfmobile] auf dem GlassFish-Server. Das NetBeans-Projekt lautet wie folgt:

Das Projekt existiert ausschließlich für diese drei Abhängigkeiten [1], die in der Datei [pom.xml] wie folgt definiert sind:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
...

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

  <name>mv-rdvmedecins-pfmobile-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-pfmobile</artifactId>
            <version>${project.version}</version>
            <type>war</type>
        </dependency>
    </dependencies>
</project>
  • Zeilen 6–9: das Maven-Artefakt des Unternehmensprojekts,
  • Zeilen 14–33: die drei Abhängigkeiten des Projekts. Beachten Sie dabei unbedingt deren Typ (Zeilen 19, 25, 31).

Um die Webanwendung auszuführen, muss dieses Unternehmensprojekt ausgeführt werden.

9.4. Das PrimeFaces-Mobile-Webprojekt

Das PrimeFaces-Mobile-Webprojekt sieht wie folgt aus:

  • in [1], die Seiten des Projekts. Die Seite [index.xhtml] ist die einzige Seite des Projekts. Sie umfasst fünf Ansichten: [vue1.xhtml], [vue2.xhtml], [vue3.xhtml], [vueErreurs.xhtml] und [config.xhtml],
  • wobei [2] die Java-Beans enthält. Die Bean [Application] hat den Geltungsbereich application, die Bean [Form] den Geltungsbereich session. Die Klasse [Erreur] kapselt einen Fehler,
  • in [3] die Meldungsdateien für die Internationalisierung,
  • in [4] die Abhängigkeiten. Das Webprojekt hängt vom Projekt EJB der Schicht [DAO] ab, das Projekt EJB aus der Schicht [métier] sowie PrimeFaces Mobile für die Schicht [web].

9.5. Die Projektkonfiguration

Die Projektkonfiguration entspricht derjenigen der Primefaces-Projekte oder des Projekts JSF, die wir bereits behandelt haben. Wir listen die Konfigurationsdateien auf, ohne sie erneut zu erläutern.

 

[web.xml]: Konfiguriert die Webanwendung.


<?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.PROJECT_STAGE</param-name>
    <param-value>Development</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>

Beachten Sie in Zeile 26, dass die Seite [index.xhtml] die Startseite der Anwendung ist.

[faces-config.xml]: Konfiguriert die Anwendung 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>
    <default-render-kit-id>PRIMEFACES_MOBILE</default-render-kit-id>
  </application>
</faces-config>

[beans.xml]: leer, aber für die Anmerkung @Named erforderlich


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

[messages_fr.properties]: Die Datei mit den Meldungen auf Französisch


# Seite
page.titre=Les M\u00e9decins Associ\u00e9s
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy

# Ansicht1
vue1.header=Les M\u00e9decins Associ\u00e9s - R\u00e9servations

# Formular 1
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour (jj/mm/aaaa)
form1.date.requise=La date est n\u00e9cessaire
form1.date.invalide=La date est invalide
form1.date.invalide_detail=La date est invalide
form1.agenda=Agenda
form1.options=Options

# Formular 2
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
form2.retour=Retour
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
form2.precedent=Jour pr\u00e9c\u00e9dent
form2.suivant=Jour suivant
form2.today=Aujourd'hui

# Formular 3
form3.client=Client
form3.valider=Valider
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,Zahl,#00}-{6,Zahl,#00}h:{7,Zahl,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,Zahl,#00}-{6,Zahl,#00}h:{7,Zahl,#00}
form3.retour=Retour

# Fehler
erreur.titre=Une erreur s'est produite.

# Konfiguration
config.retour=Retour
config.titre=Configuration
config.langue=Langue
config.langue.francais=Fran\u00e7ais
config.langue.anglais=Anglais
config.valider=Valider

#Ausnahme
exception.titre=Application indisponible. Veuillez recommencer ult\u00e9rieurement.

[messages_en.properties]: die Datei mit den Meldungen auf Englisch


# Seite
page.titre=The Associated Doctors
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy

# Ansicht1
vue1.header=The Associated Doctors - Reservations

# Formular 1
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=day (dd/mm/yyyy)
form1.date.requise=The date is necessary
form1.date.invalide=Invalid date
form1.date.invalide_detail=invalid date
form1.agenda=Diary
form1.options=Options

# Formular 2
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
form2.retour=Back
form2.supprimer=Delete
form2.reserver=Reserve
form2.precedent=Previous Day
form2.suivant=Next Day
form2.today=Today

# Formular 3
form3.client=Patient
form3.valider=Validate
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,Zahl,#00}-{6,Zahl,#00}h:{7,Zahl,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,Zahl,#00}-{6,Zahl,#00}h:{7,Zahl,#00}
form3.retour=Back

# Fehler
erreur.titre=Some error happened

# Konfiguration
config.retour=Back
config.titre=Configuration
config.langue=Language
config.langue.francais=French
config.langue.anglais=English
config.valider=Validate

#Ausnahme
exception.titre=Application not available. Please try again later.

9.6. Die Seite [index.xhtml]

Das Projekt zeigt immer dieselbe Seite an, nämlich die folgende Seite [index.xhtml]:


<f:view xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:p="http://primefaces.org/ui"
        xmlns:pm="http://primefaces.org/mobile"
        contentType="text/html"
        locale="#{form.locale}">

  <pm:page title="#{msg['page.titre']}">
    <pm:view id="vue1">
      <ui:fragment rendered="#{form.form1Rendered}">
        <ui:include src="vue1.xhtml"/>
      </ui:fragment>
      <ui:fragment rendered="#{form.erreurInit}">
        <ui:include src="vueErreurs.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vue2">
      <ui:fragment rendered="#{form.form2Rendered}">
        <ui:include src="vue2.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vue3">
      <ui:fragment rendered="#{form.form3Rendered}">
        <ui:include src="vue3.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="vueErreurs">
      <ui:fragment rendered="#{form.erreurRendered}">
        <ui:include src="vueErreurs.xhtml"/>
      </ui:fragment>
    </pm:view>
    <pm:view id="config">
      <ui:include src="config.xhtml"/>
    </pm:view>
  </pm:page>    
</f:view>
  • Zeile 8: Die Seite ist internationalisiert (Attribut „locale“),
  • Zeile 10: Die Seite enthält fünf Ansichten: vue1 in Zeile 11, vue2 in Zeile 19, vue3 in Zeile 24, vueErreurs in Zeile 29, config in Zeile 34. Zu einem bestimmten Zeitpunkt ist jeweils nur eine dieser Ansichten sichtbar. Beim Start der Anwendung wird die Ansicht vue1 angezeigt. Hier stießen wir auf folgende Schwierigkeit: Wenn die Initialisierung der Anwendung erfolgreich verlief, muss „[vue1.xhtml]“ angezeigt werden, andernfalls muss „[vueErreurs.xhtml]“ angezeigt werden. Das Problem wurde gelöst, indem der Inhalt der Ansicht „vue1“ vom Modell verwaltet wird, das den booleschen Werten [Form].form1rendered (Zeile 12) und [Form].erreurInit (Zeile 15) Werte zuweist, um den Inhalt von „vue1“ (Zeile 11) festzulegen,

In einem Simulator hat die Ansicht [vue1.xhtml] die Darstellung [1], die Ansicht [vue2.xhtml] die Darstellung [2], die Ansicht [vue3.xhtml] die Darstellung [3]:

die Ansicht [vueErreurs.xhtml], die Darstellung [4], die Ansicht [config.xhtml], die Darstellung [5]:

9.7. Die Beans des Projekts

Die Klasse des Pakets [utils] wurde bereits vorgestellt: Die Klasse [Messages] ist eine Klasse, die die Internationalisierung der Meldungen einer Anwendung erleichtert. Sie wurde in Abschnitt 2.8.5.7 behandelt.

9.7.1. Die Bean „Application“

Die Bean [Application.java] ist eine Bean mit dem Geltungsbereich application. Wie bereits erwähnt, dient diese Art von Bean dazu, schreibgeschützte Daten zu speichern, die allen Benutzern der Anwendung zur Verfügung stehen. Diese Bean lautet wie folgt:


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 {

  // Geschäftsschicht
  @EJB
  private IMetierLocal metier;

  public Application() {
  }
  
  // Getter

  public IMetierLocal getMetier() {
    return metier;
  }
  
}
  • Zeile 8: Der Bean wird der Name „application“ gegeben,
  • Zeile 9: Sie hat den Geltungsbereich „application“,
  • Zeilen 13–14: Eine Referenz auf die lokale Schnittstelle der Schicht [métier] wird ihr vom Container EJB des Anwendungsservers injiziert. Erinnern wir uns an die Architektur der Anwendung:

Die Anwendungen PFM, EJB und [Metier] werden in derselben JVM (Java Virtual Machine) ausgeführt. Daher nutzt die Schicht [PFM] die lokale Schnittstelle von EJB. Das ist alles. Die Bean [Application] enthält nichts anderes. Um Zugriff auf die Schicht [métier] zu erhalten, werden die anderen Beans diese aus dieser Bean abrufen.

9.7.2. Die Bean [Erreur]

Die Klasse [Erreur] sieht wie folgt aus:


package beans;

public class Erreur {
  
  public Erreur() {
  }
  
  // Feld
  private String classe;
  private String message;

  // Konstruktor
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // Getter und Setter
...  
}
  • Zeile 9: der Name einer Ausnahmeklasse, falls eine Ausnahme ausgelöst wurde,
  • Zeile 10: eine Fehlermeldung.

9.7.3. Die Bean [Form]

Ihr Code lautet wie folgt:


package beans;

...

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

  public Form() {
  }
  // Anwendungs-Bean
  @Inject
  private Application application;
  private IMetierLocal metier;
  // Sitzungscache
  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>();
  // Modell
  private Long idMedecin;
  private Date jour = new Date();
  private String strJour;
  private Boolean form1Rendered;
  private Boolean form2Rendered;
  private Boolean form3Rendered;
  private Boolean erreurRendered;
  private String form2Titre;
  private String form3Titre;
  private AgendaMedecinJour agendaMedecinJour;
  private Long idCreneauChoisi;
  private Medecin medecin;
  private Long idClient;
  private CreneauMedecinJour creneauChoisi;
  private List<Erreur> erreurs;
  private Boolean erreurInit = false;
  private String action;
  private String locale = "fr";
  private String msgErreurDate = "";
  private SimpleDateFormat dateFormatter;
  private Boolean erreurDate;
  
  @PostConstruct
  private void init() {
    System.out.println("init");
    // zunächst kein Fehler
    erreurInit = false;
    // Datumsformatierung
    dateFormatter = new SimpleDateFormat(Messages.getMessage(null, "format.date", null).getSummary());
    dateFormatter.setLenient(false);
    // der aktuelle Tag
    strJour = dateFormatter.format(jour);
    // die Geschäftsebene wird abgerufen
    metier = application.getMetier();
    // Ärzte und Kunden werden zwischengespeichert
    try {
      medecins = metier.getAllMedecins();
      clients = metier.getAllClients();
    } catch (Throwable th) {
      // Der Fehler wird protokolliert
      erreurInit = true;
      prepareVueErreur(th);
      return;
    }
    // Überprüfung der Listen
    if (medecins.size() == 0) {
      // Der Fehler wird protokolliert
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des médecins est vide"));
    }
    if (clients.size() == 0) {
      // Der Fehler wird notiert
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des clients est vide"));
    }
    // Fehler?
    if (erreurInit) {
      // Die Fehleransicht wird angezeigt
      setForms(false, false, false, true);
      return;
    }

    // die Wörterbücher
    for (Medecin m : medecins) {
      hMedecins.put(m.getId(), m);
    }
    for (Client c : clients) {
      hClients.put(c.getId(), c);
    }
    // Ansicht 1
    setForms(true, false, false, false);
  }

  // Ansicht anzeigen
  private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
    this.form1Rendered = form1Rendered;
    this.form2Rendered = form2Rendered;
    this.form3Rendered = form3Rendered;
    this.erreurRendered = erreurRendered;
  }

  // Vorbereitung vueErreur
  private void prepareVueErreur(Throwable th) {
    // Die Fehlerliste wird erstellt
    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()));
    }
// Die Fehleransicht wird angezeigt
    setForms(false, false, false, true);
  }

  // Getter und Setter
  ..
}
  • Zeilen 5–7: Die Klasse [Form] ist ein Bean mit dem Namen „form“ und dem Gültigkeitsbereich „session“. Es sei daran erinnert, dass die Klasse in diesem Fall serialisierbar sein muss,
  • Zeilen 12–13: Die Form-Bean verfügt über eine Referenz auf die Application-Bean. Diese wird vom Servlet-Container, in dem die Anwendung ausgeführt wird, injiziert (vorhandene Annotation @Inject).
  • Zeilen 24–27: Steuern die Anzeige der Ansichten vue1 (Zeile 24), vue2 (Zeile 25), vue3 (Zeile 26) und vueErreurs (Zeile 27),
  • Zeilen 43–44: Die Methode „init“ wird unmittelbar nach der Instanziierung der Klasse ausgeführt (vorhandene Annotation @PostConstruct),
  • Zeilen 49–50: regeln das Datumsformat. PrimeFaces Mobile bietet keinen Kalender. Außerdem können die Validatoren JSF nicht auf einer Seite PFM verwendet werden. Daher müssen wir die Eingabe des Termindatums manuell handhaben,
  • Zeile 49: Legt das Datumsformat fest. Dieses Format wird aus den internationalisierten Dateien übernommen:

format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
  • Zeile 50: Es wird festgelegt, dass bei Datumsangaben nicht „laxiste“ verwendet werden darf. Wenn dies nicht geschieht, wird eine Eingabe wie „32/12/2011“, die eine fehlerhafte Eingabe darstellt, als gültiges Datum „01/01/2012“ gewertet,
  • Zeile 54: Es wird eine Referenz auf die Ebene [métier] vom Bean [Application] abgerufen,
  • Zeilen 57–58: Von der Schicht [métier] wird die Liste der Ärzte und Kunden angefordert,
  • Zeilen 66–91: Wenn alles erfolgreich verlaufen ist, werden die Ärzte- und Kundenverzeichnisse erstellt. Sie sind nach ihrer Nummer indiziert. Anschließend wird die Ansicht [vue1.xhtml] angezeigt (Zeile 93),
  • Zeile 59: Im Fehlerfall wird die Vorlage der Seite [vueErreurs.xhtml] erstellt. Diese Vorlage ist die Fehlerliste aus Zeile 35,
  • Zeilen 105–115: Die Methode [prepareVueErreur] erstellt die anzuzeigende Fehlerliste. Die Seite [index.xhtml] zeigt dann die Ansicht [vueErreurs.xhtml] an (Zeile 114).

Die Seite [erreur.xhtml] sieht wie folgt aus:


<?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:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Fehleransicht -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="home" value=" " href="#vue1?reverse=true" />
    </f:facet>
  </pm:header>
  <pm:content>
    <div align="center">
      <h1><h:outputText value="#{msg['erreur.titre']}" style="color: blue"/></h1>
    </div>

    <p:dataList value="#{form.erreurs}" var="erreur">
      <b>#{erreur.classe}</b> : <i>#{erreur.message}</i>
    </p:dataList>
  </pm:content>
</html>

Sie verwendet ein Tag <p:dataList> (Zeilen 21–23), um die Fehlerliste anzuzeigen. Über die Schaltfläche in Zeile 13 gelangt man zurück zur Ansicht „vue1“.

  • Die Schaltfläche [1] wird durch Zeile 13 erzeugt. Das Attribut „icon“ legt das Symbol der Schaltfläche fest. Die Schaltfläche führt zurück zur Ansicht „vue1“ (Attribut „href“),
  • die Überschrift [2] wird durch die Zeilen 11–15 erzeugt,
  • der Titel „[3]“ wird in Zeile 18 generiert,
  • der Text „[4]“ wird durch die Vorlage #{erreur.classe} in Zeile 22 generiert,
  • der Text [5] wird durch die Vorlage #{erreur.message} in Zeile 22 generiert.

Wir werden nun die verschiedenen Lebensphasen der Anwendung definieren. Für jede Benutzeraktion werden wir die betroffenen Ansichten und die Ereignisbehandler untersuchen, die dort ablaufen.

9.8. Die Anzeige der Startseite

Wenn alles gut geht, wird als erste Ansicht [vue1.xhtml] angezeigt. Dies ergibt folgende Ansicht:

Der Code der Ansicht [vue1.xhtml] lautet wie folgt:


<?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:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Ansicht 1 -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="gear" value=" "  href="#config" />
    </f:facet>
  </pm:header>
  <pm:content>
    <h:form id="form1">
      <div align="center">
        <h1><h:outputText value="#{msg['form1.titre']}" style="color: blue"/></h1>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['form1.medecin']}" for="choixMedecin"/>
        <h:selectOneMenu id="choixMedecin" value="#{form.idMedecin}">  
          <f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>  
        </h:selectOneMenu>              
      </pm:field>
      <pm:field>
        <h:outputLabel value="#{msg['form1.jour']}" for="jour"/>
        <p:inputText id="jour" value="#{form.strJour}"/>
        <ui:fragment rendered="#{form.erreurDate}">
          <p:spacer width="50px"/>
          <h:outputText id="msgErreurDate" value="#{form.msgErreurDate}" style="color: red"/>
        </ui:fragment>
      </pm:field>
      <p:commandButton value="#{msg['form1.agenda']}" update=":form1, :vue2, :vueErreurs" action="#{form.getAgenda}" />
    </h:form>
  </pm:content>
</html>
  • Zeilen 11–15: erzeugen die Kopfzeile [1],
  • Zeile 13: erzeugt die Schaltfläche [2]. Ein Klick darauf zeigt die Konfigurationsansicht an (Attribut „href“),
  • Zeile 19: erzeugt den Titel „[3]“,
  • Zeilen 21–26: erzeugen das Ärzte-Auswahlfeld „[4]“,
  • Zeilen 27–34: Erzeugen das Datumsfeld [5]. Dieses Feld wird mit einem <p:inputText>-Tag ohne Validator erstellt. Die Validierung des Datums erfolgt serverseitig. Ist das Datum falsch, wird eine Fehlermeldung ausgegeben, die in den Zeilen 30–34 angezeigt wird,
  • Zeile 35: Die Schaltfläche, die das Formular übermittelt. Sie aktualisiert drei Bereiche: form1 (das Formular in vue1), vue2 und vueErreurs. Ist das Datum ungültig, muss nämlich form1 aktualisiert werden. Ist das Datum korrekt, muss vue2 aktualisiert werden. Tritt schließlich eine Ausnahme auf (z. B. eine unterbrochene Datenbankverbindung), muss vueErreurs angezeigt werden. Man könnte versucht sein, vue1 anstelle von form1 zu verwenden (wodurch die gesamte Ansicht aktualisiert wird). In diesem Fall kommt es zu einem Fehler in der Anwendung.

Diese Ansicht basiert auf dem folgenden Modell:


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

  public Form() {
  }

  // Anwendungs-Bean
  @Inject
  private Application application;
  private IMetierLocal metier;
  // Sitzungscache
  private List<Medecin> medecins;
  private List<Client> clients;
  // Vorlage
  private Long idMedecin;
  private Date jour = new Date();
  private String strJour;
  private Boolean form1Rendered;
  private Boolean form2Rendered;
  private Boolean form3Rendered;
  private Boolean erreurRendered;
  private String msgErreurDate = "";
  private Boolean erreurDate;
  
    // Ärzteverzeichnis
  public List<Medecin> getMedecins() {
    return medecins;
  }

  // Terminkalender
  public String getAgenda() {
...
    }
  }
  • Das Feld in Zeile 16 dient zum Lesen und Schreiben des Werts aus der Liste in Zeile 23 der Seite. Bei der ersten Anzeige der Seite legt es den im Kombinationsfeld ausgewählten Wert fest,
  • die Methode in den Zeilen 27–29 generiert die Elemente des Ärzte-Kombinationsfelds (Zeile 24 der Ansicht). Jede generierte Option hat als Bezeichnung (itemLabel) den Titel, den Nachnamen und den Vornamen des Arztes und als Wert (itemValue) die ID des Arztes,
  • das Feld in Zeile 18 versorgt das Eingabefeld in Zeile 29 der Seite mit Lese- und Schreibzugriff,
  • Zeilen 32–34: Die Methode getAgenda verarbeitet den Klick auf die Schaltfläche [Agenda] in Zeile 35 der Seite. Ihr Code lautet wie folgt:

// Terminkalender
  public String getAgenda() {
    try {
      // Tag wird überprüft
      jour = dateFormatter.parse(strJour);
      // kein Fehler
      erreurDate=false;
      msgErreurDate = "";
      // Terminplan wird erstellt
      return getAgenda(jour);
    } catch (ParseException ex) {
      // Fehlermeldung
      erreurDate=true;
      msgErreurDate = Messages.getMessage(null, "form1.date.invalide", null).getSummary();
      // Ansicht 1      
      setForms(true, false, false, false);
      return "pm:vue1";
    }
  }
  • Die Methode überprüft zunächst die Gültigkeit des vom Benutzer eingegebenen Datums,
  • Zeile 5: Das Datum lautet parsée gemäß dem Datumsformat, das von der Methode init bei der Instanziierung des Modells initialisiert wurde,
  • Zeile 11: Tritt eine Ausnahme auf, wird ein Fehler gesetzt (Zeile 13), eine internationalisierte Fehlermeldung erstellt (Zeile 14), die Ansicht „vue1“ vorbereitet (Zeile 16) und die Ansicht „vue1“ angezeigt (Zeile 17),
  • Zeile 10: Ist das Datum korrekt, wird die folgende Methode ausgeführt:

  // Kalender
  public String getAgenda(Date jour) {
    // Bisher wurde noch kein Termin ausgewählt
    creneauChoisi = null;
    try {
      // Der ausgewählte Arzt wird abgerufen
      medecin = hMedecins.get(idMedecin);
      // Formularüberschrift 2
      form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
      // Der Terminkalender des Arztes für einen bestimmten Tag
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // Ansicht 2 wird angezeigt
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      System.out.println(th);
      // Fehleransicht
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}

Hier finden wir einen Code wieder, der bereits mehrfach aufgetreten ist. In Zeile 9 wird eine internationalisierte Meldung für die Ansicht „vue2“ erstellt:


form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}

Beachten Sie, dass wir „XHTML“ in die Meldung eingefügt haben. Diese wird wie folgt angezeigt:

9.9. Den Terminkalender eines Arztes anzeigen

Der Terminkalender des Arztes wird über die Ansicht [vue2.xhtml] angezeigt:

Der Code der Ansicht [vue2.xhtml] lautet wie folgt:


<?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:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Ansicht 2 -->
  <pm:header title="#{msg['page.titre']}" swatch="b"/>
  <pm:content>
    <h:form id="form2">
      <div align="center">
        <pm:buttonGroup orientation="horizontal">
          <p:commandButton inline="true" icon="back" value=" " action="#{form.showVue1}" update=":vue1"/>
          <p:commandButton inline="true" icon="minus" value=" " action="#{form.getPreviousAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="home" value=" " action="#{form.getTodayAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="plus" value=" " action="#{form.getNextAgenda}" update=":form2"/>
        </pm:buttonGroup>
        <h3><h:outputText value="#{form.form2Titre}" style="color: blue" escape="false"/></h3>
      </div>

      <p:dataList id="creneaux" type="inset" value="#{form.agendaMedecinJour.creneauxMedecinJour}" var="creneauMedecinJour">
        <p:column>
          <div align="center">
            <h2>
              <h:outputFormat value="{0,number,#00}h:{1,number,#00} - {2,number,#00}h:{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>
              <ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
                <br/>
                <h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}" style="color: blue"/>
              </ui:fragment>
            </h2>
          </div>
          <div align="center">
            <ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.supprimer']}" icon="minus" update=":form2, :vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
            <ui:fragment rendered="#{creneauMedecinJour.rv==null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.reserver']}" icon="plus" update=":form2, :vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
          </div>
        </p:column>
      </p:dataList>
    </h:form>
  </pm:content>
</html>
  • Zeile 11: Erzeugt die Kopfzeile [1],
  • Zeilen 15–20: Erzeugt die Schaltflächengruppe [2],
  • Zeile 21: Erzeugt den Titel [3]. Beachten Sie den Wert des Attributs „escape“. Dieser ermöglicht die Interpretation des Codes XHTML, den wir in form2Titre eingefügt haben,
  • Zeile 24: Die Anzeige der Zeitfenster erfolgt mithilfe eines dataList,
  • Zeilen 28–33: Erzeugen die Bezeichnung des Zeitfensters [4],
  • Zeilen 34–37: zeigen einen Ausschnitt an, wenn in dem Zeitfenster ein Termin vorhanden ist,
  • Zeile 36: Zeigt die Identität des Kunden an, der den Termin vereinbart hat,
  • Zeilen 41–45: Zeigen die Schaltfläche [Supprimer] an, wenn ein Termin vorliegt,
  • Zeilen 46–50: Zeigen die Schaltfläche „[Réserver]“ an, wenn kein Termin vorliegt.

Diese Ansicht wird hauptsächlich durch das folgende Modell gespeist:


private AgendaMedecinJour agendaMedecinJour;

das das Feld dataList in Zeile 24 versorgt. Dieses Feld wurde von der Methode getAgenda erstellt, als vom Layout „vue1“ zum Layout „vue2“ gewechselt wurde.

9.10. Löschen eines Termins

Das Löschen eines Termins erfolgt nach folgender Abfolge:

Die von dieser Aktion betroffene Ansicht ist die folgende:


<ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.supprimer']}" icon="minus" update=":form2, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
  • Zeile 2: Die Schaltfläche [Supprimer] ist der Methode [Form].action zugeordnet (Attribut „action“),
  • Zeile 3: Die ID des Zeitfensters, in dem man sich gerade befindet, wird an das Modell [Form].idCreneauChoisi gesendet,
  • Zeile 2: Der Aufruf von AJAX aktualisiert die Felder von form2 (Formular von Ansicht2) und die Ansicht vueErreurs. Es gibt nämlich zwei Fälle: Wenn alles gut läuft, wird die Ansicht „vue2“ erneut angezeigt, andernfalls wird die Ansicht „vueErreurs“ angezeigt.

Die Methode [action] lautet wie folgt:


  // Aktion auf RV
  public String action() {
    // Es wird nach einem Termin im Kalender gesucht
    int i = 0;
    Boolean trouvé = false;
    while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
      if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneauChoisi) {
        trouvé = true;
      } else {
        i++;
      }
    }
    // Wurde etwas gefunden?
    if (!trouvé) {
      // Das ist seltsam – form2 wird erneut angezeigt
      setForms(false, true, false, false);
      return "pm:vue2";
    } else {
      creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
    }
    // Es wurde gefunden
    // je nach gewünschter Aktion
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }

  // Reservierung
  public String reserver() {
 ...
  }

  public String supprimer() {
    try {
      // Termin löschen
      metier.supprimerRv(creneauChoisi.getRv());
      // der Kalender wird aktualisiert
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // Formular 2 wird angezeigt
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // Fehleransicht
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}
  • Zeilen 3–12: Es wird nach dem Zeitfenster gesucht, dessen ID übermittelt wurde (Zeile 7),
  • Wenn er nicht gefunden wird – was ungewöhnlich ist –, wird Ansicht2 erneut angezeigt (Zeilen 16–17),
  • Zeile 19: Wenn er gefunden wird, speichern wir das entsprechende Objekt [CreneauMedecinJour]. Dieses Objekt ermöglicht uns den Zugriff auf den zu löschenden Termin,
  • Zeile 26: Wir löschen ihn,
  • Zeilen 35–48: Die Methode „löschen“ gibt die Ansicht vue2 zurück, wenn das Löschen erfolgreich war (Zeilen 42–43), oder die Ansicht vueErreurs, wenn ein Problem aufgetreten ist (Zeilen 46–47).

9.11. Terminvereinbarung

Die Terminvereinbarung erfolgt nach folgender Abfolge:

Man wechselt von der Ansicht „vue2“ zur Ansicht „vue3“. Der für diese Aktion relevante Code lautet wie folgt:


<ui:fragment rendered="#{creneauMedecinJour.rv==null}">
              <p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.reserver']}" icon="plus" update=":vue3, :vueErreurs">
                <f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
              </p:commandButton>
            </ui:fragment>
  • Zeile 2: Die Schaltfläche [Réserver] ist der Methode [Form].action zugeordnet (Attribut action), also derselben wie bei der Schaltfläche [Supprimer]. Der Aufruf AJAX aktualisiert die Ansichten „vue3“ und „vueErreurs“, je nachdem, ob bei der Verarbeitung des Aufrufs Fehler aufgetreten sind oder nicht.
  • Zeile 3: Wie bei der Schaltfläche [Supprimer] wird die ID des Zeitfensters an das Modell übergeben.

Das Template, das diese Aktion verarbeitet, lautet wie folgt:


// Aktion auf RV
  public String action() {
    ...
    // je nach gewünschter Aktion
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }

  // Reservierung
  public String reserver() {
    try {
      // Titel Formular 3
      form3Titre = Messages.getMessage(null, "form3.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour),
                creneauChoisi.getCreneau().getHdebut(), creneauChoisi.getCreneau().getMdebut(), creneauChoisi.getCreneau().getHfin(), creneauChoisi.getCreneau().getMfin()}).getSummary();
      // Formular 3 wird angezeigt
      setForms(false, false, true, false);
      return "pm:vue3";
    } catch (Throwable th) {
      // Fehleransicht
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }
  • Zeilen 2–10: Die Methode action ermittelt die Referenz creneauChoisi des Objekts [CreneauMedecinJour], das Gegenstand einer Reservierung ist, und ruft anschließend die Methode reserver auf,
  • Zeile 16: Es wird eine internationalisierte Meldung erstellt. Diese lautet wie folgt:
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}

Dies wird der Titel der Ansicht „vue3“ sein. Wie bei der Ansicht „vue2“ enthält dieser Titel den Code XML. Er enthält außerdem formatierte Parameter zur Anzeige der Zeiten des Zeitfensters,

  • Zeilen 19–20: Die Ansicht „vue3“ wird angezeigt,
  • Zeilen 23–24: Die Ansicht „vueErreurs“ wird angezeigt, wenn Probleme aufgetreten sind.

9.12. Bestätigung eines Termins

Die Bestätigung eines Termins erfolgt nach folgender Abfolge:

In [1] wird die Ansicht „vue3“ und in [2] die Ansicht „vue2“ nach dem Hinzufügen eines Termins angezeigt.

Der Code [vue3.xhtml] für vue3 lautet wie folgt:


<?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:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Ansicht 3 -->
  <pm:header title="#{msg['page.titre']}" swatch="b"/>
  <pm:content>
    <h:form id="form3">
      <p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>
      <div align="center">
        <h3><h:outputText value="#{form.form3Titre}" style="color: blue" escape="false"/></h3>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['form3.client']}" for="choixClient"/>
        <h:selectOneMenu id="choixClient" value="#{form.idClient}">
          <f:selectItems value="#{form.clients}" var="client" itemLabel="#{client.titre} #{client.prenom} #{client.nom}" itemValue="#{client.id}"/>
        </h:selectOneMenu>
      </pm:field>
      <div align="center">
        <p:commandButton inline="true" value="#{msg['form3.valider']}" action="#{form.validerRv}" update=":vue2, :vueErreurs" icon="check"/>
      </div>
    </h:form>
  </pm:content>
</html>
  • Zeile 16: Erzeugt den Titel der Ansicht [3]. Beachten Sie den Wert des Attributs „escape“, das die Interpretation der Zeichen XHTML im Titel ermöglicht,
  • Zeilen 18–23: Erzeugen die Kundenauswahl [4],
  • Zeile 25: Erzeugt die Schaltfläche [Valider] [5]. Die Methode [Form].validerRv ist dieser Schaltfläche zugeordnet:

// Terminbestätigung
  public String validerRv() {
    try {
      // Es wird eine Instanz des ausgewählten Zeitfensters abgerufen
      Creneau creneau = metier.getCreneauById(idCreneauChoisi);
      // Der Termin wird hinzugefügt
      metier.ajouterRv(jour, creneau, hClients.get(idClient));
      // Der Kalender wird aktualisiert
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // form2 wird angezeigt
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // Fehleransicht
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }

Dieser Code kam bereits in Version 01 vor. Man beachte lediglich die Anzeige der Ansichten:

  • die Ansicht „vue2“ (Zeilen 11–12), wenn alles geklappt hat,
  • die Ansicht vueErreurs (Zeilen 15–16), falls nicht.

9.13. Stornierung eines Termins

Dies entspricht der folgenden Abfolge:

Die Schaltfläche [1] in der Ansicht [vue3.xhtml] lautet wie folgt:


      <p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>

Die Methode [Form].showVue2 wird daher aufgerufen. Sie zeigt lediglich die Ansicht „vue2“ an:


  public String showVue2() {
    // Ansicht 2
    setForms(false, true, false, false);
    return "pm:vue2?reverse=true";
}

9.14. Navigation im Kalender

In Ansicht 2 ermöglichen Schaltflächen die Navigation im Kalender:

Vortag:

Nächster Tag:

Heute:

Der Kalender, der auf den obigen Screenshots nicht zu sehen ist, wird aktualisiert und zeigt die Termine des neu ausgewählten Tages an.

Die Tags der drei betreffenden Schaltflächen lauten in [vue2.xhtml] wie folgt:


<pm:buttonGroup orientation="horizontal">
          <p:commandButton inline="true" icon="back" value=" " action="#{form.showVue1}" update=":vue1"/>
          <p:commandButton inline="true" icon="minus" value=" " action="#{form.getPreviousAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="home" value=" " action="#{form.getTodayAgenda}" update=":form2"/>
          <p:commandButton inline="true" icon="plus" value=" " action="#{form.getNextAgenda}" update=":form2"/>
        </pm:buttonGroup>

Die Methoden [Form].getPreviousAgenda, [Form], getNextAgenda, [Form].today wurden im Beispiel 03 behandelt.

9.15. Änderung der Anzeigesprache

Die Sprachumstellung erfolgt über eine Schaltfläche auf der Startseite:

Der Code der Schaltfläche lautet wie folgt:


      <p:button icon="gear" value=" "  href="#config" />

Er leitet somit zur Konfigurationsansicht [2] weiter. Die Ansicht [config.xhtml] sieht wie folgt aus:


<?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:pm="http://primefaces.org/mobile"
      xmlns:ui="http://java.sun.com/jsf/facelets">

  <!-- Ansicht 1 -->
  <pm:header title="#{msg['page.titre']}" swatch="b">
    <f:facet name="left">
      <p:button icon="back" value=" " href="#vue1?reverse=true" />
    </f:facet>
  </pm:header>
  <pm:content>
    <h:form id="frmConfig">
      <div align="center">
        <h3><h:outputText value="#{msg['config.titre']}" style="color: blue"/></h3>
      </div>
      <pm:field>
        <h:outputLabel value="#{msg['config.langue']}" for="langue"/>
        <h:selectOneRadio id="langue" value="#{form.locale}">
          <f:selectItem itemLabel="#{msg['config.langue.francais']}" itemValue="fr"/>
          <f:selectItem itemLabel="#{msg['config.langue.anglais']}" itemValue="en" />
        </h:selectOneRadio>
      </pm:field>
      <p:commandButton value="#{msg['config.valider']}" action="#{form.configurer}" update=":vue1"/>
    </h:form>
  </pm:content>
</html>
  • Zeile 11: Zeigt [3] an,
  • Zeile 13: Die Schaltfläche [4] wird angezeigt. Mit dieser Schaltfläche kehrt man zur Ansicht „vue1“ zurück,
  • Zeile 17: das Formular der Ansicht,
  • Zeile 19: zeigt den Titel der Ansicht „[5]“ an,
  • Zeilen 21–27: zeigen die Optionsfelder an. Der Wert (itemValue) des ausgewählten Optionsfelds wird an das Modell [Form].locale übermittelt (Attribut „value“ in Zeile 23),
  • Zeile 28: Lässt die Schaltfläche [Valider] anzeigen. Der Aufruf von AJAX aktualisiert die Ansicht vue1 (Attribut „update“). Die aufgerufene Methode ist [Form].configurer:

public String configurer(){
    // Nach der Konfiguration – Ansicht 1 wird erneut angezeigt
    redirect();
    return null;
  }
  
  private void redirect() {
    // Der Client wird an das Servlet weitergeleitet
    ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
    try {
      ctx.redirect(ctx.getRequestContextPath());
    } catch (IOException ex) {
      Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

Die Methode „configurer“ (Zeile 1) leitet den Browser des Mobilgeräts lediglich auf die Seite „URL“ der Anwendung weiter. Es wird also die Seite „[index.xhtml]“ geladen:


<f:view xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:p="http://primefaces.org/ui"
        xmlns:pm="http://primefaces.org/mobile"
        contentType="text/html"
        locale="#{form.locale}">

  <pm:page title="#{msg['page.titre']}">
    <pm:view id="vue1">
      ...
    </pm:view>
    ...
  </pm:page>    
</f:view>
  • Zeile 8: Die Ansicht verwendet die gerade geänderte Sprache (Attribut „locale“) und zeigt „vue1“ an (Zeile 11).

9.16. Fazit

Zur Erinnerung: Die Architektur der soeben erstellten Anwendung sieht wie folgt aus:

Die Umstellung auf eine mobile Benutzeroberfläche erforderte die Neuprogrammierung der Seiten XHTML. Das Modell hingegen hat sich kaum verändert. Die unteren Schichten [métier], [DAO] und [JPA] sind hingegen völlig unverändert geblieben.

9.17. Eclipse-Tests

Wie bereits bei den vorherigen Versionen der Beispielanwendung zeigen wir, wie man diese Version 05 mit Eclipse testen kann. Zunächst importieren wir die Maven-Projekte des Beispiels 05 [1] in Eclipse:

  • [mv-rdvmedecins-ejb-dao-jpa]: die Schichten [DAO] und [JPA],
  • [mv-rdvmedecins-ejb-metier]: die Schicht [métier],
  • [mv-rdvmedecins-pfmobile]: die von PrimeFaces Mobile implementierte Ebene [web],
  • [mv-rdvmedecins-pfmobile-app]: das übergeordnete Unternehmensprojekt [mv-rdvmedecins-pfmobile-app-ear]. Beim Importieren des übergeordneten Projekts wird das untergeordnete Projekt automatisch importiert,
  • in [2] wird das Unternehmensprojekt [mv-rdvmedecins-pfmobile-app-ear] ausgeführt,
  • in [3] wählt man den Glassfish-Server aus,
  • in [4] wurde die Anwendung auf der Registerkarte [Servers] bereitgestellt. Sie läuft nicht von selbst. Man muss sie in einem Browser oder einem mobilen Simulator aufrufen: