6. Applicazione di esempio-03: rdvmedecins-pf-ejb
Esaminiamo la struttura dell'applicazione di esempio sviluppata per il server GlassFish:
![]() |
Non modificheremo nulla di questa architettura, ad eccezione del livello web, che verrà implementato qui utilizzando JSF e PrimeFaces.
![]() |
6.1. Il progetto NetBeans
Sopra, i livelli [business] e [DAO] sono quelli dell'Esempio 01 JSF / EJB / Glassfish. Li stiamo riutilizzando.
![]() |
- [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 [business] dell'Esempio 01,
- [mv-rdvmedecins-pf]: progetto del livello [web] / Primefaces – nuovo,
- [mv-rdvmedecins-app-ear]: progetto enterprise per distribuire l'applicazione sul server GlassFish – nuovo.
6.2. Il progetto enterprise
Il progetto enterprise viene utilizzato esclusivamente per distribuire i tre moduli [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-business], [mv-rdvmedecins-pf] 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>
<parent>
<artifactId>mv-rdvmedecins-app</artifactId>
<groupId>istia.st</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>istia.st</groupId>
<artifactId>mv-rdvmedecins-app-ear</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>ear</packaging>
<name>mv-rdvmedecins-app-ear</name>
...
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
<version>${project.version}</version>
<type>ejb</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-ejb-metier</artifactId>
<version>${project.version}</version>
<type>ejb</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-pf</artifactId>
<version>${project.version}</version>
<type>war</type>
</dependency>
</dependencies>
</project>
- righe 10–13: l'artefatto Maven per il progetto aziendale,
- righe 18–37: le tre dipendenze del progetto. Notare i loro tipi (righe 23, 29, 35).
Per eseguire l'applicazione web, è necessario eseguire questo progetto enterprise.
6.3. Il progetto web PrimeFaces
Il progetto web PrimeFaces è il seguente:
![]() |
- in [1], le pagine del progetto. La pagina [index.xhtml] è l'unica pagina del progetto. Contiene tre frammenti: [form1.xhtml], [form2.xhtml] e [error.xhtml]. Le altre pagine sono incluse esclusivamente a scopo di formattazione.
- in [2], i Java bean. Il bean [Application] ha ambito di applicazione, mentre il bean [Form] ha ambito di sessione. La classe [Error] incapsula un errore. La classe [MyDataModel] funge da modello per un tag <dataTable> di PrimeFaces,
- in [3], i file di 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 per il livello [web].
6.4. Configurazione del progetto
La configurazione del progetto è la stessa di quella dei progetti PrimeFaces o JSF che abbiamo studiato. Elenchiamo i file di configurazione senza spiegarli nuovamente.
![]() | ![]() |
[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.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Production</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
<error-page>
<error-code>500</error-code>
<location>/faces/exception.xhtml</location>
</error-page>
<error-page>
<exception-type>Exception</exception-type>
<location>/faces/exception.xhtml</location>
</error-page>
</web-app>
Si noti che alla riga 30, la pagina [index.xhtml] è la home page 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>
</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>
[styles.css]: il foglio di stile dell'applicazione
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
La libreria PrimeFaces include i propri fogli di stile. Il foglio di stile sopra riportato viene utilizzato solo per la pagina da visualizzare in caso di eccezione, ovvero una pagina non gestita dall'applicazione. Viene quindi visualizzata la pagina [exception.xhtml].
[messages_fr.properties]: il file dei messaggi in francese
# layout
layout.entete=Les M\u00e9decins Associ\u00e9s
layout.basdepage=ISTIA, universit\u00e9 d'Angers - application propuls\u00e9e par PrimeFaces et JQuery
# exception
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=Url demand\u00e9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
# formulaire 1
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour
form1.options=Options
form1.francais=Fran\u00e7ais
form1.anglais=Anglais
form1.rafraichir=Rafra\u00eechir
form1.precedent=Jour pr\u00e9c\u00e9dent
form1.suivant=Jour suivant
form1.agenda=Affiche l'agenda du m\u00e9decin choisi pour le jour choisi
form1.today=Aujourd'hui
# formulaire 2
form2.titre=Agenda de {0} {1} {2} le {3}
form2.titre_detail=Agenda de {0} {1} {2} le {3}
form2.creneauHoraire=Cr\u00e9neau horaire
form2.client=Client
form2.accueil=Accueil
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
form2.valider=Valider
form2.annuler=Annuler
form2.erreur=Erreur
form2.emtyMessage=Pas de cr\u00e9neaux entr\u00e9s dans la base
form2.suppression.confirmation=Etes-vous s\u00fbr(e) ?
form2.suppression.message=Suppression d'un rendez-vous
form2.supprimer.oui=Oui
form2.supprimer.non=Non
form2.erreurClient=Client [{0}] inconnu
form2.erreurClient_detail=Client {0} inconnu
form2.erreurAction=Action non autoris\u00e9e
form2.erreurAction_detail=Action non autoris\u00e9e
# erreur
erreur.titre=Une erreur s'est produite.
erreur.exceptions=Cha\u00eene des exceptions
erreur.type=Type de l'exception
erreur.message=Message associ\u00e9
erreur.accueil=Accueil
[messages_en.properties]: il file dei messaggi in inglese
# layout
layout.entete=Associated Doctors
layout.basdepage=ISTIA, Angers university - Application powered by PrimeFaces and JQuery
# exception
exception.header=The following exceptions occurred
exception.httpCode=Error HTTP code
exception.message=Exception message
exception.requestUri=Url targeted when error occurred
exception.servletName=Servlet targeted's name when error occurred
# formulaire 1
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=Date
form1.options=Options
form1.francais=French
form1.anglais=English
form1.rafraichir=Refresh
form1.precedent=Previous Day
form1.suivant=Next day
form1.agenda=Show the doctor's diary for the chosen doctor and the chosen day
form1.today=Today
# formulaire 2
form2.titre={0} {1} {2}'' diary on {3}
form2.titre_detail={0} {1} {2}'' diary on {3}
form2.creneauHoraire=Time Period
form2.client=Client
form2.accueil=Welcome Page
form2.supprimer=Delete
form2.reserver=Reserve
form2.valider=Submit
form2.annuler=Cancel
form2.erreur=Error
form2.emtyMessage=No Time periods in the database
form2.suppression.confirmation=Are-you sure ?
form2.suppression.message=Booking deletion
form2.supprimer.oui=Yes
form2.supprimer.non=No
form2.erreurClient=Unknown Client {0}
form2.erreurClient_detail=Unknown Client [{0}]
form2.erreurAction=Unauthorized action
form2.erreurAction_detail=Action non autoris\u00e9e
# erreur
erreur.titre=The following exceptions occurred
erreur.exceptions=Exceptions' chain
erreur.type=Exception type
erreur.message=Associated Message
erreur.accueil=Welcome
6.5. Il modello di pagina [layout.xhtml]
![]() |
Il modello [layout.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:ui="http://java.sun.com/jsf/facelets">
<f:view locale="#{form.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('#{request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 1200px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
<tr>
<td style="width: 10px;" bgcolor="#ffcccc">
<ui:include src="menu.xhtml"/>
</td>
<td>
<p:outputPanel id="contenu">
<ui:insert name="contenu">
<h2>Contenu</h2>
</ui:insert>
</p:outputPanel>
</td>
</tr>
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
</h:body>
</f:view>
</html>
L'unica parte variabile di questo modello è l'area alle righe 28–30. Quest'area si trova nell'ID :form:content (riga 27). Tenetelo a mente. Le chiamate AJAX che aggiornano quest'area avranno l'attributo update=":form:content". Inoltre, il modulo inizia alla riga 15. Pertanto, il frammento inserito alle righe 28–30 viene inserito in questo modulo.
Questo modello produce il seguente output:
![]() |
La parte dinamica della pagina verrà inserita nell'area incorniciata sopra.
6.6. La pagina [index.xhtml]
![]() |
Il progetto visualizza sempre la stessa pagina, ovvero la seguente pagina [index.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="layout.xhtml">
<ui:define name="contenu">
<ui:fragment rendered="#{form.form1Rendered}">
<ui:include src="form1.xhtml"/>
</ui:fragment>
<ui:fragment rendered="#{form.form2Rendered}">
<ui:include src="form2.xhtml"/>
</ui:fragment>
<ui:fragment rendered="#{form.erreurRendered}">
<ui:include src="erreur.xhtml"/>
</ui:fragment>
</ui:define>
</ui:composition>
</html>
- righe 8-9: questo frammento XHTML verrà inserito nell'area dinamica del modello [layout.xhtml],
- la pagina è composta da tre sotto-frammenti:
- [form1.xhtml], righe 10–12;
- [form2.xhtml], righe 13–15;
- [error.xhtml], righe 16–18.
La presenza di questi frammenti in [index.xhtml] è controllata da valori booleani nel modello [Form.java] associato alla pagina. Pertanto, modificando questi valori booleani, la pagina visualizzata cambia.
Il frammento [form1.xhtml] viene visualizzato come segue:
Il frammento [form2.xhtml] viene visualizzato come segue:
![]() |
Il frammento [erreur.xhtml] viene visualizzato come segue:
![]() |
6.7. I bean del progetto
![]() |
La classe nel pacchetto [utils] è già stata presentata: la classe [Messages] facilita l'internazionalizzazione dei messaggi di un'applicazione. Ne abbiamo parlato nella Sezione 2.8.5.7.
6.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 JSF e l'EJB [Business] verranno eseguiti nella stessa JVM (Java Virtual Machine). Pertanto, il livello [JSF] 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.
6.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.
6.7.3. Il bean [Form]
Il suo codice è il seguente:
package beans;
import java.io.IOException;
...
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// bean Application
@Inject
private Application application;
// 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>();
private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
// model
private Long idMedecin;
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
private String form2Titre;
private AgendaMedecinJour agendaMedecinJour;
private Long idCreneauChoisi;
private Medecin medecin;
private Long idClient;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
private Boolean erreur = false;
private String identiteClient;
private String action;
private String msgErreurClient;
private Boolean erreurClient;
private String msgErreurAction;
private Boolean erreurAction;
private String locale = "fr";
@PostConstruct
private void init() {
// caching doctors and customers
try {
medecins = application.getMetier().getAllMedecins();
clients = application.getMetier().getAllClients();
} catch (Throwable th) {
// we note the error
prepareVueErreur(th);
return;
}
// dictionaries
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
...
// view display
private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean erreurRendered) {
this.form1Rendered = form1Rendered;
this.form2Rendered = form2Rendered;
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(true, false, true);
}
// getters and setters
...
}
- righe 6-8: la classe [Form] è un bean denominato "form" con ambito di sessione. Si noti che la classe deve quindi essere serializzabile,
- righe 14–15: il bean form ha un riferimento al bean dell'applicazione. Questo riferimento verrà iniettato dal contenitore servlet in cui gira l'applicazione (presenza dell'annotazione @Inject).
- righe 17–44: i modelli di pagina [form1.xhtml, form2.xhtml, error.xhtml]. La visualizzazione di queste pagine è controllata dai valori booleani nelle righe 27–29. Si noti che per impostazione predefinita viene visualizzata la pagina [form1.xhtml] (riga 27),
- righe 46–47: il metodo `init` viene eseguito immediatamente dopo l'istanziazione della classe (grazie alla presenza dell'annotazione `@PostConstruct`),
- righe 50-51: il livello [business] viene interrogato per ottenere l'elenco dei medici e dei clienti,
- righe 59–65: se tutto è andato bene, vengono costruiti i dizionari dei medici e dei clienti. Sono indicizzati in base al loro numero. Successivamente, verrà visualizzata la pagina [form1.xhtml] (riga 27),
- riga 54: in caso di errore, viene costruito il modello di pagina [error.xhtml]. Questo modello è l'elenco degli errori della riga 36,
- righe 78–88: il metodo [prepareVueErreur] crea l'elenco degli errori da visualizzare. La pagina [index.xhtml] visualizza quindi i frammenti [form1.xhtml] e [erreur.xhtml] (riga 87).
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:ui="http://java.sun.com/jsf/facelets">
<body>
<p:panel header="#{msg['erreur.titre']}" closable="true" >
<hr/>
<p:dataTable value="#{form.erreurs}" var="erreur">
<f:facet name="header">
<h:outputText value="#{msg['erreur.exceptions']}"/>
</f:facet>
<p:column>
<f:facet name="header">
<h:outputText value="#{msg['erreur.type']}"/>
</f:facet>
<h:outputText value="#{erreur.classe}"/>
</p:column>
<p:column>
<f:facet name="header">
<h:outputText value="#{msg['erreur.message']}"/>
</f:facet>
<h:outputText value="#{erreur.message}"/>
</p:column>
</p:dataTable>
</p:panel>
</body>
</html>
Utilizza un tag <p:dataTable> (righe 12–28) per visualizzare l'elenco degli errori. Il risultato è una pagina di errore simile alla seguente:
![]() |
Definiremo ora le diverse fasi del ciclo di vita dell'applicazione. Per ogni azione dell'utente, esamineremo le viste e i gestori di eventi pertinenti.
6.8. Visualizzazione della pagina iniziale
Se tutto va bene, la prima pagina visualizzata è [form1.xhtml]. Ciò produce la seguente vista:
![]() |
La pagina [form1.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:ui="http://java.sun.com/jsf/facelets">
<p:toolbar>
<p:toolbarGroup align="left">
...
</p:toolbarGroup>
<p:toolbarGroup align="right">
...
</p:toolbarGroup>
</p:toolbar>
</html>
La barra degli strumenti evidenziata nella schermata è il componente PrimeFaces Toolbar. È definita nelle righe 8–14. Contiene due gruppi di componenti, ciascuno definito da un tag <toolbarGroup>, nelle righe 9–11 e 12–14. Uno dei gruppi è allineato a sinistra della barra degli strumenti (riga 9), l'altro a destra (riga 12).
Esaminiamo alcuni componenti del gruppo di sinistra:
<p:toolbar>
<p:toolbarGroup align="left">
<h:outputText value="#{msg['form1.medecin']}"/>
<p:selectOneMenu value="#{form.idMedecin}" effect="fade">
<f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>
<p:ajax update=":formulaire:contenu" listener="#{form.hideAgenda}" />
</p:selectOneMenu>
<p:separator/>
<h:outputText value="#{msg['form1.jour']}"/>
<p:calendar id="calendrier" value="#{form.jour}" readOnlyInputText="true">
<p:ajax event="dateSelect" listener="#{form.hideAgenda}" update=":formulaire:contenu"/>
</p:calendar>
<p:separator/>
<p:commandButton id="resa-agenda" icon="ui-icon-check" actionListener="#{form.getAgenda}" update=":formulaire:contenu"/>
<p:tooltip for="resa-agenda" value="#{msg['form1.agenda']}"/>
...
</p:toolbarGroup>
...
- righe 4-7: il menu a tendina "doctors" a cui è stato aggiunto un effetto (effect="fade"),
- riga 6: un comportamento AJAX. Quando si verifica una modifica nel menu a tendina, verrà eseguito il metodo [Form].hideAgenda (listener="#{form.hideAgenda}") e l'area dinamica :form:content (update=":form:content") verrà aggiornata,
- riga 8: include un separatore nella barra degli strumenti,
- righe 10–12: il campo di immissione della data. Qui viene utilizzato il calendario PrimeFaces. Il campo di immissione è di sola lettura (readOnlyInputText="true"),
- riga 11: un comportamento AJAX. Quando la data cambia, verrà eseguito il metodo [Form].hideAgenda e il campo dinamico :form:content verrà aggiornato,
- riga 14: un pulsante. Facendovi clic si attiva una chiamata AJAX al metodo [Form].getAgenda(); il modello verrà quindi modificato e la risposta del server verrà utilizzata per aggiornare il campo dinamico :form:content,
- riga 15: il tag <tooltip> consente di associare un tooltip a un componente. L'ID del componente è specificato dall'attributo for del tooltip. Qui (for="resa-agenda") si riferisce al pulsante della riga 14:
![]() | ![]() |
Questa pagina è basata sul seguente template:
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// session cache
private List<Medecin> medecins;
private List<Client> clients;
// model
private Long idMedecin;
private Date jour = new Date();
// list of doctors
public List<Medecin> getMedecins() {
return medecins;
}
// customer list
public List<Client> getClients() {
return clients;
}
// agenda
public void getAgenda() {
...
}
- Il campo alla riga 12 legge e scrive il valore dell'elenco alla riga 4 della pagina. Al primo caricamento della pagina, imposta il valore selezionato nella casella combinata. Al caricamento iniziale, idMedecin è uguale a null, quindi verrà selezionato il primo medico.
- il metodo alle righe 16–18 genera le voci per il menu a tendina dei medici (riga 5 della pagina). 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 13 fornisce l'accesso in lettura/scrittura al campo di immissione alla riga 10 della pagina. Alla visualizzazione iniziale, viene mostrata la data corrente,
- Righe 26–28: Il metodo getAgenda gestisce il clic sul pulsante [Agenda] alla riga 14 della pagina. È quasi identico a com'era nella versione JSF:
// bean Application
@Inject
private Application application;
// session cache
private List<Medecin> medecins;
private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
// model
private Long idMedecin;
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
private AgendaMedecinJour agendaMedecinJour;
private Long idCreneauChoisi;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
private Boolean erreur = false;
public void getAgenda() {
try {
// we get the doctor back
medecin = hMedecins.get(idMedecin);
// the doctor's diary for a given day
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// form 2 is displayed
setForms(true, true, false);
} catch (Throwable th) {
// error view
prepareVueErreur(th);
}
// no slots selected yet
creneauChoisi = null;
}
Non commenteremo questo codice. È già stato fatto.
6.9. Visualizza l'orario di un medico
6.9.1. Panoramica dell'orario
Ecco il seguente caso d'uso:
![]() |
- In [1], si seleziona un medico [1] e un giorno [2], quindi si richiede [3] l'orario del medico per il giorno selezionato;
- in [4], questo appare sotto la barra degli strumenti.
Il codice della pagina [form2.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:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<body>
<!-- context menu -->
<p:contextMenu for="agenda">
...
</p:contextMenu>
<!-- agenda -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- schedule column -->
<p:column style="width: 100px">
...
</p:column>
<!-- customer column -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
<!-- confirmation deletion RV -->
<p:confirmDialog id="confirmDialog" message="#{msg['form2.suppression.confirmation']}"
header="#{msg['form2.suppression.message']}" severity="alert" widgetVar="confirmation">
...
</p:confirmDialog>
<!-- error message -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
...
</p:dialog>
<!-- server return management -->
<script type="text/javascript">
...
}
</script>
</body>
</html>
- righe 16-26: l'elemento principale della pagina è il <dataTable> che mostra l'orario del medico,
![]() |
- righe 12-14: useremo un menu contestuale per aggiungere/eliminare un appuntamento:
![]() |
- righe 29-32: verrà visualizzata una finestra di dialogo di conferma quando l'utente desidera eliminare un appuntamento:
![]() |
- righe 35-37: verrà utilizzata una finestra di dialogo per segnalare un errore:
![]() |
- righe 40–43: dovremo aggiungere del codice JavaScript.
6.9.2. La tabella degli appuntamenti
Qui tratteremo il modello della tabella dati come discusso nella Sezione 5.15, pagina 327.
Esaminiamo l'elemento principale della pagina, la tabella che visualizza il calendario:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- schedule column -->
<p:column style="width: 100px">
...
</p:column>
<!-- customer column -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
Il risultato è il seguente:
![]() |
Si tratta di una tabella a due colonne (righe 4–6 e 8–10) popolata dalla sorgente [Form].getMyDataModel() (value="#{form.myDataModel}"). È possibile selezionare una sola riga alla volta (selectionMode="single"). Ad ogni POST, un riferimento all'elemento selezionato viene assegnato a [Form].creneauChoisi (selection="#{form.creneauChoisi}").
Si ricordi che il metodo getAgenda ha inizializzato il seguente campo nel modello:
// modèle
private AgendaMedecinJour agendaMedecinJour;
Il modello della tabella si ottiene richiamando il seguente metodo [Form].getMyDataModel (attributo "value" del tag <dataTable>):
// the dataTable model
public MyDataModel getMyDataModel() {
return new MyDataModel(agendaMedecinJour.getCreneauxMedecinJour());
}
Esaminiamo la classe [MyDataModel], che funge da modello per il tag <p:dataTable>:
package beans;
import javax.faces.model.ArrayDataModel;
import org.primefaces.model.SelectableDataModel;
import rdvmedecins.metier.entites.CreneauMedecinJour;
public class MyDataModel extends ArrayDataModel<CreneauMedecinJour> implements SelectableDataModel<CreneauMedecinJour> {
// manufacturers
public MyDataModel() {
}
public MyDataModel(CreneauMedecinJour[] creneauxMedecinJour) {
super(creneauxMedecinJour);
}
@Override
public Object getRowKey(CreneauMedecinJour creneauMedecinJour) {
return creneauMedecinJour.getCreneau().getId();
}
@Override
public CreneauMedecinJour getRowData(String rowKey) {
// list of slots
CreneauMedecinJour[] creneauxMedecinJour = (CreneauMedecinJour[]) getWrappedData();
// the key is a long integer
long key = Long.parseLong(rowKey);
// search for the selected slot
for (CreneauMedecinJour creneauMedecinJour : creneauxMedecinJour) {
if (creneauMedecinJour.getCreneau().getId().longValue() == key) {
return creneauMedecinJour;
}
}
// nothing
return null;
}
}
- riga 7: la classe [MyDataModel] è il modello per il tag <p:dataTable>. Lo scopo di questa classe è collegare l'elemento rowkey inviato con l'elemento associato a quella riga,
- Riga 7: La classe implementa l'interfaccia [SelectableDataModel] tramite la classe [ArrayDataModel]. Ciò significa che il parametro del costruttore è un array. Questo array popola il tag <dataTable>. Qui, ogni riga dell'array sarà associata a un elemento di tipo [CreneauMedecinJour],
- Righe 13–15: Il costruttore passa il suo parametro alla classe padre,
- righe 18–20: Ogni riga dell'array corrisponde a una fascia oraria e sarà identificata dall'ID della fascia oraria (riga 19). È questo ID che verrà inviato al server,
- riga 23: il codice che verrà eseguito sul lato server quando viene inviato l'ID di una fascia oraria. Lo scopo di questo metodo è restituire il riferimento all'oggetto [CreneauMedecinJour] associato a tale ID. Tale riferimento verrà assegnato al valore dell'attributo "selection" del tag <dataTable>:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
Il campo [Form].creneauChoisi conterrà quindi il riferimento all'oggetto [CreneauMedecinJour] che si desidera aggiungere o eliminare.
6.9.3. La colonna della fascia oraria
![]() |
La colonna "Intervallo di tempo" viene generata utilizzando il seguente codice:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- schedule column -->
<p:column style="width: 100px">
<f:facet name="header">
<h:outputText value="#{msg['form2.creneauHoraire']}"/>
</f:facet>
<div align="center">
<h:outputFormat value="{0,number,#00}:{1,number,#00} - {2,number,#00}:{3,number,#00}">
<f:param value="#{creneauMedecinJour.creneau.hdebut}" />
<f:param value="#{creneauMedecinJour.creneau.mdebut}" />
<f:param value="#{creneauMedecinJour.creneau.hfin}" />
<f:param value="#{creneauMedecinJour.creneau.mfin}" />
</h:outputFormat>
</div>
</p:column>
<!-- customer column -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
- righe 5-7: l'intestazione della colonna,
- righe 8–15: l'elemento della colonna corrente. Notare la riga 9, dove il tag <h:outputFormat> viene utilizzato per formattare gli elementi da visualizzare. Il parametro value specifica la stringa da visualizzare. La notazione {i,type,format} si riferisce al parametro numero i, al tipo di quel parametro e al suo formato. Qui ci sono 4 parametri numerati da 0 a 3; il loro tipo è numerico e verranno visualizzati con due cifre,
- Righe 10–13: i quattro parametri previsti dal tag <h:outputFormat>.
6.9.4. La colonna del cliente
![]() |
La colonna "cliente" viene generata utilizzando il seguente codice:
<!-- agenda -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- schedule column -->
...
<!-- customer column -->
<p:column style="width: 300px">
<f:facet name="header">
<h:outputText value="#{msg['form2.client']}"/>
</f:facet>
<ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
<h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}" />
</ui:fragment>
<ui:fragment rendered="#{creneauMedecinJour.rv==null and form.creneauChoisi!=null and form.creneauChoisi.creneau.id==creneauMedecinJour.creneau.id}">
...
</ui:fragment>
</p:column>
</p:dataTable>
- righe 8–10: l'intestazione della colonna,
- righe 11–13: l'elemento corrente quando c'è un appuntamento per la fascia oraria. In questo caso, visualizziamo il titolo, il nome e il cognome del cliente per il quale è stato fissato questo appuntamento,
- righe 14–16: un altro frammento su cui torneremo più avanti.
6.10. Eliminazione di un appuntamento
L'eliminazione di un appuntamento prevede la seguente sequenza:
![]() |
![]() |
La vista oggetto della presente azione è la seguente:
<!-- contextual menu -->
<p:contextMenu for="agenda">
...
<p:menuitem value="#{msg['form2.supprimer']}" onclick="confirmation.show()"/>
</p:contextMenu>
<!-- agenda -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
...
</p:dataTable>
<!-- confirm deletion RV -->
<p:confirmDialog id="confirmDialog" message="#{msg['form2.suppression.confirmation']}"
header="#{msg['form2.suppression.message']}" severity="alert" widgetVar="confirmation">
<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
oncomplete="handleRequest(xhr, status, args); confirmation.hide()">
<f:setPropertyActionListener value="supprimer" target="#{form.action}"/>
</p:commandButton>
<p:commandButton value="#{msg['form2.supprimer.non']}" onclick="confirmation.hide()" type="button" />
</p:confirmDialog>
- Righe 2-5: Un menu contestuale collegato all'array di dati (per l'attributo). Presenta due opzioni [1]:
![]() |
- riga 4: l'opzione [Elimina] fa apparire la finestra di dialogo [2] delle righe 13-20,
- riga 15: cliccando su [Sì] si avvia l'esecuzione di [Form.action], che eliminerà l'appuntamento. Normalmente, il menu contestuale non dovrebbe offrire l'opzione [Elimina] se l'elemento selezionato non ha un appuntamento, e non l'opzione [Prenota] se l'elemento selezionato ha un appuntamento. Non siamo riusciti a rendere il menu contestuale così sottile. Funziona per il primo elemento selezionato, ma poi abbiamo notato che il menu contestuale mantiene la configurazione impostata per quella prima selezione. Diventa quindi errato. Abbiamo quindi mantenuto entrambe le opzioni e abbiamo deciso di fornire un feedback all'utente se elimina un elemento senza un appuntamento,
- Riga 16: L'attributo `oncomplete`, che consente di definire il codice JavaScript da eseguire al completamento della richiesta AJAX. In questo caso, il codice sarà il seguente:
<!-- message d'erreur -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
<h:outputText value="#{form.msgErreur}" />
</p:dialog>
<!-- gestion du retour serveur -->
<script type="text/javascript">
function handleRequest(xhr, status, args) {
// erreur ?
if(args.erreur) {
dlgErreur.show();
}
}
</script>
- Riga 10: Il codice JavaScript verifica se il dizionario args contiene un attributo error. In tal caso, visualizza la finestra di dialogo della riga 2 (attributo widgetVar). Questa finestra di dialogo visualizza il modello [Form].msgError.
Diamo un'occhiata al codice eseguito per gestire l'eliminazione di un appuntamento:
<p:confirmDialog ...>
<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
...>
<f:setPropertyActionListener value="supprimer" target="#{form.action}"/>
</p:commandButton>
...
</p:confirmDialog>
- Riga 2: Verrà eseguito il metodo [Form].action,
- riga 4: prima che venga eseguito, il campo action sarà stato impostato su 'delete'.
Il metodo [action] è il seguente:
// action on RV
public void action() {
// according to desired action
if (action.equals("supprimer")) {
supprimer();
}
...
}
public void supprimer() {
// is there anything we can do?
Rv rv = creneauChoisi.getRv();
if (rv == null) {
signalerActionIncorrecte();
return;
}
try {
// deleting an appointment
application.getMetier().supprimerRv(rv);
// updating the agenda
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// form2 is displayed
setForms(true, true, false);
} catch (Throwable th) {
// error view
prepareVueErreur(th);
}
// raz of the chosen slot
creneauChoisi = null;
}
- riga 4: se l'azione è 'delete', esegui il metodo [delete],
- riga 12: recupera l'appuntamento per lo slot selezionato. Ricordiamo che [selectedSlot] è stato inizializzato con il riferimento all'elemento [DoctorSlot] selezionato;
- se questo appuntamento esiste, viene eliminato (riga 19), il calendario viene aggiornato (riga 21) e poi visualizzato nuovamente (riga 23),
- se l'eliminazione non è andata a buon fine, viene visualizzata la pagina di errore (riga 26),
- se l'elemento selezionato non ha alcun appuntamento (riga 13), allora ci troviamo in una situazione in cui l'utente ha cliccato su [Elimina] su uno slot che non ha alcun appuntamento. Segnaliamo questo errore:
![]() |
Il metodo [reportIncorrectAction] è il seguente:
// report an incorrect action
private void signalerActionIncorrecte() {
// raz selected slot
creneauChoisi = null;
// error
msgErreur = Messages.getMessage(null, "form2.erreurAction", null).getSummary();
RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
}
- riga 4: rimuovi la selezione,
- riga 6: generare un messaggio di errore internazionalizzato,
- riga 7: aggiungere l'attributo ('error', true) al dizionario args della chiamata AJAX.
Torniamo al codice XHTML del pulsante [Sì]:
<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
oncomplete="handleRequest(xhr, status, args); confirmation.hide()">
- Riga 2: Dopo l'esecuzione del metodo [Form].action, viene eseguito il metodo JavaScript handleRequest:
<!-- message d'erreur -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
<h:outputText value="#{form.msgErreur}" />
</p:dialog>
<!-- gestion du retour serveur -->
<script type="text/javascript">
function handleRequest(xhr, status, args) {
// erreur ?
if(args.erreur) {
dlgErreur.show();
}
}
</script>
- Riga 10: Verifichiamo se il dizionario args contiene un attributo denominato 'error'. In tal caso, viene visualizzata la finestra di dialogo della riga 2.
- Riga 3: visualizza il messaggio di errore generato dal modello.
6.11. Fissare un appuntamento
La fissazione di un appuntamento corrisponde alla seguente sequenza:
![]() |
La visione alla base di questa azione è la seguente:
<!-- context menu -->
<p:contextMenu for="agenda">
<p:menuitem value="#{msg['form2.reserver']}" update=":formulaire:contenu" action="#{form.action}" oncomplete="handleRequest(xhr, status, args)">
<f:setPropertyActionListener value="reserver" target="#{form.action}"/>
</p:menuitem>
...
</p:contextMenu>
<!-- agenda -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- schedule column -->
<p:column style="width: 100px">
...
</p:column>
<!-- customer column -->
<p:column style="width: 300px">
<f:facet name="header">
<h:outputText value="#{msg['form2.client']}"/>
</f:facet>
...
<ui:fragment rendered="#{creneauMedecinJour.rv==null and form.creneauChoisi!=null and form.creneauChoisi.creneau.id==creneauMedecinJour.creneau.id}">
<p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>
<p:spacer width="50px"/>
<p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
<f:setPropertyActionListener value="valider" target="#{form.action}"/>
</p:commandLink>
<p:spacer width="50px"/>
<p:commandLink action="#{form.action()}" value="#{msg['form2.annuler']}" update=":formulaire:contenu">
<f:setPropertyActionListener value="annuler" target="#{form.action}"/>
</p:commandLink>
</ui:fragment>
</p:column>
</p:dataTable>
...
- righe 21–31: visualizza quanto segue:
![]() |
- riga 21: viene visualizzata se non ci sono appuntamenti, è stata effettuata una selezione e l'ID della fascia oraria selezionata corrisponde a quella della riga corrente della tabella. Se questa condizione non è soddisfatta, il frammento viene visualizzato per tutte le fasce orarie,
- riga 22: il campo di immissione sarà un campo di immissione assistito. Si presume qui che possano esserci molti clienti,
- Righe 24–26: il link [Invia],
- righe 28–30: il link [Annulla].
Il campo di completamento automatico viene generato dal seguente codice:
<p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>
Il metodo [Form].completeClients ha il compito di fornire suggerimenti all'utente in base ai caratteri digitati nel campo di immissione:
![]() |
I suggerimenti sono nel formato [Cognome, nome, titolo]. Il codice per il metodo [Form].completeClients è il seguente:
// the autocomplete text method
public List<String> completeClients(String query) {
List<String> identites = new ArrayList<String>();
// we look for customers who match
for (Client c : clients) {
String identite = identite(c);
if (identite.toLowerCase().startsWith(query.toLowerCase())) {
identites.add(identite);
}
}
return identites;
}
private String identite(Client c) {
return c.getNom() + " " + c.getPrenom() + " " + c.getTitre();
}
- Riga 2: query è la stringa inserita dall'utente,
- riga 3: l'elenco dei suggerimenti. Inizialmente è un elenco vuoto,
- righe 5–10: costruiamo i nomi completi dei clienti [Cognome, Nome, Titolo]. Se un nome completo inizia con query (riga 7), viene aggiunto all'elenco dei suggerimenti (riga 8).
6.12. Conferma di un appuntamento
La conferma di un appuntamento segue questa sequenza:
![]() |
Il codice per il link [Conferma] è il seguente:
<p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
<f:setPropertyActionListener value="valider" target="#{form.action}"/>
</p:commandLink>
Quindi il metodo [Form].action() gestirà questo evento. Nel frattempo, il modello [Form].action avrà ricevuto la stringa 'submit'. Il codice è il seguente:
// bean Application
@Inject
private Application application;
// session cache
...
private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
// model
private Date jour = new Date();
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
private AgendaMedecinJour agendaMedecinJour;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
private Boolean erreur = false;
private String identiteClient;
private String action;
private String msgErreur;
@PostConstruct
private void init() {
...
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
// action on RV
public void action() {
// according to desired action
...
if (action.equals("valider")) {
validerResa();
}
}
// rv validation
public void validerResa() {
// reservation validation
try {
// does the customer exist?
Boolean erreur = !hIdentitesClients.containsKey(identiteClient);
if (erreur) {
msgErreur = Messages.getMessage(null, "form2.erreurClient", new Object[]{identiteClient}).getSummary();
RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
return;
}
// we add the Rv
application.getMetier().ajouterRv(jour, creneauChoisi.getCreneau(), hIdentitesClients.get(identiteClient));
// updating the agenda
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// form2 is displayed
setForms(true, true, false);
} catch (Throwable th) {
// error view
prepareVueErreur(th);
}
// raz of the chosen slot
creneauChoisi = null;
// raz client
identiteClient = null;
}
- righe 33-35: a causa del valore del campo action, verrà eseguito il metodo [validateReservation],
- Riga 43: Verifichiamo innanzitutto che il cliente esista. Questo perché, nel campo di immissione assistita, l'utente potrebbe aver inserito i dati manualmente senza utilizzare i suggerimenti forniti. L'immissione assistita è associata al modello [Form].identiteClient. Verifichiamo quindi se questa identità esiste nel dizionario identitesClients creato al momento dell'istanziazione del modello (riga 20). Questo dizionario associa un'identità cliente di tipo [Cognome, Nome, Titolo] al cliente stesso (riga 25),
- riga 44: se il cliente non esiste, viene restituito un errore al browser,
- riga 45: un messaggio di errore internazionalizzato,
- riga 46: aggiungiamo l'attributo ('error', true) al dizionario args della chiamata AJAX. La chiamata AJAX è stata definita come segue:
<p:commandLink action="#{form.action()}" value="#{msg['form2.valider']}" update=":formulaire:contenu" oncomplete="handleRequest(xhr, status, args)">
<f:setPropertyActionListener value="valider" target="#{form.action}"/>
</p:commandLink>
Nella riga 3 sopra, vediamo che il link [Validate] ha un attributo oncomplete. È questo attributo che visualizzerà il messaggio di errore utilizzando una tecnica che abbiamo già incontrato.
- Riga 50: Chiediamo al livello [business] di aggiungere un appuntamento per il giorno selezionato (day), la fascia oraria selezionata (creneauChoisi.getCreneau()) e il cliente selezionato (hIdentitesClients.get(identiteClient)),
- riga 52: chiediamo al livello [business] di aggiornare il calendario del medico. Vedremo l'appuntamento aggiunto insieme a eventuali modifiche apportate da altri utenti dell'applicazione,
- riga 54: il calendario [form2.xhtml] viene visualizzato nuovamente,
- riga 57: se si verifica un errore, viene visualizzata la pagina di errore.
6.13. Annullamento di un appuntamento
Ciò corrisponde alla seguente sequenza:
![]() |
Il pulsante [Annulla] nella pagina [form2.xhtml] è il seguente:
<p:commandLink action="#{form.action()}" value="#{msg['form2.annuler']}" update=":formulaire:contenu">
<f:setPropertyActionListener value="annuler" target="#{form.action}"/>
</p:commandLink>
Viene quindi chiamato il metodo [Form].action:
// action sur RV
public void action() {
// selon l'action désirée
...
if (action.equals("annuler")) {
annulerRv();
}
}
// annulation prise de Rdv
public void annulerRv() {
// on affiche form2
setForms(true, true, false);
// raz du créneau choisi
creneauChoisi = null;
// raz client
identiteClient = null;
}
6.14. Navigazione nel calendario
La barra degli strumenti consente di navigare nel calendario:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Sebbene non sia visibile nelle schermate sopra riportate, il calendario viene aggiornato con gli appuntamenti relativi al giorno appena selezionato.
I tag relativi ai tre pulsanti in questione sono i seguenti nel file [form1.xhtml]:
<p:toolbar>
<p:toolbarGroup align="left">
...
<h:outputText value="#{msg['form1.jour']}"/>
<p:calendar id="calendrier" value="#{form.jour}" readOnlyInputText="true">
<p:ajax event="dateSelect" listener="#{form.hideAgenda}" update=":formulaire:contenu"/>
</p:calendar>
<p:separator/>
<p:commandButton id="resa-agenda" icon="ui-icon-check" actionListener="#{form.getAgenda}" update=":formulaire:contenu"/>
<p:tooltip for="resa-agenda" value="#{msg['form1.agenda']}"/>
<p:commandButton id="resa-precedent" icon="ui-icon-seek-prev" actionListener="#{form.getPreviousAgenda}" update=":formulaire:contenu"/>
<p:tooltip for="resa-precedent" value="#{msg['form1.precedent']}"/>
<p:commandButton id="resa-suivant" icon="ui-icon-seek-next" actionListener="#{form.getNextAgenda}" update=":formulaire:contenu"/>
<p:tooltip for="resa-suivant" value="#{msg['form1.suivant']}"/>
<p:commandButton id="resa-today" icon="ui-icon-home" actionListener="#{form.today}" update=":formulaire:contenu"/>
<p:tooltip for="resa-today" value="#{msg['form1.today']}"/>
</p:toolbarGroup>
<p:toolbarGroup align="right">
...
</p:toolbarGroup>
</p:toolbar>
I metodi [Form].getPreviousAgenda, [Form].getNextAgenda e [Form].today sono i seguenti:
private Date jour = new Date();
public void getPreviousAgenda() {
// on passe au jour précédent
Calendar cal = Calendar.getInstance();
cal.setTime(jour);
cal.add(Calendar.DAY_OF_YEAR, -1);
jour = cal.getTime();
// agenda
if (form2Rendered) {
getAgenda();
}
}
public void getNextAgenda() {
// on passe au jour suivant
Calendar cal = Calendar.getInstance();
cal.setTime(jour);
cal.add(Calendar.DAY_OF_YEAR, 1);
jour = cal.getTime();
// agenda
if (form2Rendered) {
getAgenda();
}
}
// agenda aujourd'hui
public void today() {
jour = new Date();
// agenda
if (form2Rendered) {
getAgenda();
}
}
- riga 1: il giorno in cui viene visualizzato il calendario,
- riga 5: utilizziamo un calendario,
- riga 6: che viene inizializzato al giorno corrente del calendario,
- riga 7: sottraiamo un giorno dal calendario,
- riga 8: e lo reimpostiamo alla data di visualizzazione del calendario,
- riga 11: ridisegniamo il calendario se è attualmente visualizzato. Questo perché l'utente può utilizzare la barra degli strumenti anche se il calendario non è visualizzato.
Gli altri metodi sono simili.
6.15. Modifica della lingua di visualizzazione
Il cambio di lingua è gestito dal pulsante del menu sulla barra degli strumenti:
![]() |
![]() |
I tag del pulsante del menu sono i seguenti:
<p:toolbar>
<p:toolbarGroup align="left">
...
</p:toolbarGroup>
<p:toolbarGroup align="right">
<p:menuButton value="#{msg['form1.options']}">
<p:menuitem id="menuitem-francais" value="#{msg['form1.francais']}" actionListener="#{form.setFrenchLocale}" update=":formulaire"/>
<p:menuitem id="menuitem-anglais" value="#{msg['form1.anglais']}" actionListener="#{form.setEnglishLocale}" update=":formulaire"/>
<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>
</p:menuButton>
</p:toolbarGroup>
</p:toolbar>
I metodi eseguiti nel modello sono i seguenti:
private String locale = "fr";
public void setFrenchLocale() {
locale = "fr";
// reload page
redirect();
}
public void setEnglishLocale() {
locale = "en";
// reload page
redirect();
}
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);
}
}
I metodi alle righe 3 e 9 si limitano a inizializzare il campo local della riga 1 e quindi reindirizzano il browser del client alla stessa pagina. Un reindirizzamento è una risposta in cui il server indica al browser di caricare un'altra pagina. Il browser esegue quindi una richiesta GET a questa nuova pagina.
- Riga 17: [ExternalContext] è una classe JSF che fornisce l'accesso al servlet attualmente in esecuzione,
- riga 19: eseguiamo il reindirizzamento. Il parametro del metodo redirect è l'URL della pagina a cui il browser del client deve essere reindirizzato. Qui vogliamo reindirizzare a [/mv-rdvmedecins-pf], che è il nome della nostra applicazione:
![]() |
Il metodo [getRequestContextPath] fornisce questo nome. Verrà quindi caricata la pagina iniziale [index.xhtml] della nostra applicazione. Questa pagina è associata al modello [Form] a ambito di sessione. Questo modello gestisce tre valori booleani che controllano l'aspetto della pagina [index.xhtml]:
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
Poiché il modello è a livello di sessione, questi tre valori booleani hanno mantenuto i propri valori. La pagina [index.xhtml] apparirà quindi come prima del reindirizzamento. Questa pagina è formattata utilizzando il seguente modello facelet [layout.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:view locale="#{form.locale}">
....
</f:view>
</html>
Il tag alla riga 9 imposta la lingua di visualizzazione della pagina utilizzando il suo attributo locale. La pagina passerà quindi al francese o all'inglese a seconda dei casi. Ora, perché un reindirizzamento? Torniamo ai tag relativi alle opzioni di cambio lingua:
<p:toolbar>
<p:toolbarGroup align="left">
...
</p:toolbarGroup>
<p:toolbarGroup align="right">
<p:menuButton value="#{msg['form1.options']}">
<p:menuitem id="menuitem-francais" value="#{msg['form1.francais']}" actionListener="#{form.setFrenchLocale}" update=":formulaire"/>
<p:menuitem id="menuitem-anglais" value="#{msg['form1.anglais']}" actionListener="#{form.setEnglishLocale}" update=":formulaire"/>
<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>
</p:menuButton>
</p:toolbarGroup>
</p:toolbar>
Inizialmente erano stati scritti per aggiornare l'area dell'ID del modulo tramite una chiamata AJAX (l'attributo update nelle righe 7 e 8). Tuttavia, durante i test, il cambio di lingua non funzionava sempre. Da qui il reindirizzamento per risolvere il problema. Avremmo anche potuto impostare l'attributo ajax='false' sui tag per innescare un ricaricamento della pagina. Ciò avrebbe evitato il reindirizzamento.
6.16. Aggiornamento degli elenchi
Ciò corrisponde alla seguente azione:
![]() |
Il tag associato all'opzione [Aggiorna] è il seguente:
<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>
Il metodo [Form].refresh è il seguente:
public void refresh() {
// on rafraîchit les listes
init();
}
Il metodo init è il metodo eseguito immediatamente dopo la creazione del bean [Form]. Il suo scopo è quello di memorizzare nella cache i dati provenienti dal database nel modello:
// bean Application
@Inject
private Application application;
// 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>();
private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
...
@PostConstruct
private void init() {
// caching doctors and customers
try {
medecins = application.getMetier().getAllMedecins();
clients = application.getMetier().getAllClients();
} catch (Throwable th) {
...
}
...
// dictionaries
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
Il metodo init costruisce gli elenchi e i dizionari nelle righe 5–9. Lo svantaggio di questa tecnica è che questi elementi non riflettono più le modifiche apportate al database (aggiunta di un cliente, di un medico, ecc.). Il metodo refresh forza la ricostruzione di questi elenchi e dizionari. Pertanto, lo useremo ogni volta che viene apportata una modifica al database, come l'aggiunta di un nuovo cliente.
6.17. Conclusione
Rivediamo l'architettura dell'applicazione che abbiamo appena realizzato:
![]() |
Ci siamo basati in gran parte sulla versione JSF2 predefinita:
- i livelli [business], [DAO] e [JPA] sono stati mantenuti,
- i bean [Application] e [Form] nel livello web sono stati mantenuti ma sono state aggiunte nuove funzionalità a causa del miglioramento dell'interfaccia utente,
- l'interfaccia utente è stata modificata in modo significativo. In particolare, ora è più ricca di funzionalità e più intuitiva.
Il passaggio da JSF a PrimeFaces per la realizzazione dell’interfaccia web richiede una certa esperienza, poiché all’inizio si è un po’ sopraffatti dall’elevato numero di componenti disponibili e alla fine non si è del tutto sicuri di quali utilizzare. È quindi necessario concentrarsi sull’usabilità desiderata per l’interfaccia.
6.18. Test su Eclipse
Come abbiamo fatto per le versioni precedenti dell'applicazione di esempio, mostreremo come testare questa versione 03 utilizzando Eclipse. Innanzitutto, importiamo i progetti Maven per l'Esempio 03 [1] in Eclipse:
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: i livelli [DAO] e [JPA],
- [mv-rdvmedecins-ejb-metier]: il livello [business],
- [mv-rdvmedecins-pf]: il livello [web] implementato con JSF e PrimeFaces,
- [mv-rdvmedecins-app]: il progetto padre del progetto aziendale [mv-rdvmedecins-app-ear]. Quando si importa il progetto padre, il progetto figlio viene importato automaticamente,
- in [2], eseguire il progetto aziendale [mv-rdvmedecins-app-ear],
![]() |
- in [3], selezionare il server Glassfish,
- in [4], nella scheda [Servers], l'applicazione è stata distribuita. Non si avvia automaticamente. È necessario richiederne l'URL [http://localhost:8080/mv-rdvmedecins-pf/] in un browser [5]:
![]() |
















































