6. Aplicativo de exemplo-03: rdvmedecins-pf-ejb
Vamos relembrar a estrutura da aplicação de exemplo desenvolvida para o servidor Glassfish:
![]() |
Não alteramos nada nessa arquitetura, exceto a camada web, que aqui será implementada com o auxílio de JSF e Primefaces.
![]() |
6.1. O projeto NetBeans
Acima, as camadas [métier] e [DAO] são as do exemplo 01 JSF / EJB / Glassfish. Nós as reutilizamos.
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: projeto EJB das camadas [DAO] e [JPA] do exemplo 01,
- [mv-rdvmedecins-ejb-metier]: projeto EJB da camada [métier] do exemplo 01,
- [mv-rdvmedecins-pf]: projeto da camada [web] / Primefaces – novo,
- [mv-rdvmedecins-app-ear]: projeto corporativo para implantar o aplicativo no servidor Glassfish – novo.
6.2. O projeto corporativo
O projeto corporativo serve exclusivamente para a implantação dos três módulos [mv-rdvmedecins-ejb-dao-jpa], [mv-rdvmedecins-ejb-metier] e [mv-rdvmedecins-pf] no servidor Glassfish. O projeto do NetBeans é o seguinte:
![]() |
O projeto existe apenas para essas três dependências [1], definidas no arquivo [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 artefato Maven do projeto corporativo,
- linhas 18-37: as três dependências do projeto. Observe bem o tipo de cada uma delas (linhas 23, 29, 35).
Para executar o aplicativo web, será necessário executar este projeto corporativo.
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. Ela contém três fragmentos: [form1.xhtml], [form2.xhtml] e [erreur.xhtml]. As demais páginas servem apenas para fins de formatação.
- Em [2], os beans Java. O bean [Application] com escopo application; o bean [Form] com escopo session. A classe [Erreur] encapsula um erro. A classe [MyDataModel] serve de modelo para uma tag <dataTable> do PrimeFaces,
- em [3], os arquivos de mensagens para internacionalização,
- em [4], as dependências. O projeto web depende do projeto EJB da camada [DAO], o projeto EJB da camada [métier] e o Primefaces para a camada [web].
6.4. A configuração do projeto
A configuração do projeto é a mesma dos projetos Primefaces ou JSF que analisamos. Listamos os arquivos de configuração sem explicá-los 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>
Observe, na linha 30, que a página [index.xhtml] é a página inicial do aplicativo.
[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 do aplicativo
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
A biblioteca Primefaces vem com suas próprias folhas de estilo. A folha de estilo acima é usada apenas para a página a ser exibida em caso de exceção, uma página não gerenciada pelo aplicativo. É a página [exception.xhtml] que é exibida nesse caso.
[messages_fr.properties]: o arquivo 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
# exceção
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
# formulário 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
# formulário 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
# erro
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 arquivo de mensagens em inglês
# layout
layout.entete=Associated Doctors
layout.basdepage=ISTIA, Angers university - Application powered by PrimeFaces and JQuery
# exceção
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
# formulário 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
# formulário 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
# erro
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 das páginas [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 desse modelo é a área das linhas 28 a 30. Essa área está localizada na zona de id:formulário:conteúdo (linha 27). É importante lembrar disso. As chamadas AJAX que atualizam essa área terão o atributo update=":formulário:conteúdo". Além disso, o formulário começa na linha 15. Portanto, o fragmento inserido nas linhas 28 a 30 é inserido nesse formulário.
A aparência resultante desse modelo é a seguinte:
![]() |
A parte dinâmica da página será inserida na área destacada acima.
6.6. A página [index.xhtml]
![]() |
O projeto sempre exibe a mesma página, a página [index.xhtml] a seguir:
<?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 contém três subfragmentos:
- [form1.xhtml], linhas 10-12;
- [form2.xhtml], linhas 13-15;
- [erreur.xhtml], linhas 16-18.
A presença desses fragmentos em [index.xhtml] é controlada por variáveis booleanas do modelo [Form.java] associado à página. Portanto, ao alterar essas variáveis, a página renderizada fica diferente.
O fragmento [form1.xhtml] tem a seguinte exibição:
O fragmento [form2.xhtml] apresenta a seguinte exibição:
![]() |
O fragmento [erreur.xhtml] é exibido da seguinte forma:
![]() |
6.7. Os beans do projeto
![]() |
A classe do pacote [utils] já foi apresentada: a classe [Messages] é uma classe que facilita a internacionalização das mensagens de um aplicativo. Ela foi abordada no parágrafo 2.8.5.7.
6.7.1. O bean Application
O bean [Application.java] é um bean de escopo application. Vale lembrar que esse tipo de bean serve para armazenar dados somente para leitura e disponíveis para todos os usuários da aplicação. Esse 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 {
// camada de negócios
@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 escopo “application”,
- linhas 13-14: uma referência à interface local da camada [métier] será injetada nele pelo contêiner EJB do servidor de aplicativos. Lembremo-nos da arquitetura do aplicativo:
![]() |
As aplicações JSF, EJB e [Metier] serão executadas na mesma JVM (Máquina Virtual Java). Portanto, a camada [JSF] utilizará a interface local da EJB. É isso. O bean [Application] não contém mais nada. Para ter acesso à camada [métier], os outros beans irão buscá-la nesse bean.
6.7.2. O bean [Erreur]
A classe [Erreur] é a seguinte:
- package beans;
public class Erreur {public Erreur() {// campoprivate String classe;private String message;// construtorpublic Erreur(String classe, String message){this.setClasse(classe);this.message=message;// getters e setters
- linha 9, o nome de uma classe de exceção, caso uma exceção tenha sido lançada,
- linha 10: uma mensagem de erro.
6.7.3. O bean [Form]
Seu código é o seguinte:
package beans;
import java.io.IOException;
...
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// bean de aplicação
@Inject
private Application application;
// cache de sessão
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>();
// modelo
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() {
// armazenamos médicos e clientes no cache
try {
medecins = application.getMetier().getAllMedecins();
clients = application.getMetier().getAllClients();
} catch (Throwable th) {
// registramos o erro
prepareVueErreur(th);
return;
}
// os dicionários
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
hIdentitesClients.put(identite(c), c);
}
}
...
// exibição da visualização
private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean erreurRendered) {
this.form1Rendered = form1Rendered;
this.form2Rendered = form2Rendered;
this.erreurRendered = erreurRendered;
}
// preparação vueErreur
private void prepareVueErreur(Throwable th) {
// é criada a lista de erros
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()));
}
// a visualização dos erros é exibida
setForms(true, false, true);
}
// getters e setters
...
}
- linhas 6-8: a classe [Form] é um bean de nome “form” e de escopo de sessão. Vale lembrar que, nesse caso, a classe deve ser serializável,
- linhas 14-15: o bean “form” possui uma referência ao bean “application”. Essa referência será injetada pelo contêiner de servlets no qual a aplicação é executada (presença da anotação @Inject).
- linhas 17-44: o modelo das páginas [form1.xhtml, form2.xhtml, erreur.xhtml]. A exibição dessas páginas é controlada pelos valores booleanos nas linhas 27-29. Observe-se que, por padrão, é a página [form1.xhtml] que é exibida (linha 27),
- linhas 46-47: o método init é executado logo após a instanciação da classe (presença da anotação @PostConstruct),
- linhas 50-51: solicita-se à camada [métier] a lista de médicos e clientes,
- linhas 59-65: se tudo correu bem, os dicionários de médicos e clientes são criados. Eles são indexados por 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 [erreur.xhtml] é gerado. Esse modelo é a lista de erros da linha 36,
- linhas 78-88: o método [prepareVueErreur] gera a lista de erros a ser exibida. A página [index.xhtml] exibe, então, os fragmentos [form1.xhtml] e [erreur.xhtml] (linha 87).
A página [erreur.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>
Ela utiliza uma tag <p:dataTable> (linhas 12-28) para exibir a lista de erros. Isso resulta em uma página de erro semelhante à seguinte:
![]() |
Vamos agora definir as diferentes fases do ciclo de vida do aplicativo. Para cada ação do usuário, analisaremos as visualizações envolvidas e os manipuladores de eventos.
6.8. Exibição da página inicial
Se tudo correr bem, a primeira página exibida é [form1.xhtml]. Isso resulta na seguinte visualização:
![]() |
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 tela é o componente Primefaces Toolbar. Ele está definido nas linhas 8 a 14. Contém dois grupos de componentes, cada um definido por uma tag <toolbarGroup>, nas linhas 9 a 11 e 12 a 14. Um dos grupos está alinhado à esquerda da barra de ferramentas (linha 9), e 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: a lista suspensa dos médicos, à qual foi adicionado um efeito (effect="fade"),
- linha 6: um comportamento AJAX. Quando houver uma alteração na lista suspensa, o método [Form].hideAgenda (listener="#{form.hideAgenda}") será executado e a área dinâmica :formulário:conteúdo (update=":formulaire:contenu") será atualizada,
- linha 8: inclui um separador na barra de ferramentas,
- linhas 10-12: o campo de entrada da data. Aqui, utiliza-se o calendário do Primefaces. O campo de entrada é somente leitura (readOnlyInputText="true"),
- linha 11: um comportamento AJAX. Quando houver uma alteração na data, o método [Form].hideAgenda será executado e a área dinâmica :formulário:conteúdo será atualizada,
- linha 14: um botão. Ao clicar nele, é executada uma chamada AJAX para o método [Form].getAgenda (), o modelo será então modificado e a resposta do servidor será usada para atualizar a área dinâmica :formulário:conteúdo,
- linha 15: a tag <tooltip> permite associar uma dica de ajuda a um componente. O ID deste último é indicado pelo atributo for da tag tooltip. Aqui (for="resa-agenda") refere-se ao botão da linha 14:
![]() | ![]() |
Esta página é alimentada pelo seguinte modelo:
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// cache da sessão
private List<Medecin> medecins;
private List<Client> clients;
// modelo
private Long idMedecin;
private Date jour = new Date();
// lista de médicos
public List<Medecin> getMedecins() {
return medecins;
}
// lista de clientes
public List<Client> getClients() {
return clients;
}
// agenda
public void getAgenda() {
...
}
- o campo da linha 12 fornece, em leitura e gravação, o valor da lista da linha 4 da página. Na exibição inicial da página, ele define o valor selecionado no menu suspenso. Na exibição inicial, idMedecin é igual a null; portanto, o primeiro médico será selecionado,
- o método das linhas 16-18 gera os itens da lista suspensa de médicos (linha 5 da página). Cada opção gerada terá como rótulo (itemLabel) o título, sobrenome e nome do médico e, como valor (itemValue), o ID do médico,
- o campo da linha 13 alimenta, em leitura/gravação, o campo de entrada da linha 10 da página. Na exibição inicial, é exibida a data de hoje,
- linhas 26-28: o método getAgenda gerencia o clique no botão [Agenda] da linha 14 da página. Ele é praticamente idêntico ao que era na versão JSF:
// bean de aplicação
@Inject
private Application application;
// cache da sessão
private List<Medecin> medecins;
private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
// modelo
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 {
// busca o médico
medecin = hMedecins.get(idMedecin);
// agenda do médico para um determinado dia
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// exibição do formulário 2
setForms(true, true, false);
} catch (Throwable th) {
// visualização dos erros
prepareVueErreur(th);
}
// nenhum horário selecionado no momento
creneauChoisi = null;
}
Não faremos comentários sobre esse código. Isso já foi feito.
6.9. Exibir a agenda de um médico
6.9.1. Visão geral da agenda
Este é o seguinte caso de uso:
![]() |
- em [1], seleciona-se um médico [1] e um dia [2]; em seguida, solicita-se [3] a agenda do médico para o dia escolhido,
- em [4], ela 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>
<!-- menu de contexto -->
<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']}">
<!-- coluna de horários -->
<p:column style="width: 100px">
...
</p:column>
<!-- coluna de clientes -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
<!-- confirmação de exclusão RV -->
<p:confirmDialog id="confirmDialog" message="#{msg['form2.suppression.confirmation']}"
header="#{msg['form2.suppression.message']}" severity="alert" widgetVar="confirmation">
...
</p:confirmDialog>
<!-- mensagem de erro -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
...
</p:dialog>
<!-- gerenciamento da resposta do servidor -->
<script type="text/javascript">
...
}
</script>
</body>
</html>
- linhas 16-26: o elemento principal da página é a tabela <dataTable>, que exibe a agenda do médico,
![]() |
- linhas 12-14: usaremos um menu de contexto para adicionar/excluir um compromisso:
![]() |
- linhas 29-32: uma caixa de confirmação será exibida quando o usuário quiser excluir um compromisso:
![]() |
- linhas 35-37: uma caixa de diálogo será usada para sinalizar um erro:
![]() |
- linhas 40-43: precisaremos inserir um pouco de JavaScript.
6.9.2. A tabela de compromissos
Abordamos aqui o modelo de uma tabela de dados, conforme estudado no parágrafo 5.15, página 327.
Vamos examinar o elemento principal da página, a tabela que exibe a agenda:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- coluna de horários -->
<p:column style="width: 100px">
...
</p:column>
<!-- coluna de clientes -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
A exibição fica assim:
![]() |
Trata-se de uma tabela com duas colunas (linhas 4-6 e 8-10) alimentada pela fonte [Form].getMyDataModel() (value="#{form.myDataModel}"). Apenas uma linha pode ser selecionada por vez (selectionMode="single"). A cada POST, uma referência do elemento selecionado é atribuída a [Form].creneauChoisi (selection="#{form.creneauChoisi}").
Lembre-se de que o método getAgenda inicializou o seguinte campo no modelo:
// modelo
private AgendaMedecinJour agendaMedecinJour;
O modelo da tabela é obtido por meio da chamada do método [Form].getMyDataModel (atributo value da tag <dataTable>) a seguir:
// o modelo do dataTable
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> {
// fabricantes
public MyDataModel() {
}
public MyDataModel(CreneauMedecinJour[] creneauxMedecinJour) {
super(creneauxMedecinJour);
}
@Override
public Object getRowKey(CreneauMedecinJour creneauMedecinJour) {
return creneauMedecinJour.getCreneau().getId();
}
@Override
public CreneauMedecinJour getRowData(String rowKey) {
// lista de horários
CreneauMedecinJour[] creneauxMedecinJour = (CreneauMedecinJour[]) getWrappedData();
// a chave é um inteiro longo
long key = Long.parseLong(rowKey);
// buscando o horário selecionado
for (CreneauMedecinJour creneauMedecinJour : creneauxMedecinJour) {
if (creneauMedecinJour.getCreneau().getId().longValue() == key) {
return creneauMedecinJour;
}
}
// nada
return null;
}
}
- linha 7: a classe [MyDataModel] é o modelo da tag <p:dataTable>. O objetivo dessa classe é estabelecer a ligação entre o elemento rowkey que é enviado e o elemento associado a essa linha,
- linha 7: a classe implementa a interface [SelectableDataModel] por meio da classe [ArrayDataModel]. Isso significa que o parâmetro do construtor é um array. É esse array que alimenta a tag <dataTable>. Aqui, cada linha do array será associada a um elemento do tipo [CreneauMedecinJour],
- linhas 13-15: o construtor passa seu parâmetro para sua classe pai,
- linhas 18-20: cada linha da matriz corresponde a um horário e será identificada pelo ID do horário (linha 19). É esse ID que será enviado ao servidor,
- linha 23: o código que será executado no servidor quando o ID de um intervalo de horário for enviado. O objetivo deste método é obter a referência do objeto [CreneauMedecinJour] associado a esse id. Essa 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 se deseja adicionar ou excluir.
6.9.3. A coluna dos horários
![]() |
A coluna dos intervalos horários é obtida com o código a seguir:
<p:dataTable id="agenda" value="#{form.myDataModel}" var="creneauMedecinJour" style="width: 800px"
selectionMode="single" selection="#{form.creneauChoisi}" emptyMessage="#{msg['form2.emtyMessage']}">
<!-- coluna dos horários -->
<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>
<!-- coluna de clientes -->
<p:column style="width: 300px">
...
</p:column>
</p:dataTable>
- linhas 5-7: o cabeçalho da coluna,
- linhas 8-15: o elemento atual da coluna. Observe, na linha 9, o uso da tag <h:outputFormat>, que permite formatar os elementos a serem exibidos. O parâmetro value indica a sequência de caracteres a ser exibida. A notação {i,type,format} designa o parâmetro nº i, o tipo desse parâmetro e seu formato. Aqui há 4 parâmetros numerados de 0 a 3; o tipo deles é numérico e serão exibidos com dois dígitos,
- linhas 10-13: os quatro parâmetros esperados pela tag <h:outputFormat>.
6.9.4. A coluna dos clientes
![]() |
A coluna dos clientes é obtida com 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']}">
<!-- coluna de horários -->
...
<!-- coluna de clientes -->
<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 há um compromisso para o horário. Nesse caso, são exibidos o título, o nome e o sobrenome do cliente para quem esse compromisso foi agendado,
- linhas 14-16: outro fragmento sobre o qual voltaremos a falar.
6.10. Exclusão de um compromisso
A exclusão de um compromisso segue a seguinte sequência:
![]() |
![]() |
A visualização relacionada a essa ação é a seguinte:
<!-- menu de contexto -->
<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>
<!-- confirmação de exclusão 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 vinculado à tabela de dados (atributo for). Ele possui duas opções [1]:
![]() |
- linha 4: a opção [Supprimer] aciona a exibição da caixa de diálogo [2] das linhas 13 a 20,
- linha 15: clicar em [Oui] aciona a execução de [Form.action], que excluirá o compromisso. Normalmente, o menu de contexto não deveria exibir a opção [Supprimer] se o item selecionado não tiver um compromisso, nem a opção [Réserver] se o item selecionado tiver um compromisso. Não conseguimos fazer com que o menu de contexto fosse tão sutil assim. Conseguimos isso para o primeiro elemento selecionado, mas depois percebemos que o menu de contexto mantém a configuração definida para essa primeira seleção. Assim, ele se torna incorreto. Por isso, mantivemos as duas opções e decidimos fornecer um aviso ao usuário caso ele exclua um elemento sem compromisso,
- linha 16: o atributo oncomplete, que permite definir um código JavaScript a ser executado após a execução da chamada AJAX. Esse código será o seguinte:
<!-- mensagem de erro -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
<h:outputText value="#{form.msgErreur}" />
</p:dialog>
<!-- gestão da resposta do servidor -->
<script type="text/javascript">
function handleRequest(xhr, status, args) {
// erro?
if(args.erreur) {
dlgErreur.show();
}
}
</script>
- linha 10: o código JavaScript verifica se o dicionário args possui o atributo erreur. Se sim, ele exibe a caixa de diálogo da linha 2 (atributo widgetVar). Essa caixa exibe o modelo [Form].msgErreur.
Vejamos o código executado para gerenciar a exclusã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 sua execução, o campo action terá recebido o valor 'supprimer'.
O método [action] é o seguinte:
// ação em RV
public void action() {
// dependendo da ação desejada
if (action.equals("supprimer")) {
supprimer();
}
...
}
public void supprimer() {
// é preciso fazer alguma coisa?
Rv rv = creneauChoisi.getRv();
if (rv == null) {
signalerActionIncorrecte();
return;
}
try {
// exclusão de um compromisso
application.getMetier().supprimerRv(rv);
// atualiza-se a agenda
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// exibe o form2
setForms(true, true, false);
} catch (Throwable th) {
// visualização de erros
prepareVueErreur(th);
}
// zerar o intervalo selecionado
creneauChoisi = null;
}
- linha 4: se a ação for 'excluir', executa-se o método [supprimer],
- linha 12: recupera-se o compromisso do intervalo selecionado. Vale lembrar que [creneauChoisi] foi inicializado pela referência do elemento [CreneauMedecinJour] selecionado;
- se esse compromisso existir, ele é excluído (linha 19), a agenda é regenerada (linha 21) e, em seguida, exibida novamente (linha 23),
- se a exclusão falhar, exibe-se a página de erros (linha 26),
- se o elemento selecionado não tiver nenhum compromisso (linha 13), então estamos na situação em que o usuário clicou em [Supprimer] em um intervalo de tempo que não contém nenhum compromisso. Este erro é sinalizado:
![]() |
O método [signalerActionIncorrecte] é o seguinte:
// alertar sobre uma ação incorreta
private void signalerActionIncorrecte() {
// zerar o intervalo selecionado
creneauChoisi = null;
// erro
msgErreur = Messages.getMessage(null, "form2.erreurAction", null).getSummary();
RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
}
- linha 4: remove-se a seleção,
- linha 6: gera-se uma mensagem de erro internacionalizada,
- linha 7: adiciona-se ao dicionário args da chamada AJAX o atributo ('erro', true).
Voltemos ao código XHTML do botão [Oui]:
<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 JavaScript handleRequest é executado:
<!-- mensagem de erro -->
<p:dialog header="#{msg['form2.erreur']}" widgetVar="dlgErreur" height="100" >
<h:outputText value="#{form.msgErreur}" />
</p:dialog>
<!-- gerenciamento da resposta do servidor -->
<script type="text/javascript">
function handleRequest(xhr, status, args) {
// erro?
if(args.erreur) {
dlgErreur.show();
}
}
</script>
- linha 10: verifica-se se o dicionário args possui o atributo denominado 'erreur'. Se sim, a caixa de diálogo da linha 2 é exibida,
- linha 3: ela exibe a mensagem de erro gerada pelo modelo.
6.11. Agendamento de consultas
A marcação de consulta corresponde à seguinte sequência:
![]() |
A visualização envolvida nessa ação é a seguinte:
<!-- menu de contexto -->
<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']}">
<!-- coluna de horários -->
<p:column style="width: 100px">
...
</p:column>
<!-- coluna de clientes -->
<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: exibem o seguinte:
![]() |
- linha 21: a exibição ocorre se não houver compromisso, se houver uma seleção e se o ID do horário escolhido corresponder ao do elemento atual da tabela. Se essa condição não for definida, o fragmento será exibido para todos os horários;
- linha 22: o campo de entrada será um campo de entrada assistida. Supõe-se aqui que possa haver muitos clientes,
- linhas 24-26: o link [Valider],
- linhas 28-30: o link [Annuler].
O campo de entrada assistida é gerado pelo código a seguir:
<p:autoComplete completeMethod="#{form.completeClients}" value="#{form.identiteClient}" size="30"/>
O método [Form].completeClients é responsável por apresentar sugestões ao usuário com base nos caracteres digitados no campo de entrada:
![]() |
As propostas têm o formato [Nom prénom titre]. O código do método [Form].completeClients é o seguinte:
// o método de autocompletar texto
public List<String> completeClients(String query) {
List<String> identites = new ArrayList<String>();
// buscamos os clientes que correspondem
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 sequência de caracteres digitada pelo usuário,
- linha 3: a lista de sugestões. Inicialmente, uma lista vazia,
- linhas 5-10: constroem-se as identidades [Nom prénom titre] dos clientes. Se uma identidade começar por query (linha 7), ela é incluída na lista de sugestões (linha 8).
6.12. Validação de um compromisso
A validação de um compromisso corresponde à seguinte sequência:
![]() |
O código do link [Valider] é 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>
Portanto, é o método [Form].action() que irá gerenciar esse evento. Enquanto isso, o modelo [Form].action terá recebido a string 'validar'. O código é o seguinte:
// bean Application
@Inject
private Application application;
// cache da sessão
...
private Map<String, Client> hIdentitesClients = new HashMap<String, Client>();
// modelo
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);
}
}
// ação em RV
public void action() {
// de acordo com a ação desejada
...
if (action.equals("valider")) {
validerResa();
}
}
// confirmação da reserva
public void validerResa() {
// validação da reserva
try {
// o cliente existe?
Boolean erreur = !hIdentitesClients.containsKey(identiteClient);
if (erreur) {
msgErreur = Messages.getMessage(null, "form2.erreurClient", new Object[]{identiteClient}).getSummary();
RequestContext.getCurrentInstance().addCallbackParam("erreur", true);
return;
}
// adiciona-se a consulta
application.getMetier().ajouterRv(jour, creneauChoisi.getCreneau(), hIdentitesClients.get(identiteClient));
// atualiza-se a agenda
agendaMedecinJour = application.getMetier().getAgendaMedecinJour(medecin, jour);
// exibe-se o form2
setForms(true, true, false);
} catch (Throwable th) {
// visualização de erros
prepareVueErreur(th);
}
// Zerar o horário selecionado
creneauChoisi = null;
// zerar dados do cliente
identiteClient = null;
}
- linhas 33-35: devido ao valor do campo action, o método [validerResa] será executado,
- linha 43: verifica-se primeiro se o cliente existe. De fato, na área de preenchimento assistido, o usuário pôde inserir um valor diretamente, sem utilizar as sugestões que lhe foram oferecidas. O preenchimento assistido está associado ao modelo [Form].identiteClient. Verifica-se, portanto, se essa identidade existe no dicionário identitesClients, criado na instanciação do modelo (linha 20). Esse dicionário associa a uma identidade de cliente do tipo [Nom prénom titre] o próprio cliente (linha 25),
- linha 44: se o cliente não existir, é retornado um erro ao navegador,
- linha 45: uma mensagem de erro internacionalizada,
- linha 46: adiciona-se o atributo ('erro', 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 [Valider] possui um atributo oncomplete. É esse atributo que fará com que a mensagem de erro seja exibida, de acordo com uma técnica já abordada.
- linha 50: solicita-se à camada [métier] que adicione um compromisso para o dia selecionado (jour), no horário selecionado (creneauChoisi.getCreneau()) e para o cliente selecionado (hIdentitesClients.get(identiteClient)),
- linha 52: solicita-se à camada [métier] que atualize a agenda do médico. Veremos a consulta adicionada, além de todas as alterações que outros usuários do aplicativo possam ter feito,
- linha 54: exibe-se novamente a agenda [form2.xhtml],
- linha 57: exibe-se a página de erro caso ocorra algum erro.
6.13. Cancelamento de uma consulta
Isso corresponde à seguinte sequência:
![]() |
O botão [Annuler] 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:
// ação em RV
public void action() {
// de acordo com a ação desejada
...
if (action.equals("annuler")) {
annulerRv();
}
}
// cancelamento da marcação de consulta
public void annulerRv() {
// exibe o formulário 2
setForms(true, true, false);
// zerar o horário selecionado
creneauChoisi = null;
// zerar dados do cliente
identiteClient = null;
}
6.14. Navegação no calendário
A barra de ferramentas permite navegar pelo calendário:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Embora não apareça nas capturas de tela acima, a agenda é atualizada com os compromissos do novo dia selecionado.
As tags dos três botões em questão são as seguintes no [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, [Form].today são os seguintes:
private Date jour = new Date();
public void getPreviousAgenda() {
// passa para o dia anterior
Calendar cal = Calendar.getInstance();
cal.setTime(jour);
cal.add(Calendar.DAY_OF_YEAR, -1);
jour = cal.getTime();
// agenda
if (form2Rendered) {
getAgenda();
}
}
public void getNextAgenda() {
// passa para o dia seguinte
Calendar cal = Calendar.getInstance();
cal.setTime(jour);
cal.add(Calendar.DAY_OF_YEAR, 1);
jour = cal.getTime();
// agenda
if (form2Rendered) {
getAgenda();
}
}
// agenda de hoje
public void today() {
jour = new Date();
// agenda
if (form2Rendered) {
getAgenda();
}
}
- linha 1: o dia de exibição da agenda,
- linha 5: utiliza-se um calendário,
- linha 6: que é inicializado com o dia atual da agenda,
- linha 7: subtraímos um dia do calendário,
- linha 8: e reinicializamos com o dia de exibição da agenda,
- linha 11: reexibe-se a agenda caso ela esteja em exibição. De fato, o usuário pode utilizar a barra de ferramentas mesmo que a agenda não esteja em exibição.
Os demais métodos são análogos.
6.15. Alteração do idioma de exibição
A mudança de idioma é feita pelo botão de menu da barra de ferramentas:
![]() |
![]() |
As tags do botão de 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";
// atualizar a página
redirect();
}
public void setEnglishLocale() {
locale = "en";
// recarregar a página
redirect();
}
private void redirect() {
// redirecionando o cliente para o 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 das linhas 3 e 9 limitam-se a inicializar o campo locale da linha 1 e, em seguida, redirecionam o navegador do cliente para a mesma página. Um redirecionamento é uma resposta na qual o servidor solicita ao navegador que carregue outra página. O navegador então realiza um GET para essa nova página.
- linha 17: [ExternalContext] é uma classe JSF que permite acessar o servlet em execução,
- linha 19: é feita a redirecionamento. O parâmetro do método redirect é o URL da página para a qual o navegador do cliente deve ser redirecionado. Aqui, queremos redirecionar para [/mv-rdvmedecins-pf], que é o nome do nosso aplicativo:
![]() |
o método [getRequestContextPath] permite obter esse nome. Portanto, a página inicial [index.xhtml] do nosso aplicativo será carregada. Essa página está associada ao modelo [Form], com escopo de sessão. Esse modelo gerencia três valores booleanos que controlam a aparência da página [index.xhtml]:
private Boolean form1Rendered = true;
private Boolean form2Rendered = false;
private Boolean erreurRendered = false;
Como o modelo tem escopo de sessão, essas três variáveis booleanas mantiveram seus valores. A página [index.xhtml] será, portanto, exibida no estado em que se encontrava antes do redirecionamento. Essa página é formatada com o facelet [layout.xhtml] a seguir:
<?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 da linha 9 define o idioma de exibição da página por meio do atributo locale. A página será exibida em francês ou em inglês, conforme o caso. Agora, por que ocorre um redirecionamento? Voltemos às tags das 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>
Elas foram escritas inicialmente para atualizar, por meio de uma chamada AJAX, o campo de ID do formulário (atributo update das linhas 7 e 8). Mas, nos testes, a mudança de idioma não funcionava sempre. Daí a redirecionamento para resolver esse problema. Talvez também fosse possível definir o atributo ajax='false' nas tags para provocar uma recarga da página. Isso teria evitado o redirecionamento.
6.16. Atualização das listas
Isso corresponde à seguinte ação:
![]() |
A tag associada à opção [Rafraîchir] é 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() {
// atualizando as listas
init();
}
O método init é executado imediatamente após a criação do bean [Form]. Seu objetivo é armazenar em cache os dados do banco de dados no modelo:
// bean Application
@Inject
private Application application;
// cache da sessão
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() {
// armazenamos os médicos e os clientes no cache
try {
medecins = application.getMetier().getAllMedecins();
clients = application.getMetier().getAllClients();
} catch (Throwable th) {
...
}
...
// os dicionários
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 cria as listas e os dicionários das linhas 5 a 9. A desvantagem dessa técnica é que esses elementos não levam mais em conta as alterações no banco de dados (adição de um cliente, de um médico, etc.). O método refresh força a reconstrução dessas listas e dicionários. Portanto, ele será utilizado sempre que for feita uma alteração no banco de dados, como a adição de um novo cliente, por exemplo.
6.17. Conclusion
Vamos relembrar a arquitetura do aplicativo que acabamos de construir:
![]() |
Nos baseamos amplamente na versão JSF2 já construída:
- as camadas [métier], [DAO] e [JPA] foram mantidas,
- os beans [Application] e [Form] da camada web foram mantidos, mas novas funcionalidades foram adicionadas a eles devido ao aprimoramento da interface do usuário,
- a interface do usuário passou por uma profunda reformulação. Ela está, notavelmente, mais rica em funcionalidades e mais intuitiva.
A migração do JSF para o Primefaces na construção da interface web requer certa experiência, pois, no início, ficamos um pouco perdidos diante do grande número de componentes disponíveis e, no fim das contas, não sabemos muito bem quais usar. É preciso, então, analisar a ergonomia desejada para a interface.
6.18. Testes no Eclipse
Assim como fizemos nas versões anteriores do aplicativo de exemplo, mostramos como testar esta versão 03 com o Eclipse. Primeiramente, importamos para o Eclipse os projetos Maven do exemplo 03 [1]:
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: as camadas [DAO] e [JPA],
- [mv-rdvmedecins-ejb-metier]: a camada [métier],
- [mv-rdvmedecins-pf]: a camada [web] implementada com JSF e Primefaces,
- [mv-rdvmedecins-app]: o projeto pai do projeto corporativo [mv-rdvmedecins-app-ear]. Ao importar o projeto pai, o projeto filho é importado automaticamente,
- no [2], executa-se o projeto corporativo [mv-rdvmedecins-app-ear],
![]() |
- em [3], seleciona-se o servidor Glassfish,
- no [4], na aba [Servers], o aplicativo foi implantado. Ele não é executado automaticamente. É preciso acessá-la em um navegador:
![]() |
















































