6. Aplicação de exemplo-03: rdvmedecins-pf-ejb
Vamos rever a estrutura da aplicação de exemplo desenvolvida para o servidor GlassFish:
![]() |
Não vamos alterar nada nesta arquitetura, exceto a camada web, que será implementada aqui utilizando JSF e PrimeFaces.
![]() |
6.1. O projeto NetBeans
Acima, as camadas [business] e [DAO] são as do Exemplo 01 JSF / EJB / Glassfish. Estamos a reutilizá-las.
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: Projeto EJB para as camadas [DAO] e [JPA] do Exemplo 01,
- [mv-rdvmedecins-ejb-metier]: projeto EJB para a camada [business] do Exemplo 01,
- [mv-rdvmedecins-pf]: projeto da camada [web] / Primefaces – novo,
- [mv-rdvmedecins-app-ear]: projeto empresarial para implementar a aplicação no servidor GlassFish – novo.
6.2. O projeto empresarial
O projeto empresarial é utilizado exclusivamente para implementar os três módulos [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-business], [mv-rdvmedecins-pf] no servidor GlassFish. O projeto NetBeans é o seguinte:
![]() |
O projeto existe exclusivamente para estas três dependências [1] definidas no ficheiro [pom.xml] da seguinte forma:
<?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>
- linhas 10–13: o artefacto Maven para o projeto empresarial,
- linhas 18–37: as três dependências do projeto. Repare nos seus tipos (linhas 23, 29, 35).
Para executar a aplicação web, deve executar este projeto empresarial.
6.3. O projeto web PrimeFaces
O projeto web PrimeFaces é o seguinte:
![]() |
- em [1], as páginas do projeto. A página [index.xhtml] é a única página do projeto. Contém três fragmentos: [form1.xhtml], [form2.xhtml] e [error.xhtml]. As outras páginas são incluídas apenas para fins de formatação.
- em [2], os Java beans. O bean [Application] tem âmbito de aplicação e o bean [Form] tem âmbito de sessão. A classe [Error] encapsula um erro. A classe [MyDataModel] serve como modelo para uma tag <dataTable> do PrimeFaces,
- em [3], os ficheiros de mensagens para internacionalização,
- em [4], as dependências. O projeto web depende do projeto EJB para a camada [DAO], do projeto EJB para a camada [business] e do PrimeFaces para a camada [web].
6.4. Configuração do projeto
A configuração do projeto é a mesma dos projetos PrimeFaces ou JSF que estudámos. Listamos os ficheiros de configuração sem os explicar novamente.
![]() | ![]() |
[web.xml]: configura a aplicação 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>
Note que, na linha 30, a página [index.xhtml] é a página inicial da aplicação.
[faces-config.xml]: configura a aplicação 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]: vazio, mas necessário para a anotação @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]: a folha de estilo da aplicação
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
A biblioteca PrimeFaces inclui as suas próprias folhas de estilo. A folha de estilo acima é utilizada apenas para a página a ser apresentada em caso de exceção — uma página não gerida pela aplicação. A página [exception.xhtml] é então apresentada.
[messages_fr.properties]: o ficheiro de mensagens em francês
# 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]: o ficheiro de mensagens em inglês
# 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. O modelo de página [layout.xhtml]
![]() |
O modelo [layout.xhtml] é o seguinte:
<?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>
A única parte variável deste modelo é a área nas linhas 28–30. Esta área está localizada no ID :form:content (linha 27). Tenha isto em mente. As chamadas AJAX que atualizam esta área terão o atributo update=":form:content". Além disso, o formulário começa na linha 15. Portanto, o fragmento inserido nas linhas 28–30 é inserido neste formulário.
Este modelo produz o seguinte resultado:
![]() |
A parte dinâmica da página será inserida na área emoldurada acima.
6.6. A página [index.xhtml]
![]() |
O projeto apresenta sempre a mesma página, a seguinte página [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>
- linhas 8-9: este fragmento XHTML será inserido na área dinâmica do modelo [layout.xhtml],
- a página é composta por três subfragmentos:
- [form1.xhtml], linhas 10–12;
- [form2.xhtml], linhas 13–15;
- [error.xhtml], linhas 16–18.
A presença destes fragmentos em [index.xhtml] é controlada por valores booleanos no modelo [Form.java] associado à página. Assim, ao ajustar estes valores booleanos, a página renderizada altera-se.
O fragmento [form1.xhtml] é apresentado da seguinte forma:
O fragmento [form2.xhtml] é renderizado da seguinte forma:
![]() |
O fragmento [erreur.xhtml] é renderizado da seguinte forma:
![]() |
6.7. Os beans do projeto
![]() |
A classe no pacote [utils] já foi apresentada: a classe [Messages] facilita a internacionalização das mensagens de uma aplicação. Foi discutida na Secção 2.8.5.7.
6.7.1. O bean de aplicação
O bean [Application.java] é um bean com âmbito de aplicação. Recorde-se que este tipo de bean é utilizado para armazenar dados de leitura apenas, disponíveis para todos os utilizadores da aplicação. Este bean é o seguinte:
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;
}
}
- linha 8: atribuímos ao bean o nome "application",
- linha 9: ele tem âmbito de aplicação,
- linhas 13–14: uma referência à interface local da camada [de negócios] será injetada nele pelo contentor EJB do servidor de aplicações. Vamos rever a arquitetura da aplicação:
![]() |
A aplicação JSF e o EJB [Business] serão executados na mesma JVM (Java Virtual Machine). Portanto, a camada [JSF] utilizará a interface local do EJB. É tudo. O bean [Application] não contém mais nada. Para aceder à camada [Business], os outros beans irão obtê-la a partir deste bean.
6.7.2. O bean [Error]
A classe [Error] é a seguinte:
- pacote 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
...
}
- linha 9: o nome de uma classe de exceção, caso tenha sido lançada uma exceção,
- linha 10: uma mensagem de erro.
6.7.3. O bean [Form]
O seu código é o seguinte:
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
...
}
- linhas 6-8: a classe [Form] é um bean chamado «form» com âmbito de sessão. Note-se que a classe deve, portanto, ser serializável,
- linhas 14–15: o bean form tem uma referência ao bean da aplicação. Esta referência será injetada pelo contentor de servlets no qual a aplicação é executada (presença da anotação @Inject).
- linhas 17–44: os modelos de página [form1.xhtml, form2.xhtml, error.xhtml]. A exibição destas páginas é controlada pelos valores booleanos nas linhas 27–29. Note-se que, por predefinição, a página [form1.xhtml] é renderizada (linha 27),
- linhas 46–47: O método `init` é executado imediatamente após a instância da classe (devido à presença da anotação `@PostConstruct`),
- linhas 50-51: a camada [business] é consultada para obter a lista de médicos e clientes,
- linhas 59–65: se tudo correu bem, os dicionários de médicos e clientes são construídos. São indexados pelo seu número. Em seguida, a página [form1.xhtml] será exibida (linha 27),
- linha 54: em caso de erro, o modelo da página [error.xhtml] é construído. Este modelo é a lista de erros da linha 36,
- linhas 78–88: o método [prepareVueErreur] constrói a lista de erros a apresentar. A página [index.xhtml] apresenta então os fragmentos [form1.xhtml] e [erreur.xhtml] (linha 87).
A página [error.xhtml] é a seguinte:
<?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>
Utiliza uma tag <p:dataTable> (linhas 12–28) para apresentar a lista de erros. Isto resulta numa página de erro semelhante à seguinte:
![]() |
Vamos agora definir as diferentes fases do ciclo de vida da aplicação. Para cada ação do utilizador, iremos examinar as vistas e os manipuladores de eventos relevantes.
6.8. Exibição da página inicial
Se tudo correr bem, a primeira página apresentada é [form1.xhtml]. Isto resulta na seguinte vista:
![]() |
A página [form1.xhtml] é a seguinte:
<?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>
A barra de ferramentas destacada na captura de ecrã é o componente PrimeFaces Toolbar. Está definida nas linhas 8–14. Contém dois grupos de componentes, cada um definido por uma tag <toolbarGroup>, nas linhas 9–11 e 12–14. Um dos grupos está alinhado à esquerda da barra de ferramentas (linha 9), o outro à direita (linha 12).
Vamos examinar alguns componentes do grupo da esquerda:
<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>
...
- linhas 4-7: o menu suspenso dos médicos ao qual foi adicionado um efeito (effect="fade"),
- linha 6: um comportamento AJAX. Quando houver uma alteração no menu suspenso, o método [Form].hideAgenda (listener="#{form.hideAgenda}") será executado e a área dinâmica :form:content (update=":form:content") será atualizada,
- linha 8: inclui um separador na barra de ferramentas,
- linhas 10–12: o campo de entrada de data. O calendário PrimeFaces é utilizado aqui. O campo de entrada é de leitura apenas (readOnlyInputText="true"),
- linha 11: um comportamento AJAX. Quando a data for alterada, o método [Form].hideAgenda será executado e o campo dinâmico :form:content atualizado,
- linha 14: um botão. Clicar nele aciona uma chamada AJAX para o método [Form].getAgenda(); o modelo será então modificado e a resposta do servidor será usada para atualizar o campo dinâmico :form:content,
- linha 15: a tag <tooltip> permite associar uma dica de ferramenta a um componente. O ID do componente é especificado pelo atributo for da dica de ferramenta. Aqui (for="resa-agenda") refere-se ao botão na linha 14:
![]() | ![]() |
Esta página é alimentada pelo seguinte modelo:
@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() {
...
}
- O campo na linha 12 lê e grava o valor da lista na linha 4 da página. Quando a página é carregada pela primeira vez, define o valor selecionado na caixa combinada. No carregamento inicial, idMedecin é igual a null, pelo que o primeiro médico será selecionado.
- O método nas linhas 16–18 gera os itens para o menu suspenso dos médicos (linha 5 da página). Cada opção gerada terá como rótulo (itemLabel) o título, o apelido e o nome do médico e, como valor (itemValue), o ID do médico;
- o campo na linha 13 fornece acesso de leitura/gravação ao campo de entrada na linha 10 da página. Na exibição inicial, é apresentada a data atual,
- Linhas 26–28: O método getAgenda trata do clique no botão [Agenda] na linha 14 da página. É quase idêntico ao que era na versão 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;
}
Não iremos comentar este código. Isso já foi feito.
6.9. Exibir a agenda de um médico
6.9.1. Visão geral da agenda
Eis o seguinte caso de utilização:
![]() |
- Em [1], seleciona-se um médico [1] e um dia [2] e, em seguida, solicita-se [3] a agenda do médico para o dia selecionado;
- em [4], esta aparece abaixo da barra de ferramentas.
O código da página [form2.xhtml] é o seguinte:
<?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>
- linhas 16-26: o elemento principal da página é o <dataTable> que apresenta a agenda do médico,
![]() |
- linhas 12-14: vamos usar um menu de contexto para adicionar/eliminar uma consulta:
![]() |
- linhas 29-32: Será exibida uma caixa de diálogo de confirmação quando o utilizador pretender eliminar um compromisso:
![]() |
- linhas 35-37: será utilizada uma caixa de diálogo para comunicar um erro:
![]() |
- linhas 40–43: teremos de adicionar algum código JavaScript.
6.9.2. A Tabela de Consultas
Aqui abordaremos o modelo de tabela de dados, conforme discutido na Secção 5.15, página 327.
Vamos examinar o elemento principal da página, a tabela que exibe o calendário:
<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>
O resultado é o seguinte:
![]() |
Esta é uma tabela de duas colunas (linhas 4–6 e 8–10) preenchida pela fonte [Form].getMyDataModel() (value="#{form.myDataModel}"). Apenas uma linha pode ser selecionada de cada vez (selectionMode="single"). A cada POST, uma referência ao item selecionado é atribuída a [Form].creneauChoisi (selection="#{form.creneauChoisi}").
Recorde-se que o método getAgenda inicializou o seguinte campo no modelo:
// modèle
private AgendaMedecinJour agendaMedecinJour;
O modelo da tabela é obtido através da chamada ao seguinte método [Form].getMyDataModel (atributo «value» da tag <dataTable>):
// the dataTable model
public MyDataModel getMyDataModel() {
return new MyDataModel(agendaMedecinJour.getCreneauxMedecinJour());
}
Vamos examinar a classe [MyDataModel], que serve de modelo para a 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;
}
}
- linha 7: a classe [MyDataModel] é o modelo para a tag <p:dataTable>. O objetivo desta classe é associar o elemento rowkey que é enviado com o elemento associado a essa linha,
- Linha 7: A classe implementa a interface [SelectableDataModel] através da classe [ArrayDataModel]. Isto significa que o parâmetro do construtor é uma matriz. Esta matriz preenche a tag <dataTable>. Aqui, cada linha da matriz será associada a um elemento do tipo [CreneauMedecinJour],
- Linhas 13–15: O construtor passa o seu parâmetro para a sua classe pai,
- Linhas 18–20: Cada linha da matriz corresponde a um intervalo de tempo e será identificada pelo ID do intervalo de tempo (linha 19). É este ID que será enviado para o servidor,
- linha 23: o código que será executado no lado do servidor quando for enviado um ID de intervalo de tempo. O objetivo deste método é devolver a referência ao objeto [CreneauMedecinJour] associado a este ID. Esta referência será atribuída ao valor do atributo «selection» da tag <dataTable>:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
O campo [Form].creneauChoisi conterá, portanto, a referência do objeto [CreneauMedecinJour] que pretende adicionar ou eliminar.
6.9.3. A coluna do horário
![]() |
A coluna do intervalo de tempo é gerada utilizando o seguinte código:
<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>
- linhas 5-7: o cabeçalho da coluna,
- linhas 8–15: o elemento da coluna atual. Observe a linha 9, onde a tag <h:outputFormat> é usada para formatar os elementos a serem exibidos. O parâmetro value especifica a string a ser exibida. A notação {i,type,format} refere-se ao parâmetro número i, ao tipo desse parâmetro e ao seu formato. Aqui, existem 4 parâmetros numerados de 0 a 3; o seu tipo é numérico e serão apresentados com dois dígitos,
- Linhas 10–13: os quatro parâmetros esperados pela tag <h:outputFormat>.
6.9.4. A coluna «cliente»
![]() |
A coluna «Cliente» é gerada utilizando o seguinte código:
<!-- 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>
- linhas 8–10: o cabeçalho da coluna,
- linhas 11–13: o elemento atual quando existe um compromisso para o intervalo de tempo. Neste caso, apresentamos o título, o nome próprio e o apelido do cliente para quem este compromisso foi marcado,
- linhas 14–16: outro fragmento ao qual voltaremos mais tarde.
6.10. Eliminar um compromisso
A eliminação de um compromisso envolve a seguinte sequência:
![]() |
![]() |
A visão envolvida nesta ação é a seguinte:
<!-- 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>
- Linhas 2-5: Um menu de contexto associado à matriz de dados (para o atributo). Possui duas opções [1]:
![]() |
- linha 4: a opção [Apagar] aciona a exibição da caixa de diálogo [2] das linhas 13-20,
- linha 15: clicar em [Sim] aciona a execução de [Form.action], que irá eliminar o compromisso. Normalmente, o menu de contexto não deveria oferecer a opção [Eliminar] se o item selecionado não tiver nenhum compromisso, nem a opção [Marcar] se o item selecionado tiver um compromisso. Não conseguimos tornar o menu de contexto tão subtil. Funciona para o primeiro item selecionado, mas depois reparámos que o menu de contexto mantém a configuração definida para essa primeira seleção. Torna-se então incorreto. Por isso, mantivemos ambas as opções e decidimos fornecer feedback ao utilizador caso este eliminasse um item sem compromisso,
- Linha 16: O atributo `oncomplete`, que permite definir código JavaScript a ser executado após a conclusão do pedido AJAX. Neste caso, o código será o seguinte:
<!-- 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>
- Linha 10: O código JavaScript verifica se o dicionário args possui um atributo error. Se for o caso, exibe a caixa de diálogo da linha 2 (atributo widgetVar). Esta caixa de diálogo exibe o modelo [Form].msgError.
Vejamos o código executado para tratar a eliminação de um compromisso:
<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>
- Linha 2: O método [Form].action será executado,
- linha 4: antes de ser executado, o campo action terá sido definido como «delete».
O método [action] é o seguinte:
// 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;
}
- linha 4: se a ação for «delete», executar o método [delete],
- linha 12: recuperar o compromisso para o intervalo selecionado. Recorde-se que [selectedSlot] foi inicializado com a referência ao elemento [DoctorSlot] selecionado;
- se este compromisso existir, é eliminado (linha 19), o calendário é atualizado (linha 21) e, em seguida, voltado a apresentar (linha 23),
- se a eliminação falhar, é apresentada a página de erro (linha 26),
- se o elemento selecionado não tiver nenhum compromisso (linha 13), então estamos numa situação em que o utilizador clicou em [Delete] num intervalo que não tem nenhum compromisso. Reportamos este erro:
![]() |
O método [reportIncorrectAction] é o seguinte:
// 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);
}
- linha 4: remover a seleção,
- linha 6: gerar uma mensagem de erro internacionalizada,
- linha 7: adicionar o atributo ('error', true) ao dicionário args da chamada AJAX.
Voltemos ao código XHTML do botão [Sim]:
<p:commandButton value="#{msg['form2.supprimer.oui']}" update=":formulaire:contenu" action="#{form.action}"
oncomplete="handleRequest(xhr, status, args); confirmation.hide()">
- Linha 2: Após a execução do método [Form].action, o método handleRequest do JavaScript é executado:
<!-- 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>
- Linha 10: Verificamos se o dicionário args tem um atributo chamado «error». Se tiver, a caixa de diálogo da linha 2 é exibida.
- linha 3: exibe a mensagem de erro gerada pelo modelo.
6.11. Marcar uma consulta
Marcar uma consulta corresponde à seguinte sequência:
![]() |
A visão envolvida nesta ação é a seguinte:
<!-- 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>
...
- linhas 21–31: exiba o seguinte:
![]() |
- linha 21: isto é exibido se não houver compromissos, tiver sido feita uma seleção e o ID do intervalo de tempo selecionado corresponder ao da linha atual da tabela. Se esta condição não for incluída, o fragmento é exibido para todos os intervalos de tempo,
- linha 22: o campo de entrada será um campo de entrada assistida. Partimos do princípio de que poderá haver muitos clientes,
- Linhas 24–26: o link [Enviar],
- linhas 28–30: o link [Cancelar].
O campo de preenchimento automático é gerado pelo seguinte código:
<p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>
O método [Form].completeClients é responsável por fornecer sugestões ao utilizador com base nos caracteres digitados no campo de entrada:
![]() |
As sugestões têm o formato [Apelido, nome próprio, título]. O código para o método [Form].completeClients é o seguinte:
// 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();
}
- Linha 2: query é a string introduzida pelo utilizador,
- linha 3: a lista de sugestões. Inicialmente uma lista vazia,
- linhas 5–10: construímos os nomes completos dos clientes [Apelido, Nome próprio, Título]. Se um nome completo começar por query (linha 7), é adicionado à lista de sugestões (linha 8).
6.12. Confirmar uma marcação
A confirmação de uma marcação segue esta sequência:
![]() |
O código para o link [Validar] é o seguinte:
<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>
Assim, o método [Form].action() irá tratar este evento. Entretanto, o modelo [Form].action terá recebido a string «submit». O código é o seguinte:
// 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;
}
- linhas 33-35: devido ao valor do campo action, o método [validateReservation] será executado,
- Linha 43: Verificamos primeiro se o cliente existe. Isto porque, no campo de entrada assistida, o utilizador pode ter introduzido dados manualmente sem utilizar as sugestões fornecidas. A entrada assistida está associada ao modelo [Form].identiteClient. Por isso, verificamos se esta identidade existe no dicionário identitesClients criado quando o modelo foi instanciado (linha 20). Este dicionário associa uma identidade de cliente do tipo [Apelido, Nome próprio, Título] ao próprio cliente (linha 25),
- linha 44: se o cliente não existir, é devolvido um erro ao navegador,
- linha 45: uma mensagem de erro internacionalizada,
- linha 46: adicionamos o atributo ('error', true) ao dicionário args da chamada AJAX. A chamada AJAX foi definida da seguinte forma:
<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>
Na linha 3 acima, vemos que o link [Validate] tem um atributo oncomplete. É este atributo que irá exibir a mensagem de erro utilizando uma técnica que já conhecemos.
- Linha 50: Solicitamos à camada [business] que adicione um compromisso para o dia selecionado (day), o intervalo de tempo selecionado (creneauChoisi.getCreneau()) e o cliente selecionado (hIdentitesClients.get(identiteClient)),
- linha 52: solicitamos à camada [de negócios] que atualize a agenda do médico. Veremos a consulta adicionada, juntamente com quaisquer alterações que outros utilizadores da aplicação possam ter feito,
- linha 54: o calendário [form2.xhtml] é exibido novamente,
- linha 57: a página de erro é exibida se ocorrer um erro.
6.13. Cancelar uma consulta
Isto corresponde à seguinte sequência:
![]() |
O botão [Cancelar] na página [form2.xhtml] é o seguinte:
<p:commandLink action="#{form.action()}" value="#{msg['form2.annuler']}" update=":formulaire:contenu">
<f:setPropertyActionListener value="annuler" target="#{form.action}"/>
</p:commandLink>
O método [Form].action é, portanto, chamado:
// 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. Navegar no calendário
A barra de ferramentas permite-lhe navegar no calendário:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Embora não apareça nas imagens acima, o calendário é atualizado com os compromissos do dia recém-selecionado.
As etiquetas para os três botões relevantes são as seguintes em [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>
Os métodos [Form].getPreviousAgenda, [Form].getNextAgenda e [Form].today são os seguintes:
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();
}
}
- linha 1: o dia em que o calendário é exibido,
- linha 5: usamos um calendário,
- linha 6: que é inicializado para o dia atual do calendário,
- linha 7: subtraímos um dia do calendário,
- linha 8: e redefinimo-lo para a data de exibição do calendário,
- linha 11: redesenhamos o calendário se este estiver atualmente a ser apresentado. Isto porque o utilizador pode utilizar a barra de ferramentas mesmo que o calendário não esteja a ser apresentado.
Os outros métodos são semelhantes.
6.15. Alterar o idioma de exibição
A mudança de idioma é gerida pelo botão de menu na barra de ferramentas:
![]() |
![]() |
As etiquetas do botão do menu são as seguintes:
<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>
Os métodos executados no modelo são os seguintes:
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);
}
}
Os métodos nas linhas 3 e 9 simplesmente inicializam o campo local da linha 1 e, em seguida, redirecionam o navegador do cliente para a mesma página. Um redirecionamento é uma resposta na qual o servidor instrui o navegador a carregar outra página. O navegador então realiza uma solicitação GET para essa nova página.
- Linha 17: [ExternalContext] é uma classe JSF que fornece acesso ao servlet atualmente em execução,
- linha 19: realizamos o redirecionamento. O parâmetro do método de redirecionamento é a URL da página para a qual o navegador do cliente deve ser redirecionado. Aqui, queremos redirecionar para [/mv-rdvmedecins-pf], que é o nome da nossa aplicação:
![]() |
O método [getRequestContextPath] fornece este nome. A página inicial [index.xhtml] da nossa aplicação será, portanto, carregada. Esta página está associada ao modelo [Form] com âmbito de sessão. Este modelo gere três valores booleanos que controlam a apresentação da página [index.xhtml]:
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
Uma vez que o modelo está no âmbito da sessão, estes três valores booleanos mantiveram os seus valores. A página [index.xhtml] aparecerá, portanto, tal como estava antes do redirecionamento. Esta página é formatada utilizando o seguinte modelo 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>
A tag na linha 9 define o idioma de exibição da página utilizando o seu atributo locale. A página irá, portanto, mudar para francês ou inglês, conforme apropriado. Agora, porquê um redirecionamento? Voltemos às tags para as opções de mudança de idioma:
<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>
Foram originalmente escritos para atualizar a área de ID do formulário através de uma chamada AJAX (o atributo update nas linhas 7 e 8). No entanto, durante os testes, a mudança de idioma nem sempre funcionou. Daí o redirecionamento para resolver este problema. Também poderíamos ter definido o atributo ajax='false' nas tags para acionar uma recarga da página. Isso teria evitado o redirecionamento.
6.16. Atualizar as listas
Isto corresponde à seguinte ação:
![]() |
A tag associada à opção [Atualizar] é a seguinte:
<p:menuitem id="menuitem-rafraichir" value="#{msg['form1.rafraichir']}" actionListener="#{form.refresh}" update=":formulaire:contenu"/>
O método [Form].refresh é o seguinte:
public void refresh() {
// on rafraîchit les listes
init();
}
O método init é o método executado imediatamente após a criação do bean [Form]. O seu objetivo é armazenar em cache os dados da base de dados no modelo:
// 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);
}
}
O método init constrói as listas e os dicionários nas linhas 5–9. A desvantagem desta técnica é que estes elementos deixam de refletir as alterações na base de dados (adição de um cliente, um médico, etc.). O método refresh força a reconstrução destas listas e dicionários. Por isso, iremos utilizá-lo sempre que for feita uma alteração na base de dados, como a adição de um novo cliente.
6.17. Conclusão
Vamos rever a arquitetura da aplicação que acabámos de construir:
![]() |
Baseámo-nos fortemente na versão pré-construída do JSF2:
- as camadas [business], [DAO] e [JPA] foram mantidas,
- os beans [Application] e [Form] na camada web foram mantidos, mas foram-lhes adicionadas novas funcionalidades devido ao aperfeiçoamento da interface do utilizador,
- a interface do utilizador foi significativamente modificada. Em particular, é agora mais rica em funcionalidades e mais intuitiva.
A transição do JSF para o PrimeFaces na construção da interface web requer alguma experiência, uma vez que, inicialmente, o utilizador pode sentir-se um pouco sobrecarregado com o grande número de componentes disponíveis e, por fim, não tem a certeza de quais utilizar. Por isso, é necessário concentrar-se na usabilidade pretendida para a interface.
6.18. Testes no Eclipse
Tal como fizemos nas versões anteriores da aplicação de exemplo, vamos mostrar como testar esta versão 03 utilizando o Eclipse. Primeiro, importamos os projetos Maven para o Exemplo 03 [1] para o Eclipse:
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: as camadas [DAO] e [JPA],
- [mv-rdvmedecins-ejb-metier]: a camada [business],
- [mv-rdvmedecins-pf]: a camada [web] implementada com JSF e PrimeFaces,
- [mv-rdvmedecins-app]: o projeto pai do projeto empresarial [mv-rdvmedecins-app-ear]. Quando o projeto pai é importado, o projeto filho é importado automaticamente,
- em [2], execute o projeto empresarial [mv-rdvmedecins-app-ear],
![]() |
- em [3], selecione o servidor Glassfish,
- em [4], no separador [Servidores], a aplicação foi implementada. Não é executada automaticamente. Deve aceder ao seu URL [http://localhost:8080/mv-rdvmedecins-pf/] num navegador [5]:
![]() |
















































