Skip to content

9. Applicazione di esempio 05: rdvmedecins-pfm-ejb

Esaminiamo la struttura dell'applicazione di esempio JSF/EJB 01 sviluppata per il server GlassFish:

Non modificheremo nulla di questa architettura, ad eccezione del livello web, che verrà implementato utilizzando JSF, PrimeFaces e PrimeFaces Mobile. Il browser di destinazione sarà un browser mobile.

Abbiamo sviluppato due applicazioni con GlassFish:

  • L'applicazione 01 utilizzava JSF/EJB e aveva un'interfaccia piuttosto semplice,
  • L'applicazione 03 utilizzava PF/EJB e aveva un'interfaccia ricca.

A causa del numero limitato di componenti disponibili in PrimeFaces Mobile, torneremo all'interfaccia di base dell'Applicazione 01. Inoltre, le viste devono essere in grado di adattarsi alle dimensioni ridotte dello schermo dei dispositivi mobili.

9.1. Le viste

Per darvi un'idea, ecco alcuni screenshot dell'applicazione in esecuzione su un simulatore di iPhone 4:

  • in [1], la pagina iniziale. Notate che questa volta abbiamo dovuto specificare il nome del computer (è possibile anche inserire il suo indirizzo IP), poiché utilizzando il computer localhost non veniva visualizzato nulla,
  • in [2], il menu a tendina dei medici,
  • in [3], la data desiderata per l'appuntamento,
  • in [4], il pulsante per richiedere l'orario giornaliero,
  • in [5], la nuova vista mostra le fasce orarie disponibili del medico,
  • in [6], una serie di pulsanti per navigare nel calendario,
  • in [7], un messaggio che ricorda il medico e il giorno,
  • in [8], una fascia oraria da prenotare. Procediamo,
  • in [9], la schermata di selezione del cliente,
  • in [10], un messaggio che ricorda all'utente il medico, il giorno e la fascia oraria dell'appuntamento,
  • in [11], il menu a tendina del cliente,
  • in [12], il pulsante di conferma,
  • in [13], cliccando sul pulsante si torna al calendario,
  • in [14], la fascia oraria occupata è ora prenotata. Ora cancelleremo la prenotazione,
  • in [15], rimaniamo nella stessa schermata,
  • ma in [16], l'appuntamento è stato cancellato,

Nella pagina iniziale è possibile cambiare la lingua [17], [18], [19]:

Infine, abbiamo incluso una schermata di errore:

9.2. Il progetto NetBeans

Il progetto NetBeans è il seguente:

  
  • [mv-rdvmedecins-ejb-dao-jpa]: Progetto EJB per i livelli [DAO] e [JPA] dell'Esempio 01,
  • [mv-rdvmedecins-ejb-metier]: Progetto EJB per il livello [aziendale] dell'Esempio 01,
  • [mv-rdvmedecins-pfmobile]: progetto del livello [web] / PrimeFaces Mobile – nuovo,
  • [mv-rdvmedecins-pfmobile-app-ear]: progetto aziendale per distribuire l'applicazione sul server GlassFish – nuovo.

9.3. Il progetto enterprise

Il progetto enterprise viene utilizzato esclusivamente per distribuire i tre moduli [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-metier], [mv-rdvmedecins-pfmobile] sul server GlassFish. Il progetto NetBeans è il seguente:

Il progetto esiste esclusivamente per queste tre dipendenze [1] definite nel file [pom.xml] come segue:


<?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>
  • righe 6–9: l'artefatto Maven per il progetto aziendale,
  • righe 14–33: le tre dipendenze del progetto. Notare i loro tipi (righe 19, 25, 31).

Per eseguire l'applicazione web, è necessario eseguire questo progetto enterprise.

9.4. Il progetto web PrimeFaces Mobile

Il progetto web PrimeFaces Mobile è il seguente:

  • in [1], le pagine del progetto. La pagina [index.xhtml] è l'unica pagina del progetto. Contiene cinque viste: [vue1.xhtml], [vue2.xhtml], [vue3.xhtml], [vueErreurs.xhtml] e [config.xhtml],
  • in [2], i bean Java. Il bean [Application] ha ambito applicativo, mentre il bean [Form] ha ambito di sessione. La classe [Error] incapsula un errore,
  • in [3], i file dei messaggi per l'internazionalizzazione,
  • in [4], le dipendenze. Il progetto web dipende dal progetto EJB per il livello [DAO], dal progetto EJB per il livello [business] e da PrimeFaces Mobile per il livello [web].

9.5. Configurazione del progetto

La configurazione del progetto è identica a quella dei progetti PrimeFaces o JSF che abbiamo studiato. Elenchiamo i file di configurazione senza ripeterne la descrizione.

 

[web.xml]: configura l'applicazione web.


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

Si noti che alla riga 26 la pagina [index.xhtml] è la pagina iniziale dell'applicazione.

[faces-config.xml]: configura l'applicazione 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]: vuoto ma necessario per l'annotazione @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]: il file dei messaggi in francese


# page
page.titre=Les M\u00e9decins Associ\u00e9s
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
 
# vue1
vue1.header=Les M\u00e9decins Associ\u00e9s - R\u00e9servations
 
# formulaire 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
 
# formulaire 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'today
 
# formulaire 3
form3.client=Client
form3.valider=Valider
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}
form3.retour=Retour
 
# erreur
erreur.titre=Une erreur s'is produced.
 
# config
config.retour=Retour
config.titre=Configuration
config.langue=Langue
config.langue.francais=Fran\u00e7ais
config.langue.anglais=Anglais
config.valider=Valider
 
#exception
exception.titre=Application indisponible. Veuillez recommencer ult\u00e9rieurement.

[messages_en.properties]: il file dei messaggi in inglese


# page
page.titre=The Associated Doctors
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
 
# vue1
vue1.header=The Associated Doctors - Reservations
 
# formulaire 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

# formulaire 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
 
# formulaire 3
form3.client=Patient
form3.valider=Validate
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}
form3.retour=Back
 
# erreur
erreur.titre=Some error happened
 
# config
config.retour=Back
config.titre=Configuration
config.langue=Language
config.langue.francais=French
config.langue.anglais=English
config.valider=Validate
 
#exception
exception.titre=Application not available. Please try again later.

9.6. La pagina [index.xhtml]

Il progetto visualizza sempre la stessa pagina, ovvero la seguente pagina [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>
  • riga 8: la pagina è internazionalizzata (attributo locale),
  • riga 10: la pagina contiene cinque viste: view1 alla riga 11, view2 alla riga 19, view3 alla riga 24, viewErrors alla riga 29 e config alla riga 34. In un dato momento, è visibile solo una di queste viste. All'avvio dell'applicazione, viene visualizzata view1. Qui abbiamo riscontrato il seguente problema: se l'applicazione si è inizializzata correttamente, dobbiamo visualizzare [view1.xhtml]; altrimenti, deve essere visualizzato [errorView.xhtml]. Abbiamo risolto il problema assicurandoci che il contenuto della vista vue1 fosse gestito dal modello, che imposta i valori dei booleani [Form].form1rendered (riga 12) e [Form].erreurInit (riga 15) per determinare il contenuto di vue1 (riga 11),

In un simulatore, la vista [vue1.xhtml] viene visualizzata come [1], la vista [vue2.xhtml] come [2] e la vista [vue3.xhtml] come [3]:

la vista [vueErreurs.xhtml] ha il rendering [4], la vista [config.xhtml] ha il rendering [5]:

9.7. I bean del progetto

La classe nel pacchetto [utils] è già stata presentata: la classe [Messages] è una classe che facilita l'internazionalizzazione dei messaggi di un'applicazione. Ne abbiamo parlato nella Sezione 2.8.5.7.

9.7.1. Il bean Application

Il bean [Application.java] è un bean a livello di applicazione. Ricordiamo che questo tipo di bean viene utilizzato per memorizzare dati di sola lettura disponibili a tutti gli utenti dell'applicazione. Questo bean è il seguente:


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 {
 
  // business layer
  @EJB
  private IMetierLocal metier;
 
  public Application() {
  }
  
  // getters
 
  public IMetierLocal getMetier() {
    return metier;
  }
  
}
  • riga 8: assegniamo al bean il nome "application",
  • riga 9: ha ambito applicativo,
  • righe 13–14: un riferimento all'interfaccia locale del livello [business] verrà iniettato al suo interno dal contenitore EJB del server dell'applicazione. Rivediamo l'architettura dell'applicazione:

L'applicazione PFM e l'EJB [Business] verranno eseguiti nella stessa JVM (Java Virtual Machine). Pertanto, il livello [PFM] utilizzerà l'interfaccia locale dell'EJB. Questo è tutto. Il bean [Application] non contiene nient'altro. Per accedere al livello [Business], gli altri bean lo recupereranno da questo bean.

9.7.2. Il bean [Error]

La classe [Error] è la seguente:


package beans;
 
public class Erreur {
  
  public Erreur() {
  }
  
  // field
  private String classe;
  private String message;
 
  // manufacturer
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
  
  // getters and setters
...  
}
  • riga 9: il nome di una classe di eccezione se è stata generata un'eccezione,
  • riga 10: un messaggio di errore.

9.7.3. Il bean [Form]

Il suo codice è il seguente:


package beans;
 
...
 
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
 
  public Form() {
  }
  // bean Application
  @Inject
  private Application application;
  private IMetierLocal metier;
  // session cache
  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");
    // initially no error
    erreurInit = false;
    // date formatting
    dateFormatter = new SimpleDateFormat(Messages.getMessage(null, "format.date", null).getSummary());
    dateFormatter.setLenient(false);
    // the current day
    strJour = dateFormatter.format(jour);
    // recover the business layer
    metier = application.getMetier();
    // caching doctors and customers
    try {
      medecins = metier.getAllMedecins();
      clients = metier.getAllClients();
    } catch (Throwable th) {
      // we note the error
      erreurInit = true;
      prepareVueErreur(th);
      return;
    }
    // list checking
    if (medecins.size() == 0) {
      // we note the error
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des médecins est vide"));
    }
    if (clients.size() == 0) {
      // we note the error
      erreurInit = true;
      erreurs = new ArrayList<Erreur>();
      erreurs.add(new Erreur("", "La liste des clients est vide"));
    }
    // mistake?
    if (erreurInit) {
      // the error view is displayed
      setForms(false, false, false, true);
      return;
    }
 
    // dictionaries
    for (Medecin m : medecins) {
      hMedecins.put(m.getId(), m);
    }
    for (Client c : clients) {
      hClients.put(c.getId(), c);
    }
    // view 1
    setForms(true, false, false, false);
  }
 
  // view display
  private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
    this.form1Rendered = form1Rendered;
    this.form2Rendered = form2Rendered;
    this.form3Rendered = form3Rendered;
    this.erreurRendered = erreurRendered;
  }
 
  // preparation vueErreur
  private void prepareVueErreur(Throwable th) {
    // create an error list
    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()));
    }
// the error view is displayed
    setForms(false, false, false, true);
  }
 
  // getters and setters
  ..
}
  • righe 5-7: la classe [Form] è un bean denominato "form" con ambito di sessione. Si noti che la classe deve quindi essere serializzabile,
  • righe 12–13: il bean form ha un riferimento al bean application. Questo riferimento verrà iniettato dal contenitore servlet in cui l'applicazione è in esecuzione (presenza dell'annotazione @Inject).
  • righe 24–27: controllano la visualizzazione delle viste vue1 (riga 24), vue2 (riga 25), vue3 (riga 26) e vueErrors (riga 27),
  • righe 43–44: il metodo init viene eseguito immediatamente dopo l'istanziazione della classe (presenza dell'annotazione @PostConstruct),
  • righe 49-50: gestiscono il formato della data. PrimeFaces Mobile non fornisce un calendario. Inoltre, i validatori JSF non possono essere utilizzati in una pagina PFM. Pertanto, dovremo gestire manualmente l'inserimento della data del calendario,
  • riga 49: imposta il formato della data. Questo formato è tratto dai file di internazionalizzazione:

format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
  • Riga 50: specifichiamo che non dobbiamo essere "tolleranti" riguardo alle date. Se non lo facciamo, un inserimento come 12/32/2011 — che è un inserimento errato — viene considerato come la data valida 01/01/2012,
  • riga 54: recuperiamo un riferimento al livello [business] dal bean [Application],
  • righe 57-58: richiediamo l'elenco dei medici e dei clienti dal livello [business],
  • righe 66–91: se tutto è andato bene, vengono costruiti i dizionari dei medici e dei clienti. Sono indicizzati in base al loro numero. Quindi, verrà visualizzata la vista [vue1.xhtml] (riga 93),
  • riga 59: in caso di errore, viene costruito il modello di pagina [errorView.xhtml]. Questo modello è l'elenco degli errori della riga 35,
  • righe 105–115: il metodo [prepareVueErreur] crea l'elenco degli errori da visualizzare. La pagina [index.xhtml] visualizza quindi la vista [vueErreurs.xhtml] (riga 114).

La pagina [error.xhtml] è la seguente:


<?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">
 
  <!-- Errors view -->
  <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>

Utilizza un tag <p:dataList> (righe 21–23) per visualizzare l'elenco degli errori. Il pulsante alla riga 13 consente di tornare alla vista vue1.

  • Il pulsante [1] viene generato dalla riga 13. L'attributo icon imposta l'icona del pulsante. Il pulsante riporta alla vista vue1 (attributo href),
  • l'intestazione [2] è generata dalle righe 11–15,
  • il titolo [3] è generato dalla riga 18,
  • il testo [4] è generato dal template #{error.class} alla riga 22,
  • il testo [5] è generato dal template #{erreur.message} alla riga 22.

Definiremo ora le diverse fasi del ciclo di vita dell'applicazione. Per ogni azione dell'utente, esamineremo le viste pertinenti e i gestori di eventi che si verificano al loro interno.

9.8. Visualizzazione della home page

Se tutto va bene, la prima vista visualizzata è [vue1.xhtml]. Il risultato è la seguente vista:

Il codice della vista [vue1.xhtml] è il seguente:


<?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">
 
  <!-- View 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>
  • righe 11–15: generano l'intestazione [1],
  • Riga 13: crea il pulsante [2]. Cliccandoci sopra si visualizza la vista di configurazione (attributo href),
  • riga 19: visualizza il titolo [3],
  • Righe 21–26: generano il menu a tendina del medico [4],
  • righe 27–34: visualizzano il campo di immissione della data [5]. Questo campo di immissione utilizza un tag <p:inputText> senza validatore. La convalida della data verrà eseguita sul lato server. Se la data non è corretta, il server imposterà un messaggio di errore visualizzato dalle righe 30–34,
  • riga 35: il pulsante che invia il modulo. Aggiorna tre aree: form1 (il modulo in vue1), vue2 e vueErrors. Infatti, se la data non è valida, form1 deve essere aggiornato. Se la data è corretta, vue2 deve essere aggiornato. Infine, se si verifica un'eccezione (come una connessione al database interrotta), deve essere visualizzata vueErrors. Potresti essere tentato di usare vue1 invece di form1 (aggiornando l'intera vista). In tal caso, l'applicazione andrà in crash.

Questa vista è supportata dal seguente modello:


@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
 
  public Form() {
  }
 
  // bean Application
  @Inject
  private Application application;
  private IMetierLocal metier;
  // session cache
  private List<Medecin> medecins;
  private List<Client> clients;
  // 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 msgErreurDate = "";
  private Boolean erreurDate;
  
    // list of doctors
  public List<Medecin> getMedecins() {
    return medecins;
  }
 
  // agenda
  public String getAgenda() {
...
    }
  }
  • Il campo alla riga 16 legge e scrive il valore dell'elenco alla riga 23 della pagina. Quando la pagina viene visualizzata per la prima volta, imposta il valore selezionato nella casella combinata,
  • Il metodo alle righe 27–29 genera le voci per l'elenco a discesa dei medici (riga 24 della vista). Ogni opzione generata avrà come etichetta (itemLabel) il titolo, il cognome e il nome del medico e come valore (itemValue) l'ID del medico.
  • il campo alla riga 18 fornisce l'accesso in lettura/scrittura al campo di immissione alla riga 29 della pagina,
  • Righe 32–34: Il metodo getAgenda gestisce il clic sul pulsante [Agenda] alla riga 35 della pagina. Il suo codice è il seguente:

// agenda
  public String getAgenda() {
    try {
      // on vérifie le jour
      jour = dateFormatter.parse(strJour);
      // pas d'erreur
      erreurDate=false;
      msgErreurDate = "";
      // on crée l'agenda
      return getAgenda(jour);
    } catch (ParseException ex) {
      // msg d'erreur
      erreurDate=true;
      msgErreurDate = Messages.getMessage(null, "form1.date.invalide", null).getSummary();
      // vue1      
      setForms(true, false, false, false);
      return "pm:vue1";
    }
  }
  • Il metodo inizia verificando la validità della data inserita dall'utente,
  • riga 5: la data viene analizzata in base al formato di data inizializzato dal metodo init al momento dell'istanziazione del modello,
  • riga 11: se si verifica un'eccezione, viene impostato un errore (riga 13), viene costruito un messaggio di errore internazionalizzato (riga 14), viene preparata la vista vue1 (riga 16) e viene visualizzata la vista vue1 (riga 17),
  • riga 10: se la data è valida, viene eseguito il seguente metodo:

  // agenda
  public String getAgenda(Date jour) {
    // no slots selected yet
    creneauChoisi = null;
    try {
      // we pick up the chosen doctor
      medecin = hMedecins.get(idMedecin);
      // title form 2
      form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
      // the doctor's diary for a given day
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // view 2 is displayed
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      System.out.println(th);
      // error view
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}

Qui vediamo del codice che è già apparso diverse volte in precedenza. Alla riga 9, viene costruito un messaggio internazionalizzato per la vista vue2:


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

Si noti che abbiamo incluso l'XHTML nel messaggio. Verrà visualizzato come segue:

9.9. Visualizza l'orario di un medico

L'orario del medico viene visualizzato dalla vista [vue2.xhtml]:

Il codice della vista [vue2.xhtml] è il seguente:


<?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">
 
  <!-- View 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>
  • riga 11: genera l'intestazione [1],
  • righe 15–20: genera il gruppo di pulsanti [2],
  • riga 21: genera il titolo [3]. Da notare il valore dell'attributo escape. È proprio questo che permette di interpretare il codice XHTML inserito in form2Titre,
  • riga 24: gli intervalli di tempo vengono visualizzati utilizzando un dataList,
  • righe 28–33: generano l'etichetta della fascia oraria [4],
  • righe 34–37: visualizzano un snippet se c'è un appuntamento nella fascia oraria,
  • riga 36: visualizza il nome del cliente che ha fissato l'appuntamento,
  • righe 41–45: visualizza il pulsante [Elimina] se c'è un appuntamento,
  • Righe 46–50: visualizza il pulsante [Prenota] se non ci sono appuntamenti.

Questa vista è popolata principalmente dal seguente modello:


private AgendaMedecinJour agendaMedecinJour;

che popola il campo dataList alla riga 24. Questo campo è stato creato dal metodo getAgenda durante il passaggio dalla vista 1 alla vista 2.

9.10. Eliminazione di un appuntamento

L'eliminazione di un appuntamento segue questa sequenza:

La schermata relativa a questa operazione è la seguente:


<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>
  • Riga 2: il pulsante [Elimina] è associato al metodo [Form].action (attributo action),
  • riga 3: l'ID dello slot attualmente selezionato verrà inviato al modello [Form].idCreneauChoisi,
  • riga 2: la chiamata AJAX aggiornerà i campi di form2 (modulo view2) e la vista vueErreurs. Sono possibili due scenari: se tutto va bene, verrà visualizzata nuovamente la vista vue2; in caso contrario, verrà visualizzata la vista vueErreurs.

Il metodo [action] è il seguente:


  // action on RV
  public String action() {
    // search for the time slot in the calendar
    int i = 0;
    Boolean trouvé = false;
    while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
      if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneauChoisi) {
        trouvé = true;
      } else {
        i++;
      }
    }
    // have we found?
    if (!trouvé) {
      // it's weird - form2 is redisplayed
      setForms(false, true, false, false);
      return "pm:vue2";
    } else {
      creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
    }
    // we found
    // according to desired action
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }
 
  // reservation
  public String reserver() {
 ...
  }
 
  public String supprimer() {
    try {
      // deleting an appointment
      metier.supprimerRv(creneauChoisi.getRv());
      // updating the agenda
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // form2 is displayed
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // error view
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
}
  • righe 3-12: cerchiamo la fascia oraria di cui abbiamo ricevuto l'ID (riga 7);
  • se non viene trovato, il che è anomalo, visualizziamo nuovamente view2 (righe 16-17),
  • riga 19: se viene trovato, viene memorizzato l'oggetto [CreneauMedecinJour] corrispondente. Questo oggetto ci dà accesso all'appuntamento da cancellare,
  • riga 26: lo eliminiamo,
  • righe 35–48: il metodo delete restituisce la vista vue2 se l'eliminazione è andata a buon fine (righe 42–43) o la vista vueErrors se si è verificato un problema (righe 46–47).

9.11. Fissare un appuntamento

La creazione di un appuntamento segue questa sequenza:

Si passa dalla vista vue2 alla vista vue3. Il codice relativo a questa azione è il seguente:


<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>
  • Riga 2: Il pulsante [Prenota] è associato al metodo [Form].action (attributo action), quindi è uguale a quello del pulsante [Elimina]. La chiamata AJAX aggiorna le viste vue3 e vueErrors a seconda che ci siano o meno errori durante l'elaborazione della chiamata.
  • Riga 3: Come per il pulsante [Delete], l'ID dello slot temporale viene passato al modello.

Il modello che gestisce questa azione è il seguente:


// action on RV
  public String action() {
    ...
    // according to desired action
    if (creneauChoisi.getRv() == null) {
      return reserver();
    } else {
      return supprimer();
    }
  }
 
  // reservation
  public String reserver() {
    try {
      // title form 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();
      // form 3 is displayed
      setForms(false, false, true, false);
      return "pm:vue3";
    } catch (Throwable th) {
      // error view
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }
  • righe 2–10: Il metodo action recupera il riferimento creneauChoisi dell'oggetto [CreneauMedecinJour] che si sta prenotando, quindi chiama il metodo reserver,
  • riga 16: Viene costruito un messaggio internazionalizzato. È il seguente:

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}

Questo sarà il titolo della vista vue3. Come per la vista vue2, questo titolo contiene XML. Include anche parametri formattati per visualizzare le fasce orarie,

  • righe 19-20: viene visualizzata la vista vue3,
  • righe 23-24: la vista vueErrors viene visualizzata se si sono verificati dei problemi.

9.12. Conferma di un appuntamento

La convalida di un appuntamento segue questa sequenza:

In [1], la vista vue3, e in [2], la vista vue2 dopo l'aggiunta di un appuntamento.

Il codice [vue3.xhtml] per vue3 è il seguente:


<?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">
 
  <!-- View 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>
  • riga 16: genera il titolo della vista [3]. Notare il valore dell'attributo escape, che consente l'interpretazione dei caratteri XHTML nel titolo,
  • righe 18–23: generano l'elenco a discesa del client [4],
  • riga 25: genera il pulsante [Validate] [5]. A questo pulsante è associato il metodo [Form].validateRv:

// rv validation
  public String validerRv() {
    try {
      // retrieve an instance of the chosen slot
      Creneau creneau = metier.getCreneauById(idCreneauChoisi);
      // we add the Rv
      metier.ajouterRv(jour, creneau, hClients.get(idClient));
      // updating the agenda
      agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
      // form2 is displayed
      setForms(false, true, false, false);
      return "pm:vue2";
    } catch (Throwable th) {
      // error view
      prepareVueErreur(th);
      return "pm:vueErreurs";
    }
  }

Questo codice era già presente nella versione 01. Si noti semplicemente la visualizzazione delle viste:

  • la vista vue2 (righe 11–12) se tutto è andato bene,
  • la vista vueErreurs (righe 15–16) in caso contrario.

9.13. Annullamento di un appuntamento

Ciò corrisponde alla seguente sequenza:

Il pulsante [1] nella vista [vue3.xhtml] è il seguente:


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

Viene quindi chiamato il metodo [Form].showVue2. Questo visualizza semplicemente vue2:


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

9.14. Navigazione nel calendario

In view2, i pulsanti consentono di navigare nel calendario:

Giorno precedente:

Giorno successivo:

Oggi:

Sebbene non sia visibile nelle schermate sopra riportate, il calendario viene aggiornato e mostra gli appuntamenti relativi al giorno appena selezionato.

I tag relativi ai tre pulsanti in questione sono i seguenti nel file [vue2.xhtml]:


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

I metodi [Form].getPreviousAgenda, [Form].getNextAgenda e [Form].today sono stati trattati nell'Esempio 03.

9.15. Modifica della lingua di visualizzazione

La lingua viene modificata utilizzando un pulsante nella pagina iniziale:

Il codice del pulsante è il seguente:


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

Si passa quindi alla vista di configurazione [2]. La vista [config.xhtml] è la seguente:


<?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">
 
  <!-- View 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>
  • riga 11: visualizza [3],
  • riga 13: visualizza il pulsante [4]. Questo pulsante consente di tornare alla vista vue1,
  • riga 17: il modulo della vista,
  • riga 19: visualizza il titolo della vista [5],
  • righe 21–27: visualizzano i pulsanti di opzione. Il valore (itemValue) del pulsante di opzione selezionato verrà inviato al modello [Form].locale (attributo value nella riga 23),
  • riga 28: visualizza il pulsante [Invia]. La chiamata AJAX aggiorna la vista vue1 (aggiornamento dell'attributo). Il metodo chiamato è [Form].configure:

public String configurer(){
    // after configuration - redisplay view1
    redirect();
    return null;
  }
  
  private void redirect() {
    // redirect the client to the servlet
    ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
    try {
      ctx.redirect(ctx.getRequestContextPath());
    } catch (IOException ex) {
      Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

Il metodo configure (riga 1) reindirizza semplicemente il browser mobile all'URL dell'applicazione. Pertanto, verrà caricata la pagina [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>
  • Riga 8: La vista utilizzerà la lingua appena modificata (attributo locale) e visualizzerà view1 (riga 11).

9.16. Conclusione

Diamo un'occhiata all'architettura dell'applicazione che abbiamo appena realizzato:

Il passaggio a un'interfaccia mobile ha richiesto la riscrittura delle pagine XHTML. Il modello, invece, è cambiato molto poco. I livelli inferiori [business], [DAO], [JPA] non sono cambiati affatto.

9.17. Test su Eclipse

Come abbiamo fatto per le versioni precedenti dell'applicazione di esempio, mostreremo come testare questa versione 05 con Eclipse. Per prima cosa, importiamo i progetti Maven dall'Esempio 05 [1] in Eclipse:

  • [mv-rdvmedecins-ejb-dao-jpa]: i livelli [DAO] e [JPA],
  • [mv-rdvmedecins-ejb-metier]: il livello [business],
  • [mv-rdvmedecins-pfmobile]: il livello [web] implementato da PrimeFaces Mobile,
  • [mv-rdvmedecins-pfmobile-app]: il progetto padre del progetto enterprise [mv-rdvmedecins-pfmobile-app-ear]. Quando si importa il progetto padre, il progetto figlio viene importato automaticamente,
  • in [2], eseguire il progetto aziendale [mv-rdvmedecins-pfmobile-app-ear],
  • in [3], selezionare il server Glassfish,
  • in [4], nella scheda [Servers], l'applicazione è stata distribuita. Non si avvia automaticamente. È necessario inserire il suo URL [http://localhost:8080/mv-rdvmedecins-pfmobile/] in un browser o in un simulatore mobile [5]: