Skip to content

9. Aplikacja przykładowa 05: rdvmedecins-pfm-ejb

Przypomnijmy strukturę przykładowej aplikacji nr 01 JSF / EJB opracowanej dla serwera Glassfish:

Nie wprowadzamy żadnych zmian w tej architekturze, z wyjątkiem warstwy internetowej, która zostanie tutaj zrealizowana przy użyciu JSF, Primefaces i Primefaces mobile. Docelową przeglądarką będzie przeglądarka mobilna.

Opracowaliśmy dwie aplikacje przy użyciu Glassfish:

  • aplikacja 01 korzystała z JSF / EJB i miała dość prosty interfejs,
  • aplikacja 03 korzystała z komponentów PF / EJB i miała bogaty interfejs.

Ze względu na niewielką liczbę komponentów dostępnych w PrimeFaces Mobile powrócimy do prostego interfejsu aplikacji 01. Ponadto widoki będą musiały dostosowywać się do mniejszych rozmiarów ekranów urządzeń mobilnych.

9.1. Widoki

Aby lepiej to zobrazować, przedstawiamy kilka widoków aplikacji uruchomionej w symulatorze iPhone'a 4:

  • w [1] – strona główna. Należy zauważyć, że tym razem konieczne było podanie nazwy maszyny (można również podać jej adres IP), ponieważ w przypadku maszyny localhost nic się nie wyświetlało,
  • w [2] – lista lekarzy,
  • na [3] – żądaną datę wizyty,
  • w [4] – przycisk wyświetlania harmonogramu na dany dzień,
  • w [5] nowy widok wyświetla dostępne terminy u lekarza,
  • w [6], zestaw przycisków do poruszania się po kalendarzu,
  • w [7], komunikat przypominający o lekarzu i dniu,
  • w [8] – przedział czasowy do rezerwacji. Robimy to,
  • w [9] – widok wyboru klienta,
  • w [10] – komunikat przypominający o lekarzu, dniu i przedziale czasowym, których dotyczy wizyta,
  • w [11] – lista klientów,
  • w [12], przycisk zatwierdzenia,
  • w [13] – po zatwierdzeniu powracamy do kalendarza,
  • w [14] – zajęty przedział czasowy jest teraz zarezerwowany. Teraz usuniemy rezerwację,
  • w [15] pozostajemy na tym samym ekranie,
  • ale w [16] spotkanie zostało usunięte,

Na stronie głównej można zmienić język: [17], [18], [19]:

Wreszcie przewidziano widok błędów:

9.2. Projekt NetBeans

Projekt NetBeans wygląda następująco:

  
  • [mv-rdvmedecins-ejb-dao-jpa]: projekt EJB warstw [DAO] i [JPA] z przykładu 01,
  • [mv-rdvmedecins-ejb-metier]: projekt EJB warstwy [métier] z przykładu 01,
  • [mv-rdvmedecins-pfmobile]: projekt warstwy [web] / Primefaces mobile – nowy,
  • [mv-rdvmedecins-pfmobile-app-ear]: projekt korporacyjny służący do wdrożenia aplikacji na serwerze Glassfish – nowy.

9.3. Projekt korporacyjny

Projekt korporacyjny służy wyłącznie do wdrożenia trzech modułów [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-metier], [mv-rdvmedecins-pfmobile] na serwerze Glassfish. Projekt NetBeans ma następującą nazwę:

Projekt ten istnieje wyłącznie w celu obsługi tych trzech zależności [1], zdefiniowanych w pliku [pom.xml] w następujący sposób:


<?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>
  • wiersze 6–9: artefakt Maven projektu korporacyjnego,
  • wiersze 14–33: trzy zależności projektu. Należy zwrócić uwagę na ich typ (wiersze 19, 25, 31).

Aby uruchomić aplikację internetową, należy uruchomić ten projekt korporacyjny.

9.4. Projekt internetowy Primefaces mobile

Projekt internetowy Primefaces mobile wygląda następująco:

  • w [1] znajdują się strony projektu. Strona [index.xhtml] jest jedyną stroną projektu. Zawiera ona pięć widoków: [vue1.xhtml], [vue2.xhtml], [vue3.xhtml], [vueErreurs.xhtml] oraz [config.xhtml],
  • w [2] znajdują się komponenty Java. Komponent [Application] o zakresie application, a komponent [Form] o zakresie session. Klasa [Erreur] zawiera błąd,
  • w [3] – pliki komunikatów do internacjonalizacji,
  • w [4] – zależności. Projekt internetowy jest zależny od projektu EJB z warstwy [DAO], projekt EJB z warstwy [métier] oraz Primefaces mobile dla warstwy [web].

9.5. Konfiguracja projektu

Konfiguracja projektu jest taka sama jak w przypadku projektów Primefaces lub JSF, które już omówiliśmy. Wymieniamy pliki konfiguracyjne bez ponownego ich wyjaśniania.

 

[web.xml]: konfiguruje aplikację internetową.


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

Warto zwrócić uwagę, że w wierszu 26 strona [index.xhtml] jest stroną główną aplikacji.

[faces-config.xml]: konfiguruje aplikację 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]: pusta, ale niezbędna dla adnotacji @Named


<?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]: plik z komunikatami w języku francuskim


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

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

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

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

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

# błąd
erreur.titre=Une erreur s'est produite.

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

#wyjątek
exception.titre=Application indisponible. Veuillez recommencer ult\u00e9rieurement.

[messages_en.properties]: plik z komunikatami w języku angielskim


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

# widok1
vue1.header=The Associated Doctors - Reservations

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

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

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

# błąd
erreur.titre=Some error happened

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

#wyjątek
exception.titre=Application not available. Please try again later.

9.6. Strona [index.xhtml]

Projekt wyświetla zawsze tę samą stronę, a mianowicie następującą stronę [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>
  • wiersz 8: strona jest zinternacjonalizowana (atrybut „locale”),
  • wiersz 10: strona zawiera pięć widoków: vue1 w wierszu 11, vue2 w wierszu 19, vue3 w wierszu 24, vueErreurs w wierszu 29, config w wierszu 34. W danym momencie widoczny jest tylko jeden z tych widoków. Po uruchomieniu aplikacji wyświetlany jest widok vue1. W tym miejscu napotkaliśmy następującą trudność: jeśli inicjalizacja aplikacji przebiegła pomyślnie, należy wyświetlić [vue1.xhtml], w przeciwnym razie należy wyświetlić [vueErreurs.xhtml]. Problem rozwiązaliśmy, sprawiając, że zawartość widoku vue1 jest zarządzana przez model, który nadaje wartości zmiennym logicznym [Form].form1rendered (wiersz 12) oraz [Form].erreurInit (wiersz 15), aby ustalić zawartość widoku vue1 (wiersz 11),

W symulatorze widok [vue1.xhtml] ma render [1], widok [vue2.xhtml] ma render [2], a widok [vue3.xhtml] ma widok wyświetlany [3]:

widok [vueErreurs.xhtml], wynik [4], widok [config.xhtml], wynik [5]:

9.7. Elementy projektu

Klasa z pakietu [utils] została już przedstawiona: klasa [Messages] ułatwia internacjonalizację komunikatów aplikacji. Została ona omówiona w paragrafie 2.8.5.7.

9.7.1. Bean Application

Bean [Application.java] jest beanem o zasięgu application. Przypomnijmy, że ten typ beana służy do przechowywania danych tylko do odczytu, dostępnych dla wszystkich użytkowników aplikacji. Bean ten wygląda następująco:


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 {

  // warstwa biznesowa
  @EJB
  private IMetierLocal metier;

  public Application() {
  }
  
  // metody pobierające

  public IMetierLocal getMetier() {
    return metier;
  }
  
}
  • wiersz 8: nadajemy beanowi nazwę „application”,
  • wiersz 9: ma zasięg aplikacji,
  • wiersze 13–14: odniesienie do lokalnego interfejsu warstwy [métier] zostanie mu wstrzyknięte przez kontener EJB serwera aplikacji. Przypomnijmy sobie architekturę aplikacji:

Aplikacje PFM, EJB oraz [Metier] będą uruchamiane w tej samej maszynie wirtualnej Java (JVM). Zatem warstwa [PFM] będzie korzystać z lokalnego interfejsu warstwy EJB. To wszystko. Bean [Application] nie zawiera niczego innego. Aby uzyskać dostęp do warstwy [métier], pozostałe beany będą ją pobierać z tego beana.

9.7.2. Bean [Erreur]

Klasa [Erreur] wygląda następująco:


package beans;

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

  // konstruktor
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // metody pobierające i ustawiające
...  
}
  • wiersz 9 – nazwa klasy wyjątku, jeśli został zgłoszony wyjątek,
  • wiersz 10: komunikat o błędzie.

9.7.3. Bean [Form]

Jego kod jest następujący:


package beans;

...

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

  public Form() {
  }
  // bean aplikacji
  @Inject
  private Application application;
  private IMetierLocal metier;
  // pamięć podręczna sesji
  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>();
  // model
  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");
    // na początku brak błędów
    erreurInit = false;
    // formatowanie dat
    dateFormatter = new SimpleDateFormat(Messages.getMessage(null, "format.date", null).getSummary());
    dateFormatter.setLenient(false);
    // bieżący dzień
    strJour = dateFormatter.format(jour);
    // pobieramy warstwę biznesową
    metier = application.getMetier();
    // zapisujemy lekarzy i klientów w pamięci podręcznej
    try {
      medecins = metier.getAllMedecins();
      clients = metier.getAllClients();
    } catch (Throwable th) {
      // odnotowujemy błąd
      erreurInit = true;
      prepareVueErreur(th);
      return;
    }
    // weryfikacja list
    if (medecins.size() == 0) {
      // odnotowujemy błąd
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des médecins est vide"));
    }
    if (clients.size() == 0) {
      // odnotowuje się błąd
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des clients est vide"));
    }
    // błąd?
    if (erreurInit) {
      // wyświetlono widok błędów
      setForms(false, false, false, true);
      return;
    }

    // słowniki
    for (Medecin m : medecins) {
      hMedecins.put(m.getId(), m);
    }
    for (Client c : clients) {
      hClients.put(c.getId(), c);
    }
    // widok 1
    setForms(true, false, false, false);
  }

  // wyświetlanie widoku
  private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
    this.form1Rendered = form1Rendered;
    this.form2Rendered = form2Rendered;
    this.form3Rendered = form3Rendered;
    this.erreurRendered = erreurRendered;
  }

  // przygotowanie vueErreur
  private void prepareVueErreur(Throwable th) {
    // tworzy się listę błędów
    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()));
    }
// wyświetlany jest widok błędów
    setForms(false, false, false, true);
  }

  // metody pobierające i ustawiające
  ..
}
  • wiersze 5–7: klasa [Form] jest beanem o nazwie „form” i zasięgu sesji. Przypominamy, że w takim przypadku klasa musi być serializowalna,
  • wiersze 12–13: bean „form” posiada odwołanie do beana „application”. Odwołanie to zostanie wstrzyknięte przez kontener serwletów, w którym działa aplikacja (obecność adnotacji @Inject).
  • wiersze 24–27: sterują wyświetlaniem widoków vue1 (wiersz 24), vue2 (wiersz 25), vue3 (wiersz 26), vueErreurs (wiersz 27),
  • wiersze 43–44: metoda init jest wykonywana zaraz po instancjonowaniu klasy (obecność adnotacji @PostConstruct),
  • wiersze 49–50: obsługują format dat. PrimeFaces Mobile nie oferuje kalendarza. Ponadto walidatory JSF nie mogą być używane na stronie PFM. Będziemy więc musieli ręcznie zarządzać wprowadzaniem daty w kalendarzu,
  • wiersz 49: określa format daty. Format ten jest pobierany z plików międzynarodowych:

format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
  • wiersz 50: należy zaznaczyć, że nie wolno stosować formatu „laxiste” w odniesieniu do dat. Jeśli tego nie zrobimy, wpis taki jak 32/12/2011, który jest nieprawidłowy, zostanie uznany za prawidłową datę 01/01/2012,
  • wiersz 54: pobieramy odwołanie do warstwy [métier] z bean’a [Application],
  • wiersze 57–58: żądamy od warstwy [métier] listy lekarzy i klientów,
  • wiersze 66–91: jeśli wszystko przebiegło pomyślnie, tworzone są słowniki lekarzy i klientów. Są one indeksowane według numerów. Następnie wyświetlany jest widok [vue1.xhtml] (wiersz 93),
  • wiersz 59: w przypadku błędu tworzony jest szablon strony [vueErreurs.xhtml]. Szablon ten zawiera listę błędów z wiersza 35,
  • wiersze 105–115: metoda [prepareVueErreur] tworzy listę błędów do wyświetlenia. Strona [index.xhtml] wyświetla następnie widok [vueErreurs.xhtml] (wiersz 114).

Strona [erreur.xhtml] wygląda następująco:


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

  <!-- Widok błędów -->
  <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>

Wykorzystuje ona tag <p:dataList> (wiersze 21–23) do wyświetlenia listy błędów. Przycisk w wierszu 13 pozwala powrócić do widoku vue1.

  • przycisk [1] jest generowany przez wiersz 13. Atrybut icon określa ikonę przycisku. Przycisk ten powoduje powrót do widoku vue1 (atrybut href),
  • nagłówek [2] jest generowany przez wiersze 11–15,
  • nagłówek [3] jest generowany przez wiersz 18,
  • tekst [4] jest generowany przez szablon #{erreur.classe} z wiersza 22,
  • tekst [5] jest generowany przez szablon #{erreur.message} z wiersza 22.

Teraz zdefiniujemy różne fazy cyklu życia aplikacji. Dla każdej akcji użytkownika przeanalizujemy odpowiednie widoki oraz procedury obsługi zdarzeń, które w nich występują.

9.8. Wyświetlanie strony głównej

Jeśli wszystko przebiega prawidłowo, pierwszym wyświetlanym widokiem jest [vue1.xhtml]. Wygląda to następująco:

Kod widoku [vue1.xhtml] wygląda następująco:


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

  <!-- Widok 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>
  • wiersze 11–15: generują nagłówek [1],
  • wiersz 13: generuje przycisk [2]. Kliknięcie tego przycisku powoduje wyświetlenie widoku konfiguracyjnego (atrybut href),
  • wiersz 19: generuje tytuł [3],
  • wiersze 21–26: generują listę rozwijaną lekarzy o nazwie [4],
  • wiersze 27–34: generują pole wprowadzania daty [5]. Pole to jest tworzone za pomocą tagu <p:inputText> bez walidatora. Walidacja daty zostanie przeprowadzona po stronie serwera. Jeśli data jest nieprawidłowa, serwer wyświetli komunikat o błędzie za pomocą wierszy 30–34,
  • wiersz 35: przycisk zatwierdzający formularz. Aktualizuje on trzy obszary: form1 (formularz w widoku 1), vue2 oraz vueErreurs. Jeśli data jest nieprawidłowa, należy zaktualizować form1. Jeśli data jest poprawna, należy zaktualizować vue2. Wreszcie, jeśli wystąpi wyjątek (na przykład przerwane połączenie z bazą danych), należy wyświetlić vueErreurs. Można ulec pokusie, aby zamiast form1 ustawić vue1 (aktualizując cały widok). W takim przypadku aplikacja ulegnie awarii.

Ten widok opiera się na następującym modelu:


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

  public Form() {
  }

  // Bean aplikacji
  @Inject
  private Application application;
  private IMetierLocal metier;
  // pamięć podręczna sesji
  private List<Medecin> medecins;
  private List<Client> clients;
  // szablon
  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;
  
    // lista lekarzy
  public List<Medecin> getMedecins() {
    return medecins;
  }

  // kalendarz
  public String getAgenda() {
...
    }
  }
  • pole w wierszu 16 zasilają w trybie odczytu i zapisu wartość listy w wierszu 23 strony. Przy pierwszym wyświetleniu strony ustala ono wartość wybraną w polu kombi,
  • metoda z wierszy 27–29 generuje elementy listy rozwijanej lekarzy (wiersz 24 widoku). Każda wygenerowana opcja będzie miała jako etykietę (itemLabel) tytuł, nazwisko i imię lekarza, a jako wartość (itemValue) identyfikator lekarza,
  • pole w wierszu 18 zasilają w trybie odczytu/zapisu pole wprowadzania danych w wierszu 29 strony,
  • wiersze 32–34: metoda getAgenda obsługuje kliknięcie przycisku [Agenda] w wierszu 35 strony. Jej kod jest następujący:

// kalendarz
  public String getAgenda() {
    try {
      // sprawdzanie dnia
      jour = dateFormatter.parse(strJour);
      // brak błędu
      erreurDate=false;
      msgErreurDate = "";
      // tworzy się kalendarz
      return getAgenda(jour);
    } catch (ParseException ex) {
      // komunikat o błędzie
      erreurDate=true;
      msgErreurDate = Messages.getMessage(null, "form1.date.invalide", null).getSummary();
      // widok1      
      setForms(true, false, false, false);
      return "pm:vue1";
    }
  }
  • metoda rozpoczyna się od sprawdzenia poprawności daty wprowadzonej przez użytkownika,
  • wiersz 5: data ma postać parsée zgodnie z formatem daty zainicjowanym przez metodę init podczas instancjonowania modelu,
  • wiersz 11: jeśli wystąpi wyjątek, ustawiany jest błąd (wiersz 13), tworzony jest zinternacjonalizowany komunikat o błędzie (wiersz 14), przygotowywany jest widok vue1 (wiersz 16) i wyświetlany jest widok vue1 (wiersz 17),
  • wiersz 10: jeśli data jest poprawna, wykonywana jest następująca metoda:

  // kalendarz
  public String getAgenda(Date jour) {
    // na razie nie wybrano żadnego terminu
    creneauChoisi = null;
    try {
      // pobieranie wybranego lekarza
      medecin = hMedecins.get(idMedecin);
      // nagłówek formularza 2
      form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
      // kalendarz lekarza na dany dzień
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // wyświetlanie widoku 2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      System.out.println(th);
      // widok błędów
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}

Jest to kod, z którym spotkaliśmy się już wielokrotnie. W wierszu 9 tworzony jest międzynarodowy komunikat dla widoku vue2:


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

Warto zauważyć, że w komunikacie umieściliśmy kod XHTML. Zostanie on wyświetlony w następujący sposób:

9.9. Wyświetlanie kalendarza lekarza

Harmonogram lekarza jest wyświetlany przez widok [vue2.xhtml]:

Kod widoku [vue2.xhtml] jest następujący:


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

  <!-- Widok 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>
  • wiersz 11: generuje nagłówek [1],
  • wiersze 15–20: generują grupę przycisków [2],
  • wiersz 21: generuje tytuł [3]. Należy zwrócić uwagę na wartość atrybutu escape. To właśnie ona pozwala zinterpretować kod XHTML, który umieściliśmy w form2Titre,
  • wiersz 24: wyświetlanie przedziałów czasowych odbywa się za pomocą kodu dataList,
  • wiersze 28–33: generują tytuł przedziału czasowego [4],
  • wiersze 34–37: wyświetlają fragment, jeśli w danym przedziale czasowym znajduje się spotkanie,
  • wiersz 36: wyświetla dane klienta, który umówił spotkanie,
  • wiersze 41–45: wyświetlają przycisk [Supprimer], jeśli istnieje spotkanie,
  • wiersze 46–50: wyświetlają przycisk [Réserver], jeśli nie ma spotkania.

Ten widok jest zasilany głównie przez następujący model:


private AgendaMedecinJour agendaMedecinJour;

który zasila pole dataList w wierszu 24. Pole to zostało utworzone przez metodę getAgenda podczas przejścia z widoku vue1 do widoku vue2.

9.10. Usunięcie spotkania

Usunięcie terminu odbywa się zgodnie z następującą sekwencją:

Widok, którego dotyczy ta operacja, jest następujący:


<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>
  • wiersz 2: przycisk [Supprimer] jest powiązany z metodą [Form].action (atrybut action),
  • wiersz 3: identyfikator przedziału, w którym aktualnie się znajdujemy, zostanie przesłany do modelu [Form].idCreneauChoisi,
  • wiersz 2: wywołanie AJAX zaktualizuje pola formularza form2 (formularz widoku 2) oraz widok vueErreurs. Mamy tu bowiem dwa przypadki: jeśli wszystko przebiegnie pomyślnie, ponownie wyświetlony zostanie widok vue2, w przeciwnym razie wyświetlony zostanie widok vueErreurs.

Metoda [action] wygląda następująco:


  // operacja na RV
  public String action() {
    // szukamy wolnego terminu w kalendarzu
    int i = 0;
    Boolean trouvé = false;
    while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
      if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneauChoisi) {
        trouvé = true;
      } else {
        i++;
      }
    }
    // czy coś znaleziono?
    if (!trouvé) {
      // to dziwne – ponownie wyświetlamy form2
      setForms(false, true, false, false);
      return "pm:vue2";
    } else {
      creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
    }
    // znaleziono
    // zgodnie z wybraną czynnością
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }

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

  public String supprimer() {
    try {
      // usunięcie spotkania
      metier.supprimerRv(creneauChoisi.getRv());
      // aktualizujemy kalendarz
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // wyświetlono form2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // widok błędów
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}
  • wiersze 3–12: wyszukujemy przedział czasowy, którego identyfikator otrzymaliśmy (wiersz 7),
  • jeśli go nie znajdziemy, co jest sytuacją nietypową, ponownie wyświetlamy widok2 (wiersze 16–17),
  • wiersz 19: jeśli go znajdziemy, zapisujemy odpowiadający mu obiekt [CreneauMedecinJour]. To właśnie on daje nam dostęp do spotkania, które ma zostać usunięte,
  • wiersz 26: usuwamy go,
  • wiersze 35–48: metoda „usuń” zwraca widok vue2, jeśli usunięcie przebiegło pomyślnie (wiersze 42–43), lub widok vueErreurs, jeśli wystąpił problem (wiersze 46–47).

9.11. Umówienie wizyty

Rezerwacja terminu przebiega zgodnie z następującą sekwencją:

Przechodzimy z widoku vue2 do widoku vue3. Kod związany z tą czynnością jest następujący:


<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>
  • wiersz 2: przycisk [Réserver] jest powiązany z metodą [Form].action (atrybut action), a więc tą samą, co w przypadku przycisku [Supprimer]. Wywołanie AJAX aktualizuje widoki vue3 i vueErreurs w zależności od tego, czy podczas przetwarzania wywołania wystąpiły błędy.
  • wiersz 3: podobnie jak w przypadku przycisku [Supprimer], identyfikator przedziału czasowego jest przekazywany do modelu.

Szablon obsługujący tę akcję jest następujący:


// operacja na RV
  public String action() {
    ...
    // w zależności od żądanej akcji
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }

  // rezerwacja
  public String reserver() {
    try {
      // nazwa formularza 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();
      // wyświetlany jest formularz 3
      setForms(false, false, true, false);
      return "pm:vue3";
    } catch (Throwable th) {
      // widok błędów
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }
  • wiersze 2–10: metoda action określi referencję creneauChoisi obiektu [CreneauMedecinJour], który jest przedmiotem rezerwacji, a następnie wywoła metodę reserver,
  • wiersz 16: tworzony jest komunikat z uwzględnieniem lokalizacji. Brzmi on następująco:
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,liczba,#00}-{6,liczba,#00}h:{7,liczba,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}

Będzie to tytuł widoku vue3. Podobnie jak w przypadku widoku vue2, tytuł ten zawiera ciąg znaków XML. Zawiera on również parametry sformatowane w celu wyświetlenia godzin przedziału czasowego,

  • wiersze 19–20: wyświetlany jest widok vue3,
  • wiersze 23–24: widok vueErreurs jest wyświetlany, jeśli wystąpiły problemy.

9.12. Potwierdzanie terminu spotkania

Potwierdzenie terminu odbywa się zgodnie z następującą sekwencją:

W widoku [1] wyświetlany jest widok vue3, a w widoku [2] – widok vue2 po dodaniu terminu.

Kod [vue3.xhtml] widoku vue3 wygląda następująco:


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

  <!-- Widok 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>
  • wiersz 16: generuje tytuł widoku [3]. Należy zwrócić uwagę na wartość atrybutu escape, który umożliwia interpretację znaków XHTML w tytule,
  • wiersze 18–23: generują listę rozwijaną klientów o nazwie [4],
  • wiersz 25: generuje przycisk o nazwie [Valider] [5]. Z tym przyciskiem powiązana jest metoda [Form].validerRv:

// potwierdzenie terminu
  public String validerRv() {
    try {
      // pobieramy instancję wybranego terminu
      Creneau creneau = metier.getCreneauById(idCreneauChoisi);
      // dodajemy spotkanie
      metier.ajouterRv(jour, creneau, hClients.get(idClient));
      // aktualizujemy kalendarz
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // wyświetla się form2
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // widok błędów
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }

Kod ten pojawił się już w wersji 01. Zwróćmy jedynie uwagę na wyświetlanie widoków:

  • widok vue2 (wiersze 11–12), jeśli wszystko przebiegło pomyślnie,
  • widok vueErreurs (wiersze 15–16) w przeciwnym razie.

9.13. Anulowanie umówionego terminu

Odpowiada to następującej sekwencji:

Przycisk [1] w widoku [vue3.xhtml] wygląda następująco:


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

Wywoływana jest zatem metoda [Form].showVue2. Wyświetla ona po prostu widok2:


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

9.14. Nawigacja w kalendarzu

W widoku 2 przyciski umożliwiają nawigację po kalendarzu:

Poprzedni dzień:

Następny dzień:

Dzisiaj:

Chociaż nie widać tego na powyższych zrzutach ekranu, kalendarz jest aktualizowany i wyświetla spotkania z nowo wybranego dnia.

Tagi trzech odpowiednich przycisków w pliku [vue2.xhtml] są następujące:


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

Metody [Form].getPreviousAgenda, [Form].getNextAgenda, [Form].today zostały omówione w przykładzie 03.

9.15. Zmiana języka wyświetlania

Zmiana języka odbywa się za pomocą przycisku na stronie głównej:

Kod przycisku jest następujący:


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

Przekazuje więc dane do widoku konfiguracyjnego [2]. Widok [config.xhtml] wygląda następująco:


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

  <!-- Widok 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>
  • wiersz 11: powoduje wyświetlenie widoku [3],
  • wiersz 13: powoduje wyświetlenie przycisku [4]. Przycisk ten pozwala powrócić do widoku vue1,
  • wiersz 17: formularz widoku,
  • wiersz 19: powoduje wyświetlenie tytułu widoku [5],
  • wiersze 21–27: powodują wyświetlenie przycisków opcji. Wartość (itemValue) wybranego przycisku opcji zostanie przesłana do szablonu [Form].locale (atrybut „value” w wierszu 23),
  • wiersz 28: powoduje wyświetlenie przycisku [Valider]. Wywołanie AJAX aktualizuje widok vue1 (atrybut update). Wywoływana metoda to [Form].configurer:

public String configurer(){
    // po konfiguracji – ponowne wyświetlenie widoku 1
    redirect();
    return null;
  }
  
  private void redirect() {
    // klient jest przekierowywany do serwletu
    ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
    try {
      ctx.redirect(ctx.getRequestContextPath());
    } catch (IOException ex) {
      Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

Metoda „configurer” (wiersz 1) po prostu przekierowuje przeglądarkę urządzenia mobilnego do strony URL w aplikacji. W związku z tym zostanie załadowana strona [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">
      ...
    </pm:view>
    ...
  </pm:page>    
</f:view>
  • wiersz 8: widok wykorzysta język, który właśnie został zmieniony (atrybut „locale”) i wyświetli widok1 (wiersz 11).

9.16. Conclusion

Przypomnijmy sobie architekturę aplikacji, którą właśnie stworzyliśmy:

Przejście na interfejs mobilny wymagało przepisania stron XHTML. Model natomiast uległ niewielkim zmianom. Niższe warstwy [métier], [DAO], [JPA] pozostały natomiast całkowicie niezmienione.

9.17. Testy w Eclipse

Podobnie jak w przypadku poprzednich wersji przykładowej aplikacji, pokazujemy, jak przetestować wersję 05 za pomocą Eclipse. Najpierw importujemy do Eclipse projekty Maven z przykładu 05 [1]:

  • [mv-rdvmedecins-ejb-dao-jpa]: warstwy [DAO] i [JPA],
  • [mv-rdvmedecins-ejb-metier]: warstwa [métier],
  • [mv-rdvmedecins-pfmobile]: warstwa [web] zaimplementowana przez Primefaces mobile,
  • [mv-rdvmedecins-pfmobile-app]: projekt nadrzędny projektu korporacyjnego [mv-rdvmedecins-pfmobile-app-ear]. Po zaimportowaniu projektu nadrzędnego projekt podrzędny jest automatycznie importowany,
  • w [2] uruchamia się projekt korporacyjny [mv-rdvmedecins-pfmobile-app-ear],
  • w [3] wybieramy serwer Glassfish,
  • w [4], w zakładce [Servers] aplikacja została wdrożona. Nie uruchamia się ona samodzielnie. Należy wywołać ją w przeglądarce lub symulatorze urządzenia mobilnego: