6. Voorbeeldtoepassing-03: rdvmedecins-pf-ejb
Laten we nog eens de structuur bekijken van de voorbeeldtoepassing die voor de Glassfish-server is ontwikkeld:
![]() |
We brengen geen wijzigingen aan in deze architectuur, behalve de weblaag, die hier wordt gerealiseerd met behulp van JSF en Primefaces.
![]() |
6.1. Het NetBeans-project
Hierboven zijn de lagen [métier] en [DAO] afkomstig uit voorbeeld 01 JSF / EJB / Glassfish. We hergebruiken deze.
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: project EJB van de lagen [DAO] en [JPA] uit voorbeeld 01,
- [mv-rdvmedecins-ejb-metier]: project EJB van de laag [métier] uit voorbeeld 01,
- [mv-rdvmedecins-pf]: project van de laag [web] / Primefaces – nieuw,
- [mv-rdvmedecins-app-ear]: bedrijfsproject om de applicatie op de Glassfish-server te implementeren – nieuw.
6.2. Het bedrijfsproject
Het bedrijfsproject dient uitsluitend voor de implementatie van de drie modules [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-metier] en [mv-rdvmedecins-pf] op de Glassfish-server. Het NetBeans-project is als volgt:
![]() |
Het project bestaat uitsluitend voor deze drie afhankelijkheden [1], die in het bestand [pom.xml] als volgt zijn gedefinieerd:
<?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>
- regels 10-13: het Maven-artefact van het bedrijfsproject,
- regels 18-37: de drie afhankelijkheden van het project. Let goed op het type hiervan (regels 23, 29, 35).
Om de webapplicatie uit te voeren, moet dit bedrijfsproject worden uitgevoerd.
6.3. Het Primefaces-webproject
Het Primefaces-webproject is als volgt:
![]() |
- in [1], de pagina's van het project. De pagina [index.xhtml] is de enige pagina van het project. Deze bevat drie fragmenten: [form1.xhtml], [form2.xhtml] en [erreur.xhtml]. De overige pagina's dienen uitsluitend voor de opmaak.
- In [2]: de Java-beans. De bean [Application] met bereik application, de bean [Form] met bereik session. De klasse [Erreur] kapselt een fout in. De klasse [MyDataModel] dient als sjabloon voor een <dataTable>-tag van PrimeFaces,
- in [3], de berichtbestanden voor internationalisering,
- in [4] de afhankelijkheden. Het webproject is afhankelijk van het project EJB van de laag [DAO], het project EJB van de laag [métier] en Primefaces voor de laag [web].
6.4. De projectconfiguratie
De projectconfiguratie is dezelfde als die van de Primefaces- of JSF-projecten die we eerder hebben besproken. We sommen de configuratiebestanden op zonder ze opnieuw uit te leggen.
![]() | ![]() |
[web.xml]: configureert de webapplicatie.
<?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>
Let op: op regel 30 staat dat de pagina [index.xhtml] de startpagina van de applicatie is.
[faces-config.xml]: configureert de applicatie 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]: leeg, maar nodig voor de annotatie @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]: het stylesheet van de applicatie
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
De Primefaces-bibliotheek wordt geleverd met eigen stylesheets. Het bovenstaande stylesheet wordt alleen gebruikt voor de pagina die bij een uitzondering moet worden weergegeven, een pagina die niet door de applicatie wordt beheerd. In dat geval wordt de pagina [exception.xhtml] weergegeven.
[messages_fr.properties]: het bestand met berichten in het Frans
# lay-out
layout.entete=Les M\u00e9decins Associ\u00e9s
layout.basdepage=ISTIA, universit\u00e9 d'Angers - application propuls\u00e9e par PrimeFaces et JQuery
# uitzondering
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
# formulier 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
# formulier 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
# fout
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]: het bestand met berichten in het Engels
# lay-out
layout.entete=Associated Doctors
layout.basdepage=ISTIA, Angers university - Application powered by PrimeFaces and JQuery
# uitzondering
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
# formulier 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
# formulier 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
# fout
erreur.titre=The following exceptions occurred
erreur.exceptions=Exceptions' chain
erreur.type=Exception type
erreur.message=Associated Message
erreur.accueil=Welcome
6.5. De paginasjabloon [layout.xhtml]
![]() |
Het sjabloon [layout.xhtml] ziet er als volgt uit:
<?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>
Het enige variabele deel van dit sjabloon is het gebied op de regels 28-30. Dit gebied bevindt zich in het gebied met id:formulier:inhoud (regel 27). Dit is belangrijk om te onthouden. De aanroepen AJAX die dit gebied bijwerken, hebben het attribuut update=":formulier:inhoud". Overigens begint het formulier op regel 15. Het fragment dat in de regels 28-30 wordt ingevoegd, wordt dus in dit formulier ingevoegd.
Dit sjabloon ziet er als volgt uit:
![]() |
Het dynamische gedeelte van de pagina wordt in het hierboven omkaderde gebied ingevoegd.
6.6. De pagina [index.xhtml]
![]() |
Het project toont altijd dezelfde pagina, namelijk de volgende 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>
- regels 8-9: dit fragment XHTML wordt ingevoegd in het dynamische gedeelte van het sjabloon [layout.xhtml],
- de pagina bestaat uit drie subfragmenten:
- [form1.xhtml], regels 10-12;
- [form2.xhtml], regels 13-15;
- [erreur.xhtml], regels 16-18.
De aanwezigheid van deze fragmenten in [index.xhtml] wordt bepaald door booleaanse waarden in het model [Form.java] dat aan de pagina is gekoppeld. Door deze waarden aan te passen, verandert de weergave van de pagina.
Het fragment [form1.xhtml] wordt als volgt weergegeven:
Het fragment [form2.xhtml] wordt als volgt weergegeven:
![]() |
Het fragment [erreur.xhtml] wordt als volgt weergegeven:
![]() |
6.7. De beans van het project
![]() |
De klasse uit het pakket [utils] is al besproken: de klasse [Messages] is een klasse die de internationalisering van berichten in een applicatie vergemakkelijkt. Deze is behandeld in paragraaf 2.8.5.7.
6.7.1. De Application-bean
De bean [Application.java] is een bean met het bereik application. Zoals bekend dient dit type bean om alleen-lezen gegevens op te slaan die beschikbaar zijn voor alle gebruikers van de applicatie. Deze bean is als volgt:
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 {
// bedrijfslaag
@EJB
private IMetierLocal metier;
public Application() {
}
// getters
public IMetierLocal getMetier() {
return metier;
}
}
- regel 8: de bean krijgt de naam ‘application’,
- regel 9: de bean heeft het bereik ‘application’,
- regels 13-14: er wordt een verwijzing naar de lokale interface van de laag [métier] in geïnjecteerd door de container EJB van de applicatieserver. Laten we de architectuur van de applicatie nog eens in herinnering brengen:
![]() |
De applicaties JSF, EJB en [Metier] worden uitgevoerd in dezelfde JVM (Java Virtual Machine). De laag [JSF] zal dus gebruikmaken van de lokale interface van de EJB. Dat is alles. De bean [Application] bevat verder niets. Om toegang te krijgen tot de laag [métier], zullen de andere beans deze uit deze bean ophalen.
6.7.2. De bean [Erreur]
De klasse [Erreur] ziet er als volgt uit:
- package beans;
public class Erreur {public Erreur() {// veldprivate String classe;private String message;// constructorpublic Erreur(String classe, String message){this.setClasse(classe);this.message=message;// getters en setters
- regel 9: de naam van een uitzonderingsklasse als er een uitzondering is gegenereerd,
- regel 10: een foutmelding.
6.7.3. De bean [Form]
De code ervan is als volgt:
package beans;
import java.io.IOException;
...
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// Application-bean
@Inject
private Application application;
// sessiecache
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() {
// artsen en klanten in de cache opslaan
try {
medecins = application.getMetier().getAllMedecins();
clients = application.getMetier().getAllClients();
} catch (Throwable th) {
// de fout wordt geregistreerd
prepareVueErreur(th);
return;
}
// de woordenboeken
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
...
// weergave
private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean erreurRendered) {
this.form1Rendered = form1Rendered;
this.form2Rendered = form2Rendered;
this.erreurRendered = erreurRendered;
}
// voorbereiding vueErreur
private void prepareVueErreur(Throwable th) {
// de foutenlijst wordt aangemaakt
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()));
}
// de foutweergave wordt getoond
setForms(true, false, true);
}
// getters en setters
...
}
- regels 6-8: de klasse [Form] is een bean met de naam ‘form’ en een bereik van ‘session’. We herinneren eraan dat de klasse in dat geval serialiseerbaar moet zijn,
- regels 14-15: de form-bean heeft een verwijzing naar de application-bean. Deze wordt geïnjecteerd door de servletcontainer waarin de applicatie draait (aanwezigheid van de annotatie @Inject).
- regels 17-44: het sjabloon van de pagina’s [form1.xhtml, form2.xhtml, erreur.xhtml]. De weergave van deze pagina’s wordt bepaald door de booleaanse waarden in de regels 27-29. Merk op dat standaard de pagina [form1.xhtml] wordt weergegeven (regel 27),
- regels 46-47: de methode init wordt direct na het instantiëren van de klasse uitgevoerd (aanwezigheid van de annotatie @PostConstruct),
- regels 50-51: de laag [métier] wordt gevraagd om de lijst met artsen en klanten,
- regels 59-65: als alles goed is gegaan, worden de woordenboeken met artsen en klanten samengesteld. Ze worden geïndexeerd op nummer. Vervolgens wordt de pagina [form1.xhtml] weergegeven (regel 27),
- regel 54: in geval van een fout wordt het sjabloon van de pagina [erreur.xhtml] opgebouwd. Dit sjabloon is de foutenlijst uit regel 36,
- regels 78-88: de methode [prepareVueErreur] stelt de weer te geven foutenlijst samen. De pagina [index.xhtml] geeft vervolgens de fragmenten [form1.xhtml] en [erreur.xhtml] weer (regel 87).
De pagina [erreur.xhtml] ziet er als volgt uit:
<?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>
Deze pagina maakt gebruik van een <p:dataTable>-tag (regels 12-28) om de lijst met fouten weer te geven. Dit resulteert in een foutpagina die er ongeveer zo uitziet:
![]() |
We gaan nu de verschillende fasen in de levenscyclus van de applicatie definiëren. Voor elke actie van de gebruiker zullen we de betreffende weergaven en de gebeurtenishandlers bekijken.
6.8. Het weergeven van de startpagina
Als alles goed gaat, is de eerste pagina die wordt weergegeven [form1.xhtml]. Dit levert de volgende weergave op:
![]() |
De pagina [form1.xhtml] ziet er als volgt uit:
<?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>
De werkbalk die in de schermafbeelding is omkaderd, is de Primefaces-component Toolbar. Deze wordt gedefinieerd in de regels 8-14. Hij bevat twee groepen componenten, die elk worden gedefinieerd door een <toolbarGroup>-tag, in de regels 9-11 en 12-14. De ene groep is links uitgelijnd in de werkbalk (regel 9), de andere rechts (regel 12).
Laten we enkele componenten van de linkergroep eens bekijken:
<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>
...
- regels 4-7: de keuzelijst met artsen waaraan een effect is toegevoegd (effect="fade"),
- regel 6: een gedrag AJAX. Wanneer er een wijziging plaatsvindt in de keuzelijst, wordt de methode [Form].hideAgenda (listener="#{form.hideAgenda}") uitgevoerd en het dynamische gebied :formulier:inhoud (update=":formulaire:contenu") bijgewerkt,
- regel 8: voegt een scheidingsteken toe aan de werkbalk,
- regels 10-12: het invoerveld voor de datum. Hier wordt de PrimeFaces-kalender gebruikt. Het invoerveld is alleen-lezen (readOnlyInputText="true"),
- regel 11: een gedrag AJAX. Wanneer de datum verandert, wordt de methode [Form].hideAgenda uitgevoerd en wordt het dynamische veld :formulier:inhoud bijgewerkt,
- regel 14: een knop. Als je hierop klikt, wordt een aanroep AJAX naar de methode [Form].getAgenda () uitgevoerd; het sjabloon wordt dan aangepast en het antwoord van de server wordt gebruikt om het dynamische veld :formulier:inhoud bij te werken,
- regel 15: met de tag <tooltip> kun je een helpballon aan een component koppelen. De id van deze component wordt aangeduid door het for-attribuut van de tooltip. Hier (for="resa-agenda") verwijst dit naar de knop op regel 14:
![]() | ![]() |
Deze pagina wordt aangedreven door het volgende sjabloon:
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// sessiecache
private List<Medecin> medecins;
private List<Client> clients;
// sjabloon
private Long idMedecin;
private Date jour = new Date();
// lijst met artsen
public List<Medecin> getMedecins() {
return medecins;
}
// lijst met klanten
public List<Client> getClients() {
return clients;
}
// agenda
public void getAgenda() {
...
}
- het veld op regel 12 leest en schrijft de waarde van de lijst op regel 4 van de pagina. Bij de eerste weergave van de pagina stelt het de geselecteerde waarde in de keuzelijst vast. Bij de eerste weergave is idMedecin gelijk aan null, dus wordt de eerste arts geselecteerd,
- de methode in de regels 16-18 genereert de opties voor de keuzelijst met artsen (regel 5 van de pagina). Elke gegenereerde optie heeft als label (itemLabel) de titel, achternaam en voornaam van de arts en als waarde (itemValue) de id van de arts,
- het veld op regel 13 voorziet het invoerveld op regel 10 van de pagina van gegevens (lezen/schrijven). Bij de eerste weergave wordt dus de huidige datum weergegeven,
- regels 26-28: de methode getAgenda verwerkt de klik op de knop [Agenda] op regel 14 van de pagina. Deze is vrijwel identiek aan wat het was in de versie JSF:
// Application-bean
@Inject
private Application application;
// sessiecache
private List<Medecin> medecins;
private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
// sjabloon
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 {
// de arts ophalen
medecin = hMedecins.get(idMedecin);
// de agenda van de arts voor een bepaalde dag
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// formulier 2 weergeven
setForms(true, true, false);
} catch (Throwable th) {
// foutenoverzicht
prepareVueErreur(th);
}
// er is momenteel geen tijdvak geselecteerd
creneauChoisi = null;
}
We zullen deze code niet toelichten. Dat is al gedaan.
6.9. De agenda van een arts weergeven
6.9.1. Overzicht van de agenda
Dit is het volgende gebruiksscenario:
![]() |
- in [1] selecteert men een arts [1] en een dag [2], waarna men met [3] de agenda van de arts voor de gekozen dag opvraagt,
- in [4] verschijnt deze onder de werkbalk.
De code van de pagina [form2.xhtml] is als volgt:
<?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>
<!-- contextmenu -->
<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']}">
<!-- kolom met tijdstippen -->
<p:column style="width: 100px">
...
</p:column>
<!-- kolom met klanten -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
<!-- bevestiging verwijdering RV -->
<p:confirmDialog id="confirmDialog" message="#{msg['form2.suppression.confirmation']}"
header="#{msg['form2.suppression.message']}" severity="alert" widgetVar="confirmation">
...
</p:confirmDialog>
<!-- foutmelding -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
...
</p:dialog>
<!-- beheer van serverreacties -->
<script type="text/javascript">
...
}
</script>
</body>
</html>
- regels 16-26: het belangrijkste onderdeel van de pagina is de tabel <dataTable> waarin de agenda van de arts wordt weergegeven,
![]() |
- regels 12-14: we gebruiken een contextmenu om een afspraak toe te voegen of te verwijderen:
![]() |
- regels 29-32: er wordt een bevestigingsvenster weergegeven wanneer de gebruiker een afspraak wil verwijderen:
![]() |
- regels 35-37: er wordt een dialoogvenster gebruikt om een fout te melden:
![]() |
- regels 40-43: we moeten wat JavaScript toevoegen.
6.9.2. De afsprakenlijst
We behandelen hier het model van een gegevenstabel zoals besproken in paragraaf 5.15, pagina 327.
Laten we het belangrijkste element van de pagina bekijken, de tabel die de agenda weergeeft:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- kolom met tijden -->
<p:column style="width: 100px">
...
</p:column>
<!-- kolom met klanten -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
De weergave ziet er als volgt uit:
![]() |
Het is een tabel met twee kolommen (regels 4-6 en 8-10) die wordt gevoed door de bron [Form].getMyDataModel() (value="#{form.myDataModel}"). Er kan telkens slechts één rij worden geselecteerd (selectionMode="single"). Bij elke POST wordt een referentie van het geselecteerde element toegewezen aan [Form].creneauChoisi (selection="#{form.creneauChoisi}").
We herinneren ons dat de methode getAgenda het volgende veld in het sjabloon heeft geïnitialiseerd:
// sjabloon
private AgendaMedecinJour agendaMedecinJour;
Het tabelmodel wordt verkregen door de volgende methode [Form].getMyDataModel (attribuut value van de tag <dataTable>) aan te roepen:
// het sjabloon van dataTable
public MyDataModel getMyDataModel() {
return new MyDataModel(agendaMedecinJour.getCreneauxMedecinJour());
}
Laten we eens kijken naar de klasse [MyDataModel] die als sjabloon dient voor de 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> {
// fabrikanten
public MyDataModel() {
}
public MyDataModel(CreneauMedecinJour[] creneauxMedecinJour) {
super(creneauxMedecinJour);
}
@Override
public Object getRowKey(CreneauMedecinJour creneauMedecinJour) {
return creneauMedecinJour.getCreneau().getId();
}
@Override
public CreneauMedecinJour getRowData(String rowKey) {
// lijst met tijdvakken
CreneauMedecinJour[] creneauxMedecinJour = (CreneauMedecinJour[]) getWrappedData();
// de sleutel is een long-geheelgetal
long key = Long.parseLong(rowKey);
// het geselecteerde tijdvak wordt opgezocht
for (CreneauMedecinJour creneauMedecinJour : creneauxMedecinJour) {
if (creneauMedecinJour.getCreneau().getId().longValue() == key) {
return creneauMedecinJour;
}
}
// niets
return null;
}
}
- regel 7: de klasse [MyDataModel] is het model voor de tag <p:dataTable>. Deze klasse heeft als doel de koppeling te leggen tussen het element rowkey dat wordt gepost, en het element dat bij deze regel hoort,
- regel 7: de klasse implementeert de interface [SelectableDataModel] via de klasse [ArrayDataModel]. Dit betekent dat de parameter van de constructor een array is. Deze array vult de tag <dataTable> in. Hier wordt elke rij van de array gekoppeld aan een element van het type [CreneauMedecinJour],
- regels 13-15: de constructor geeft zijn parameter door aan zijn bovenliggende klasse,
- regels 18-20: elke regel van de array komt overeen met een tijdslot en wordt geïdentificeerd door de id van het tijdslot (regel 19). Deze id wordt naar de server verzonden,
- regel 23: de code die aan de serverzijde wordt uitgevoerd wanneer de id van een tijdslot wordt verzonden. Het doel van deze methode is om de referentie van het object [CreneauMedecinJour] dat aan deze id is gekoppeld, op te leveren. Deze referentie wordt toegewezen aan de waarde van het attribuut `selection` van de tag <dataTable>:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
Het veld [Form].creneauChoisi bevat dus de referentie van het object [CreneauMedecinJour] dat men wil toevoegen of verwijderen.
6.9.3. De kolom met de tijdslots
![]() |
De kolom met de tijdslots wordt verkregen met de volgende code:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- kolom met tijdstippen -->
<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>
<!-- kolom met klanten -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
- regels 5-7: de kolomkop,
- regels 8-15: het huidige element van de kolom. Let op regel 9: het gebruik van de tag <h:outputFormat>, waarmee de weer te geven elementen kunnen worden opgemaakt. De parameter value geeft de weer te geven tekenreeks aan. De notatie {i,type,format} verwijst naar parameter nr. i, het type van deze parameter en de opmaak ervan. Er zijn hier 4 parameters, genummerd van 0 tot 3; het type hiervan is numeriek en ze worden weergegeven met twee cijfers,
- regels 10-13: de vier parameters die door de tag <h:outputFormat> worden verwacht.
6.9.4. De kolom met klanten
![]() |
De kolom met klanten wordt verkregen met de volgende code:
<!-- agenda -->
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- kolom met tijdstippen -->
...
<!-- kolom met klanten -->
<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>
- regels 8-10: de kolomkop,
- regels 11-13: het huidige element wanneer er een afspraak is voor het tijdslot. In dit geval worden de titel, voornaam en achternaam weergegeven van de klant voor wie deze afspraak is gemaakt,
- regels 14-16: een ander fragment waar we later op terugkomen.
6.10. Een afspraak verwijderen
Het verwijderen van een afspraak verloopt als volgt:
![]() |
![]() |
Het scherm waarop deze actie betrekking heeft, is het volgende:
<!-- contextmenu -->
<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>
<!-- bevestiging verwijdering 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>
- regels 2-5: een contextmenu dat gekoppeld is aan de gegevenstabel (attribuut 'for'). Het bevat twee opties [1]:
![]() |
- regel 4: de optie [Supprimer] activeert de weergave van het dialoogvenster [2] van de regels 13-20,
- regel 15: als je op [Oui] klikt, wordt [Form.action] uitgevoerd, waardoor de afspraak wordt verwijderd. Normaal gesproken zou het contextmenu de optie [Supprimer] niet moeten aanbieden als het geselecteerde item geen afspraak heeft, en de optie [Réserver] niet als het geselecteerde item wel een afspraak heeft. Het is ons niet gelukt om het contextmenu zo subtiel te maken. Dit lukt wel voor het eerste geselecteerde element, maar daarna blijkt dat het contextmenu de instelling behoudt die voor deze eerste selectie is vastgelegd. Het wordt dan onjuist. Daarom hebben we beide opties behouden en besloten we de gebruiker feedback te geven als hij een element zonder afspraak verwijdert,
- regel 16: het attribuut oncomplete waarmee JavaScript-code kan worden gedefinieerd die moet worden uitgevoerd na het uitvoeren van de aanroep AJAX. Deze code is hier als volgt:
<!-- foutmelding -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
<h:outputText value="#{form.msgErreur}" />
</p:dialog>
<!-- beheer van de serverrespons -->
<script type="text/javascript">
function handleRequest(xhr, status, args) {
// fout?
if(args.erreur) {
dlgErreur.show();
}
}
</script>
- regel 10: de JavaScript-code controleert of het woordenboek args het attribuut erreur heeft. Zo ja, dan wordt het dialoogvenster van regel 2 weergegeven (attribuut widgetVar). Dit dialoogvenster toont het sjabloon [Form].msgErreur.
Laten we eens kijken naar de code die wordt uitgevoerd om het verwijderen van een afspraak af te handelen:
<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>
- regel 2: de methode [Form].action wordt uitgevoerd,
- regel 4: vóór de uitvoering krijgt het veld action de waarde 'supprimer'.
De methode [action] is als volgt:
// actie op RV
public void action() {
// afhankelijk van de gewenste actie
if (action.equals("supprimer")) {
supprimer();
}
...
}
public void supprimer() {
// moet er iets gebeuren?
Rv rv = creneauChoisi.getRv();
if (rv == null) {
signalerActionIncorrecte();
return;
}
try {
// een afspraak verwijderen
application.getMetier().supprimerRv(rv);
// de agenda wordt bijgewerkt
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// formulier 2 weergeven
setForms(true, true, false);
} catch (Throwable th) {
// fouten bekijken
prepareVueErreur(th);
}
// het geselecteerde tijdvak wissen
creneauChoisi = null;
}
- regel 4: als de actie 'verwijderen' is, wordt de methode [supprimer] uitgevoerd,
- regel 12: de afspraak van het geselecteerde tijdvak wordt opgehaald. Ter herinnering: [creneauChoisi] is geïnitialiseerd met de referentie van het geselecteerde element [CreneauMedecinJour],
- als deze afspraak bestaat, wordt deze verwijderd (regel 19), wordt de agenda opnieuw gegenereerd (regel 21) en vervolgens opnieuw weergegeven (regel 23),
- als het verwijderen is mislukt, wordt de foutpagina weergegeven (regel 26),
- als het geselecteerde element geen afspraak bevat (regel 13), dan is er sprake van een situatie waarin de gebruiker op [Supprimer] heeft geklikt op een tijdslot zonder afspraak. Deze fout wordt gemeld:
![]() |
De methode [signalerActionIncorrecte] is als volgt:
// een onjuiste handeling melden
private void signalerActionIncorrecte() {
// het geselecteerde tijdvak wissen
creneauChoisi = null;
// fout
msgErreur = Messages.getMessage(null, "form2.erreurAction", null).getSummary();
RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
}
- regel 4: de selectie wordt verwijderd,
- regel 6: er wordt een geïnternationaliseerde foutmelding gegenereerd,
- regel 7: het attribuut ('fout', true) wordt toegevoegd aan het woordenboek 'args' van de aanroep AJAX.
Laten we teruggaan naar de code XHTML van de knop [Oui]:
<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
oncomplete="handleRequest(xhr, status, args); confirmation.hide()">
- regel 2: na uitvoering van de methode [Form].action wordt de JavaScript-methode handleRequest uitgevoerd:
<!-- foutmelding -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
<h:outputText value="#{form.msgErreur}" />
</p:dialog>
<!-- beheer van het antwoord van de server -->
<script type="text/javascript">
function handleRequest(xhr, status, args) {
// fout?
if(args.erreur) {
dlgErreur.show();
}
}
</script>
- regel 10: er wordt gecontroleerd of het woordenboek args het kenmerk 'erreur' bevat. Zo ja, dan wordt het dialoogvenster van regel 2 weergegeven,
- regel 3: daarin wordt de foutmelding weergegeven die door het sjabloon is opgebouwd.
6.11. Afspraak maken
Het maken van een afspraak verloopt volgens de volgende volgorde:
![]() |
De weergave die bij deze actie hoort, is de volgende:
<!-- contextmenu -->
<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']}">
<!-- kolom met tijden -->
<p:column style="width: 100px">
...
</p:column>
<!-- kolom met klanten -->
<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>
...
- regels 21-31: geven het volgende weer:
![]() |
- regel 21: de weergave vindt plaats als er geen afspraak is, er wel een selectie is gemaakt en de id van het gekozen tijdvak overeenkomt met die van het huidige element in de tabel. Als deze voorwaarde niet wordt gesteld, wordt het fragment voor alle tijdvakken weergegeven,
- regel 22: het invoerveld wordt een ondersteund invoerveld. Hier wordt ervan uitgegaan dat er veel klanten kunnen zijn,
- regels 24-26: de link [Valider],
- regels 28-30: de koppeling [Annuler].
Het ondersteunde invoerveld wordt gegenereerd door de volgende code:
<p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>
De methode [Form].completeClients is verantwoordelijk voor het doen van suggesties aan de gebruiker op basis van de tekens die in het invoerveld zijn getypt:
![]() |
De voorstellen hebben de vorm [Nom prénom titre]. De code van de methode [Form].completeClients is als volgt:
// de methode voor het automatisch aanvullen van tekst
public List<String> completeClients(String query) {
List<String> identites = new ArrayList<String>();
// er wordt gezocht naar overeenkomende klanten
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();
}
- regel 2: query is de door de gebruiker ingevoerde tekenreeks,
- regel 3: de lijst met voorstellen. In eerste instantie een lege lijst,
- regels 5-10: de identiteiten [Nom prénom titre] van de klanten worden samengesteld. Als een identiteit begint met query (regel 7), wordt deze opgenomen in de lijst met voorstellen (regel 8).
6.12. Bevestiging van een afspraak
De bevestiging van een afspraak verloopt als volgt:
![]() |
De code van de link [Valider] is als volgt:
<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>
Het is dus de methode [Form].action() die deze gebeurtenis zal afhandelen. Ondertussen heeft het model [Form].action de tekenreeks 'valider' ontvangen. De code is als volgt:
// Application-bean
@Inject
private Application application;
// sessiecache
...
private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
// sjabloon
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);
}
}
// actie op RV
public void action() {
// afhankelijk van de gewenste actie
...
if (action.equals("valider")) {
validerResa();
}
}
// bevestiging afspraak
public void validerResa() {
// bevestiging van de reservering
try {
// bestaat de klant?
Boolean erreur = !hIdentitesClients.containsKey(identiteClient);
if (erreur) {
msgErreur = Messages.getMessage(null, "form2.erreurClient", new Object[]{identiteClient}).getSummary();
RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
return;
}
// de afspraak wordt toegevoegd
application.getMetier().ajouterRv(jour, creneauChoisi.getCreneau(), hIdentitesClients.get(identiteClient));
// de agenda wordt bijgewerkt
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// form2 wordt weergegeven
setForms(true, true, false);
} catch (Throwable th) {
// foutweergave
prepareVueErreur(th);
}
// het geselecteerde tijdvak wordt gewist
creneauChoisi = null;
// klant resetten
identiteClient = null;
}
- regels 33-35: vanwege de waarde van het veld action wordt de methode [validerResa] uitgevoerd,
- regel 43: eerst wordt gecontroleerd of de klant bestaat. In het veld voor geassisteerde invoer heeft de gebruiker namelijk een vaste invoer kunnen doen zonder gebruik te maken van de voorgestelde opties. De geassisteerde invoer is gekoppeld aan het model [Form].identiteClient. Er wordt dus gekeken of deze identiteit voorkomt in het woordenboek identitesClients dat bij het instantiëren van het model is aangemaakt (regel 20). Dit woordenboek koppelt aan een klantidentiteit van het type [Nom prénom titre] de klant zelf (regel 25),
- regel 44: als de klant niet bestaat, wordt er een foutmelding teruggestuurd naar de browser,
- regel 45: een geïnternationaliseerd foutbericht,
- regel 46: het attribuut ('fout', true) wordt toegevoegd aan het woordenboek args van de aanroep AJAX. De aanroep AJAX is als volgt gedefinieerd:
<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>
In regel 3 hierboven zien we dat de koppeling [Valider] een oncomplete-attribuut heeft. Dit attribuut zorgt ervoor dat de foutmelding wordt weergegeven volgens een techniek die we al eerder zijn tegengekomen.
- regel 50: de laag [métier] wordt gevraagd om een afspraak toe te voegen voor de gekozen dag (jour), het gekozen tijdvak (creneauChoisi.getCreneau()) en de gekozen klant (hIdentitesClients.get(identiteClient)),
- regel 52: de laag [métier] wordt gevraagd de agenda van de arts te vernieuwen. We zien de toegevoegde afspraak plus alle wijzigingen die andere gebruikers van de applicatie mogelijk hebben aangebracht,
- regel 54: de agenda [form2.xhtml] wordt opnieuw weergegeven,
- regel 57: de foutpagina wordt weergegeven als er een fout optreedt.
6.13. Een afspraak annuleren
Dit komt overeen met de volgende reeks:
![]() |
De knop [Annuler] op de pagina [form2.xhtml] is als volgt:
<p:commandLink action="#{form.action()}" value="#{msg['form2.annuler']}" update=":formulaire:contenu">
<f:setPropertyActionListener value="annuler" target="#{form.action}"/>
</p:commandLink>
De methode [Form].action wordt dus aangeroepen:
// actie op RV
public void action() {
// afhankelijk van de gewenste actie
...
if (action.equals("annuler")) {
annulerRv();
}
}
// afspraak annuleren
public void annulerRv() {
// formulier 2 wordt weergegeven
setForms(true, true, false);
// het geselecteerde tijdvak wissen
creneauChoisi = null;
// klantgegevens wissen
identiteClient = null;
}
6.14. Navigeren in de kalender
Met de werkbalk kun je door de kalender navigeren:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Hoewel dit niet te zien is op de bovenstaande schermafbeeldingen, wordt de agenda bijgewerkt met de afspraken van de nieuw geselecteerde dag.
De tags van de drie betreffende knoppen zijn als volgt in [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>
De methoden [Form].getPreviousAgenda, [Form].getNextAgenda, [Form].today zijn als volgt:
private Date jour = new Date();
public void getPreviousAgenda() {
// naar de vorige dag gaan
Calendar cal = Calendar.getInstance();
cal.setTime(jour);
cal.add(Calendar.DAY_OF_YEAR, -1);
jour = cal.getTime();
// agenda
if (form2Rendered) {
getAgenda();
}
}
public void getNextAgenda() {
// ga naar de volgende dag
Calendar cal = Calendar.getInstance();
cal.setTime(jour);
cal.add(Calendar.DAY_OF_YEAR, 1);
jour = cal.getTime();
// agenda
if (form2Rendered) {
getAgenda();
}
}
// agenda vandaag
public void today() {
jour = new Date();
// agenda
if (form2Rendered) {
getAgenda();
}
}
- regel 1: de dag waarop de agenda wordt weergegeven,
- regel 5: er wordt een kalender gebruikt,
- regel 6: die wordt ingesteld op de huidige dag van de agenda,
- regel 7: er wordt een dag van de kalender afgetrokken,
- regel 8: en deze opnieuw instellen op de weergavedag van de agenda,
- regel 11: de agenda wordt opnieuw weergegeven als deze al wordt weergegeven. De gebruiker kan namelijk de werkbalk gebruiken zonder dat de agenda wordt weergegeven.
De andere methoden werken op dezelfde manier.
6.15. Wijziging van de weergavetaal
Het wisselen van taal gebeurt via de menuknop op de werkbalk:
![]() |
![]() |
De tags van de menuknop zijn als volgt:
<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>
De methoden die in het model worden uitgevoerd, zijn de volgende:
private String locale = "fr";
public void setFrenchLocale() {
locale = "fr";
// pagina opnieuw laden
redirect();
}
public void setEnglishLocale() {
locale = "en";
// de pagina wordt opnieuw geladen
redirect();
}
private void redirect() {
// de klant wordt doorgestuurd naar de servlet
ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
try {
ctx.redirect(ctx.getRequestContextPath());
} catch (IOException ex) {
Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
}
}
De methoden op regel 3 en 9 initialiseren alleen het veld locale van regel 1 en leiden vervolgens de browser van de klant om naar dezelfde pagina. Een omleiding is een antwoord waarbij de server de browser vraagt om een andere pagina te laden. De browser voert dan een GET uit naar deze nieuwe pagina.
- regel 17: [ExternalContext] is een klasse van JSF die toegang biedt tot de momenteel actieve servlet,
- regel 19: de omleiding wordt uitgevoerd. De parameter van de methode redirect is de URL van de pagina waarnaar de clientbrowser moet worden omgeleid. Hier willen we omleiden naar [/mv-rdvmedecins-pf], de naam van onze applicatie:
![]() |
met de methode [getRequestContextPath] kunnen we deze naam ophalen. De startpagina [index.xhtml] van onze applicatie wordt dus geladen. Deze pagina is gekoppeld aan het model [Form] met sessiebereik. Dit model beheert drie booleaanse variabelen die het uiterlijk van de pagina [index.xhtml] bepalen:
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
Aangezien het sjabloon een sessiebereik heeft, hebben deze drie booleaanse variabelen hun waarden behouden. De pagina [index.xhtml] zal dus verschijnen in de staat waarin deze zich bevond vóór de omleiding. Deze pagina wordt opgemaakt met het volgende facelet-sjabloon [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>
De tag op regel 9 bepaalt de weergavetaal van de pagina via het attribuut local. De pagina wordt dus naar gelang het geval in het Frans of het Engels weergegeven. Maar waarom is er dan een omleiding? Laten we teruggaan naar de tags van de opties voor het wisselen van taal:
<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>
Deze waren oorspronkelijk geschreven om via een aanroep van AJAX het formulier-id-veld bij te werken (attribuut update in de regels 7 en 8). Maar tijdens het testen werkte de taalwisseling niet altijd. Vandaar de omleiding om dit probleem op te lossen. Misschien had men ook het attribuut ajax='false' aan de tags kunnen toevoegen om de pagina opnieuw te laden. Dat zou de omleiding dan hebben voorkomen.
6.16. Lijsten vernieuwen
Dit komt overeen met de volgende actie:
![]() |
De tag die bij de optie [Rafraîchir] hoort, is de volgende:
<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>
De methode [Form].refresh is als volgt:
public void refresh() {
// de lijsten worden vernieuwd
init();
}
De methode init wordt direct na het aanmaken van de bean [Form] uitgevoerd. Deze methode heeft tot doel gegevens uit de database in het model in de cache op te slaan:
// Application-bean
@Inject
private Application application;
// sessiecache
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() {
// artsen en klanten worden in de cache opgeslagen
try {
medecins = application.getMetier().getAllMedecins();
clients = application.getMetier().getAllClients();
} catch (Throwable th) {
...
}
...
// de woordenboeken
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
De methode init stelt de lijsten en woordenboeken van de regels 5-9 samen. Het nadeel van deze techniek is dat deze elementen geen rekening meer houden met wijzigingen in de database (toevoeging van een klant, een arts, ...). De methode refresh dwingt de heropbouw van deze lijsten en woordenboeken af. Daarom zullen we deze methode gebruiken telkens wanneer er een wijziging in de database wordt aangebracht, bijvoorbeeld het toevoegen van een nieuwe klant.
6.17. Conclusion
Laten we nog eens de architectuur van de applicatie die we zojuist hebben gebouwd in herinnering brengen:
![]() |
We hebben grotendeels gebruikgemaakt van de reeds gebouwde versie JSF2:
- de lagen [métier], [DAO] en [JPA] zijn behouden,
- de beans [Application] en [Form] van de weblaag zijn behouden, maar er zijn nieuwe functionaliteiten aan toegevoegd vanwege de uitbreiding van de gebruikersinterface,
- de gebruikersinterface is grondig gewijzigd. Deze is met name rijker aan functionaliteiten en gebruiksvriendelijker geworden.
De overstap van JSF naar Primefaces voor het bouwen van de webinterface vereist enige ervaring, omdat men in het begin een beetje overweldigd raakt door het grote aantal beschikbare componenten en uiteindelijk niet goed weet welke men moet gebruiken. Men moet zich dan richten op de gewenste ergonomie van de interface.
6.18. Eclipse-tests
Net zoals we bij de vorige versies van de voorbeeldtoepassing hebben gedaan, laten we zien hoe je deze versie 03 met Eclipse kunt testen. Allereerst importeren we de Maven-projecten van voorbeeld 03 [1] in Eclipse:
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: de lagen [DAO] en [JPA],
- [mv-rdvmedecins-ejb-metier]: de laag [métier],
- [mv-rdvmedecins-pf]: de laag [web], geïmplementeerd met JSF en Primefaces,
- [mv-rdvmedecins-app]: het bovenliggende project van het bedrijfsproject [mv-rdvmedecins-app-ear]. Wanneer het bovenliggende project wordt geïmporteerd, wordt het onderliggende project automatisch geïmporteerd,
- in [2] wordt het bedrijfsproject [mv-rdvmedecins-app-ear] uitgevoerd,
![]() |
- in [3], de Glassfish-server wordt geselecteerd,
- in [4], op het tabblad [Servers], is de applicatie geïmplementeerd. Deze start niet vanzelf. Je moet de URL [http://localhost:8080/mv-rdvmedecins-pf/] opvragen in een browser [5]:
![]() |
















































