9. Aplicativo de exemplo 05: rdvmedecins-pfm-ejb
Vale lembrar a estrutura do aplicativo de exemplo 01 JSF / EJB desenvolvido para o servidor Glassfish:
![]() |
Não alteramos nada nessa arquitetura, exceto a camada web, que será implementada aqui com o auxílio do JSF, do PrimeFaces e do PrimeFaces Mobile. O navegador de destino será o de um celular.
![]() |
Desenvolvemos dois aplicativos com o Glassfish:
- a aplicação 01 utilizava JSF / EJB e possuía uma interface bastante simples,
- a aplicação 03 utilizava PF / EJB e possuía uma interface rica.
Devido ao número reduzido de componentes disponíveis no PrimeFaces Mobile, vamos voltar à interface simples do aplicativo 01. Além disso, as visualizações deverão ser capazes de se adaptar ao tamanho reduzido das telas dos dispositivos móveis.
9.1. As visualizações
Para dar uma ideia, apresentamos algumas visualizações do aplicativo executado no simulador de um iPhone 4:
![]() |
- em [1], a página inicial. Observe que, desta vez, foi necessário informar o nome da máquina (também é possível informar seu endereço IP), pois com a máquina localhost não exibia nada,
- em [2], a lista de médicos,
- em [3], a data desejada para a consulta,
- em [4], o botão para consultar a agenda do dia,
- em [5], a nova visualização exibe os horários disponíveis do médico,
- em [6], uma série de botões para navegar pelo calendário,
- em [7], uma mensagem para lembrar o nome do médico e o dia,
- em [8], um horário disponível para agendar. Fazemos isso,
![]() |
- em [9], a tela de seleção do cliente,
- em [10], uma mensagem lembrando o médico, o dia e o horário da consulta,
- em [11], a lista de clientes,
- em [12], o botão de confirmação,
- em [13], a confirmação nos leva de volta à agenda,
- em [14], o horário ocupado agora está reservado. Agora vamos excluir a reserva,
![]() |
- em [15], permanecemos na mesma tela,
- mas em [16], o compromisso foi excluído,
É possível, na página inicial, alterar o idioma: [17], [18], [19]:
![]() |
Por fim, foi prevista uma visualização de erros:
![]() |
9.2. O projeto NetBeans
O projeto NetBeans é o seguinte:
![]() |
- [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-pfmobile]: projeto da camada [web] / PrimeFaces Mobile – novo,
- [mv-rdvmedecins-pfmobile-app-ear]: projeto corporativo para implantar o aplicativo no servidor Glassfish – novo.
9.3. 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-pfmobile] 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>
...
<groupId>istia.st</groupId>
<artifactId>mv-rdvmedecins-pfmobile-app-ear</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>ear</packaging>
<name>mv-rdvmedecins-pfmobile-app-ear</name>
...
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-ejb-dao-jpa</artifactId>
<version>${project.version}</version>
<type>ejb</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-ejb-metier</artifactId>
<version>${project.version}</version>
<type>ejb</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-rdvmedecins-pfmobile</artifactId>
<version>${project.version}</version>
<type>war</type>
</dependency>
</dependencies>
</project>
- linhas 6-9: o artefato Maven do projeto corporativo,
- linhas 14-33: as três dependências do projeto. Observe bem o tipo de cada uma delas (linhas 19, 25, 31).
Para executar o aplicativo web, será necessário executar este projeto corporativo.
9.4. O projeto web Primefaces Mobile
O projeto web Primefaces Mobile é o seguinte:
![]() |
- em [1], as páginas do projeto. A página [index.xhtml] é a única página do projeto. Ela contém cinco visualizações: [vue1.xhtml], [vue2.xhtml], [vue3.xhtml], [vueErreurs.xhtml] e [config.xhtml],
- em [2], os beans Java. O bean [Application] tem escopo application, e o bean [Form] tem escopo session. A classe [Erreur] encapsula um erro,
- em [3], os arquivos de mensagens para internacionalização,
- em [4], as dependências. O projeto web depende do projeto EJB da camada [DAO], do projeto EJB da camada [métier] e do Primefaces Mobile para a camada [web].
9.5. 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.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
<error-page>
<error-code>500</error-code>
<location>/faces/exception.xhtml</location>
</error-page>
<error-page>
<exception-type>Exception</exception-type>
<location>/faces/exception.xhtml</location>
</error-page>
</web-app>
Observe, na linha 26, 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>
<default-render-kit-id>PRIMEFACES_MOBILE</default-render-kit-id>
</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>
[messages_fr.properties]: o arquivo de mensagens em francês
# página
page.titre=Les M\u00e9decins Associ\u00e9s
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
# visualização1
vue1.header=Les M\u00e9decins Associ\u00e9s - R\u00e9servations
# formulário 1
form1.titre=R\u00e9servations
form1.medecin=M\u00e9decin
form1.jour=Jour (jj/mm/aaaa)
form1.date.requise=La date est n\u00e9cessaire
form1.date.invalide=La date est invalide
form1.date.invalide_detail=La date est invalide
form1.agenda=Agenda
form1.options=Options
# formulário 2
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
form2.retour=Retour
form2.supprimer=Supprimer
form2.reserver=R\u00e9server
form2.precedent=Jour pr\u00e9c\u00e9dent
form2.suivant=Jour suivant
form2.today=Aujourd'hui
# formulário 3
form3.client=Client
form3.valider=Valider
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.retour=Retour
# erro
erreur.titre=Une erreur s'est produite.
# configuração
config.retour=Retour
config.titre=Configuration
config.langue=Langue
config.langue.francais=Fran\u00e7ais
config.langue.anglais=Anglais
config.valider=Valider
#exceção
exception.titre=Application indisponible. Veuillez recommencer ult\u00e9rieurement.
[messages_en.properties]: o arquivo de mensagens em inglês
# página
page.titre=The Associated Doctors
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
# visualização1
vue1.header=The Associated Doctors - Reservations
# formulário 1
form1.titre=Reservations
form1.medecin=Doctor
form1.jour=day (dd/mm/yyyy)
form1.date.requise=The date is necessary
form1.date.invalide=Invalid date
form1.date.invalide_detail=invalid date
form1.agenda=Diary
form1.options=Options
# formulário 2
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
form2.retour=Back
form2.supprimer=Delete
form2.reserver=Reserve
form2.precedent=Previous Day
form2.suivant=Next Day
form2.today=Today
# formulário 3
form3.client=Patient
form3.valider=Validate
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.retour=Back
# erro
erreur.titre=Some error happened
# configuração
config.retour=Back
config.titre=Configuration
config.langue=Language
config.langue.francais=French
config.langue.anglais=English
config.valider=Validate
#exceção
exception.titre=Application not available. Please try again later.
9.6. A página [index.xhtml]
![]() |
O projeto sempre exibe a mesma página, a página [index.xhtml] a seguir:
<f:view xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:pm="http://primefaces.org/mobile"
contentType="text/html"
locale="#{form.locale}">
<pm:page title="#{msg['page.titre']}">
<pm:view id="vue1">
<ui:fragment rendered="#{form.form1Rendered}">
<ui:include src="vue1.xhtml"/>
</ui:fragment>
<ui:fragment rendered="#{form.erreurInit}">
<ui:include src="vueErreurs.xhtml"/>
</ui:fragment>
</pm:view>
<pm:view id="vue2">
<ui:fragment rendered="#{form.form2Rendered}">
<ui:include src="vue2.xhtml"/>
</ui:fragment>
</pm:view>
<pm:view id="vue3">
<ui:fragment rendered="#{form.form3Rendered}">
<ui:include src="vue3.xhtml"/>
</ui:fragment>
</pm:view>
<pm:view id="vueErreurs">
<ui:fragment rendered="#{form.erreurRendered}">
<ui:include src="vueErreurs.xhtml"/>
</ui:fragment>
</pm:view>
<pm:view id="config">
<ui:include src="config.xhtml"/>
</pm:view>
</pm:page>
</f:view>
- linha 8: a página está internacionalizada (atributo locale),
- linha 10: a página contém cinco visualizações: vista1 na linha 11, vista2 na linha 19, vista3 na linha 24, vueErreurs na linha 29, config na linha 34. Em determinado momento, apenas uma dessas visualizações fica visível. Ao iniciar o aplicativo, é exibida a visualização vista1. Aqui, encontramos a seguinte dificuldade: se a inicialização do aplicativo ocorreu normalmente, deve-se exibir [vue1.xhtml]; caso contrário, deve-se exibir [vueErreurs.xhtml]. Resolvemos o problema fazendo com que o conteúdo da vista vue1 fosse gerenciado pelo modelo, que atribui valores às variáveis booleanas [Form].form1rendered (linha 12) e [Form].erreurInit (linha 15) para definir o conteúdo da vista1 (linha 11),
Em um simulador, a vista [vue1.xhtml] tem a renderização [1], a vista [vue2.xhtml] tem a renderização [2], e a vista [vue3.xhtml] tem a renderização [3]:
![]() |
a visualização [vueErreurs.xhtml] e a renderização [4], a visualização [config.xhtml] e a renderização [5]:
![]() |
9.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.
9.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 PFM, EJB e [Metier] serão executadas na mesma JVM (Java Virtual Machine). Portanto, a camada [PFM] 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.
9.7.2. O bean [Erreur]
A classe [Erreur] é a seguinte:
package beans;
public class Erreur {
public Erreur() {
}
// campo
private String classe;
private String message;
// construtor
public 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.
9.7.3. O bean [Form]
Seu código é o seguinte:
package beans;
...
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// bean de aplicação
@Inject
private Application application;
private IMetierLocal metier;
// 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>();
// modelo
private Long idMedecin;
private Date jour = new Date();
private String strJour;
private Boolean form1Rendered;
private Boolean form2Rendered;
private Boolean form3Rendered;
private Boolean erreurRendered;
private String form2Titre;
private String form3Titre;
private AgendaMedecinJour agendaMedecinJour;
private Long idCreneauChoisi;
private Medecin medecin;
private Long idClient;
private CreneauMedecinJour creneauChoisi;
private List<Erreur> erreurs;
private Boolean erreurInit = false;
private String action;
private String locale = "fr";
private String msgErreurDate = "";
private SimpleDateFormat dateFormatter;
private Boolean erreurDate;
@PostConstruct
private void init() {
System.out.println("init");
// inicialmente sem erros
erreurInit = false;
// formatação de datas
dateFormatter = new SimpleDateFormat(Messages.getMessage(null, "format.date", null).getSummary());
dateFormatter.setLenient(false);
// o dia atual
strJour = dateFormatter.format(jour);
// recuperamos a camada de negócios
metier = application.getMetier();
// armazenamos os médicos e os clientes no cache
try {
medecins = metier.getAllMedecins();
clients = metier.getAllClients();
} catch (Throwable th) {
// registramos o erro
erreurInit = true;
prepareVueErreur(th);
return;
}
// verificação das listas
if (medecins.size() == 0) {
// registra-se o erro
erreurInit = true;
erreurs = new ArrayList<Erreur>();
erreurs.add(new Erreur("", "La liste des médecins est vide"));
}
if (clients.size() == 0) {
// registra-se o erro
erreurInit = true;
erreurs = new ArrayList<Erreur>();
erreurs.add(new Erreur("", "La liste des clients est vide"));
}
// erro?
if (erreurInit) {
// a tela de erros é exibida
setForms(false, false, false, true);
return;
}
// os dicionários
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
}
// a visualização 1
setForms(true, false, false, false);
}
// exibição da visualização
private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
this.form1Rendered = form1Rendered;
this.form2Rendered = form2Rendered;
this.form3Rendered = form3Rendered;
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(false, false, false, true);
}
// getters e setters
..
}
- linhas 5-7: 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 12-13: 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 24-27: controlam a exibição das visualizações vue1 (linha 24), vue2 (linha 25), vue3 (linha 26) e vueErreurs (linha 27),
- linhas 43-44: o método init é executado logo após a instanciação da classe (presença da anotação @PostConstruct),
- linhas 49-50: gerenciam o formato das datas. O PrimeFaces Mobile não oferece um calendário. Além disso, os validadores JSF não podem ser utilizados em uma página PFM. Portanto, teremos que gerenciar manualmente a inserção da data da agenda,
- linha 49: define o formato das datas. Esse formato é obtido nos arquivos internacionalizados:
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
- linha 50: indica-se que não se deve ser “laxiste” em relação às datas. Se isso não for feito, uma entrada como 32/12/2011, que é incorreta, será considerada como a data válida 01/01/2012,
- linha 54: obtém-se uma referência à camada [métier] por meio do bean [Application],
- linhas 57-58: solicita-se à camada [métier] a lista de médicos e clientes,
- linhas 66-91: 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 visualização [vue1.xhtml] será exibida (linha 93),
- linha 59: em caso de erro, o modelo da página [vueErreurs.xhtml] é gerado. Esse modelo corresponde à lista de erros da linha 35,
- linhas 105-115: o método [prepareVueErreur] gera a lista de erros a ser exibida. A página [index.xhtml] exibe, então, a visualização [vueErreurs.xhtml] (linha 114).
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:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- Visão de erros -->
<pm:header title="#{msg['page.titre']}" swatch="b">
<f:facet name="left">
<p:button icon="home" value=" " href="#vue1?reverse=true" />
</f:facet>
</pm:header>
<pm:content>
<div align="center">
<h1><h:outputText value="#{msg['erreur.titre']}" style="color: blue"/></h1>
</div>
<p:dataList value="#{form.erreurs}" var="erreur">
<b>#{erreur.classe}</b> : <i>#{erreur.message}</i>
</p:dataList>
</pm:content>
</html>
Ela utiliza uma tag <p:dataList> (linhas 21-23) para exibir a lista de erros. O botão na linha 13 permite retornar à visualização vue1.
![]() |
- o botão [1] é gerado pela linha 13. O atributo icon define o ícone do botão. O botão retorna à visualização vue1 (atributo href),
- o cabeçalho [2] é gerado pelas linhas 11-15,
- o título [3] é gerado pela linha 18,
- o texto [4] é gerado pelo modelo #{erreur.classe} da linha 22,
- o texto [5] é gerado pelo modelo #{erreur.message} da linha 22.
Agora, vamos 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 que ocorrem nelas.
9.8. A exibição da página inicial
Se tudo correr bem, a primeira visualização exibida é [vue1.xhtml]. Isso resulta na seguinte visualização:
![]() |
O código da visualização [vue1.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:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- Visualização 1 -->
<pm:header title="#{msg['page.titre']}" swatch="b">
<f:facet name="left">
<p:button icon="gear" value=" " href="#config" />
</f:facet>
</pm:header>
<pm:content>
<h:form id="form1">
<div align="center">
<h1><h:outputText value="#{msg['form1.titre']}" style="color: blue"/></h1>
</div>
<pm:field>
<h:outputLabel value="#{msg['form1.medecin']}" for="choixMedecin"/>
<h:selectOneMenu id="choixMedecin" value="#{form.idMedecin}">
<f:selectItems value="#{form.medecins}" var="medecin" itemLabel="#{medecin.titre} #{medecin.prenom} #{medecin.nom}" itemValue="#{medecin.id}"/>
</h:selectOneMenu>
</pm:field>
<pm:field>
<h:outputLabel value="#{msg['form1.jour']}" for="jour"/>
<p:inputText id="jour" value="#{form.strJour}"/>
<ui:fragment rendered="#{form.erreurDate}">
<p:spacer width="50px"/>
<h:outputText id="msgErreurDate" value="#{form.msgErreurDate}" style="color: red"/>
</ui:fragment>
</pm:field>
<p:commandButton value="#{msg['form1.agenda']}" update=":form1, :vue2, :vueErreurs" action="#{form.getAgenda}" />
</h:form>
</pm:content>
</html>
- linhas 11-15: geram o cabeçalho [1],
- linha 13: gera o botão [2]. Ao clicar nele, a visualização de configuração é exibida (atributo href),
- linha 19: gera o título [3],
- linhas 21-26: geram a lista suspensa de médicos [4],
- linhas 27-34: geram o campo de inserção de data [5]. Essa inserção é feita com uma tag <p:inputText> sem validador. A validação da data será feita no servidor. Se a data estiver incorreta, o servidor exibirá uma mensagem de erro pelas linhas 30-34,
![]() |
- linha 35: o botão que valida o formulário. Ele atualiza três campos: form1 (o formulário na vue1), vue2 e vueErreurs. De fato, se a data for inválida, é o form1 que deve ser atualizado. Se a data estiver correta, é a vue2 que deve ser atualizada. Por fim, se ocorrer uma exceção (por exemplo, conexão com o banco de dados interrompida), é vueErreurs que deve ser exibida. Pode-se ficar tentado a colocar vue1 no lugar de form1 (atualizando toda a vista). Nesse caso, o aplicativo apresenta erros.
Essa vista está associada ao seguinte modelo:
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// Bean de aplicação
@Inject
private Application application;
private IMetierLocal metier;
// cache da sessão
private List<Medecin> medecins;
private List<Client> clients;
// modelo
private Long idMedecin;
private Date jour = new Date();
private String strJour;
private Boolean form1Rendered;
private Boolean form2Rendered;
private Boolean form3Rendered;
private Boolean erreurRendered;
private String msgErreurDate = "";
private Boolean erreurDate;
// lista de médicos
public List<Medecin> getMedecins() {
return medecins;
}
// agenda
public String getAgenda() {
...
}
}
- o campo da linha 16 alimenta, em leitura e gravação, o valor da lista da linha 23 da página. Na exibição inicial da página, ele define o valor selecionado no combo,
- o método das linhas 27-29 gera os itens da lista suspensa de médicos (linha 24 da visualização). 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 18 alimenta, em leitura/gravação, o campo de entrada da linha 29 da página,
- linhas 32-34: o método getAgenda gerencia o clique no botão [Agenda] da linha 35 da página. Seu código é o seguinte:
// agenda
public String getAgenda() {
try {
// verificação do dia
jour = dateFormatter.parse(strJour);
// sem erros
erreurDate=false;
msgErreurDate = "";
// criando a agenda
return getAgenda(jour);
} catch (ParseException ex) {
// mensagem de erro
erreurDate=true;
msgErreurDate = Messages.getMessage(null, "form1.date.invalide", null).getSummary();
// visualização 1
setForms(true, false, false, false);
return "pm:vue1";
}
}
- o método começa verificando a validade da data inserida pelo usuário,
- linha 5: a data é parsée, de acordo com o formato de data inicializado pelo método init na instanciação do modelo,
- linha 11: se ocorrer uma exceção, é gerado um erro (linha 13), é criada uma mensagem de erro internacionalizada (linha 14), a vista vue1 é preparada (linha 16) e a vista vue1 é exibida (linha 17),
- linha 10: se a data estiver correta, executa-se o seguinte método:
// agenda
public String getAgenda(Date jour) {
// nenhum horário selecionado até o momento
creneauChoisi = null;
try {
// recuperando o médico selecionado
medecin = hMedecins.get(idMedecin);
// título do formulário 2
form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
// a agenda do médico para um determinado dia
agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
// exibindo a visualização 2
setForms(false, true, false, false);
return "pm:vue2";
} catch (Throwable th) {
System.out.println(th);
// visualização de erros
prepareVueErreur(th);
return "pm:vueErreurs";
}
}
Aqui encontramos um código que já apareceu várias vezes. Linha 9: uma mensagem internacionalizada é gerada para a visualização vue2:
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
Observe que inserimos XHTML na mensagem. Ela será exibida da seguinte forma:
![]() |
9.9. Exibir a agenda de um médico
A agenda do médico é exibida pela visualização [vue2.xhtml]:
![]() |
O código da visualização [vue2.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:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- Visão 2 -->
<pm:header title="#{msg['page.titre']}" swatch="b"/>
<pm:content>
<h:form id="form2">
<div align="center">
<pm:buttonGroup orientation="horizontal">
<p:commandButton inline="true" icon="back" value=" " action="#{form.showVue1}" update=":vue1"/>
<p:commandButton inline="true" icon="minus" value=" " action="#{form.getPreviousAgenda}" update=":form2"/>
<p:commandButton inline="true" icon="home" value=" " action="#{form.getTodayAgenda}" update=":form2"/>
<p:commandButton inline="true" icon="plus" value=" " action="#{form.getNextAgenda}" update=":form2"/>
</pm:buttonGroup>
<h3><h:outputText value="#{form.form2Titre}" style="color: blue" escape="false"/></h3>
</div>
<p:dataList id="creneaux" type="inset" value="#{form.agendaMedecinJour.creneauxMedecinJour}" var="creneauMedecinJour">
<p:column>
<div align="center">
<h2>
<h:outputFormat value="{0,number,#00}h:{1,number,#00} - {2,number,#00}h:{3,number,#00}">
<f:param value="#{creneauMedecinJour.creneau.hdebut}" />
<f:param value="#{creneauMedecinJour.creneau.mdebut}" />
<f:param value="#{creneauMedecinJour.creneau.hfin}" />
<f:param value="#{creneauMedecinJour.creneau.mfin}" />
</h:outputFormat>
<ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
<br/>
<h:outputText value="#{creneauMedecinJour.rv.client.titre} #{creneauMedecinJour.rv.client.prenom} #{creneauMedecinJour.rv.client.nom}" style="color: blue"/>
</ui:fragment>
</h2>
</div>
<div align="center">
<ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
<p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.supprimer']}" icon="minus" update=":form2, :vue3, :vueErreurs">
<f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
</p:commandButton>
</ui:fragment>
<ui:fragment rendered="#{creneauMedecinJour.rv==null}">
<p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.reserver']}" icon="plus" update=":form2, :vue3, :vueErreurs">
<f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
</p:commandButton>
</ui:fragment>
</div>
</p:column>
</p:dataList>
</h:form>
</pm:content>
</html>
- linha 11: gera o cabeçalho [1],
- linhas 15-20: gera o grupo de botões [2],
- linha 21: gera o título [3]. Observe-se o valor do atributo escape. É isso que permite interpretar o código XHTML que colocamos em form2Titre,
- linha 24: a exibição dos horários é feita por meio de um dataList,
- linhas 28-33: geram o título do intervalo de horário [4],
- linhas 34-37: exibem um trecho caso haja um compromisso no intervalo de horário,
- linha 36: exibe a identidade do cliente que marcou o compromisso,
- linhas 41-45: exibem o botão [Supprimer] se houver um compromisso,
- linhas 46-50: exibem o botão [Réserver] se não houver compromisso.
Esta visualização é alimentada principalmente pelo seguinte modelo:
private AgendaMedecinJour agendaMedecinJour;
que alimenta o dataList da linha 24. Esse campo foi criado pelo método getAgenda, quando se passou da visualização vue1 para a visualização vue2.
9.10. Exclusão de um compromisso
A exclusão de um compromisso corresponde à seguinte sequência:
![]() | ![]() |
A visualização afetada por essa ação é a seguinte:
<ui:fragment rendered="#{creneauMedecinJour.rv!=null}">
<p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.supprimer']}" icon="minus" update=":form2, :vueErreurs">
<f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
</p:commandButton>
</ui:fragment>
- linha 2: o botão [Supprimer] está associado ao método [Form].action (atributo action),
- linha 3: o ID do intervalo em que estamos posicionados será enviado ao modelo [Form].idCreneauChoisi,
- linha 2: a chamada AJAX atualizará as áreas form2 (formulário da vista 2) e a vista vueErreurs. Na verdade, temos dois casos: se tudo correr bem, a vista vue2 será exibida novamente; caso contrário, será exibida a vista vueErreurs.
O método [action] é o seguinte:
// ação em RV
public String action() {
// procurando o horário disponível na agenda
int i = 0;
Boolean trouvé = false;
while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneauChoisi) {
trouvé = true;
} else {
i++;
}
}
// encontrou?
if (!trouvé) {
// Isso é estranho — exibimos novamente o form2
setForms(false, true, false, false);
return "pm:vue2";
} else {
creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
}
// encontramos
// de acordo com a ação desejada
if (creneauChoisi.getRv() == null) {
return reserver();
} else {
return supprimer();
}
}
// reserva
public String reserver() {
...
}
public String supprimer() {
try {
// exclusão de um compromisso
metier.supprimerRv(creneauChoisi.getRv());
// atualiza-se a agenda
agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
// exibe o form2
setForms(false, true, false, false);
return "pm:vue2";
} catch (Throwable th) {
// visualização de erros
prepareVueErreur(th);
return "pm:vueErreurs";
}
}
- linhas 3-12: procura-se o intervalo horário cujo ID foi recebido (linha 7);
- se não for encontrado, o que é anormal, exibe-se novamente a vista2 (linhas 16-17),
- linha 19: se for encontrado, memoriza-se o objeto [CreneauMedecinJour] correspondente. É ele que nos dá acesso ao compromisso a ser excluído,
- linha 26: a gente o exclui,
- linhas 35-48: o método “excluir” retorna a visualização vue2 se a exclusão tiver ocorrido com sucesso (linhas 42-43) ou a visualização vueErreurs caso tenha ocorrido algum problema (linhas 46-47).
9.11. Agendamento
A marcação de compromisso segue a seguinte sequência:
![]() | ![]() |
Passa-se da visualização vue2 para a visualização vue3. O código envolvido nessa ação é o seguinte:
<ui:fragment rendered="#{creneauMedecinJour.rv==null}">
<p:commandButton inline="true" action="#{form.action}" value="#{msg['form2.reserver']}" icon="plus" update=":vue3, :vueErreurs">
<f:setPropertyActionListener value="#{creneauMedecinJour.creneau.id}" target="#{form.idCreneauChoisi}"/>
</p:commandButton>
</ui:fragment>
- linha 2: o botão [Réserver] está associado ao método [Form].action (atributo action), ou seja, o mesmo que para o botão [Supprimer]. A chamada AJAX atualiza as visualizações vue3 e vueErreurs, dependendo da presença ou não de erros durante o processamento da chamada.
- linha 3: assim como no botão [Supprimer], o ID do horário é passado para o modelo.
O modelo que processa essa ação é o seguinte:
// ação em RV
public String action() {
...
// de acordo com a ação desejada
if (creneauChoisi.getRv() == null) {
return reserver();
} else {
return supprimer();
}
}
// reserva
public String reserver() {
try {
// título do formulário 3
form3Titre = Messages.getMessage(null, "form3.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour),
creneauChoisi.getCreneau().getHdebut(), creneauChoisi.getCreneau().getMdebut(), creneauChoisi.getCreneau().getHfin(), creneauChoisi.getCreneau().getMfin()}).getSummary();
// exibe o formulário 3
setForms(false, false, true, false);
return "pm:vue3";
} catch (Throwable th) {
// visualização de erros
prepareVueErreur(th);
return "pm:vueErreurs";
}
}
- linhas 2-10: o método action determinará a referência creneauChoisi do objeto [CreneauMedecinJour] que é objeto de uma reserva e, em seguida, chamará o método reserver,
- linha 16: é criada uma mensagem internacionalizada. Ela é a seguinte:
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,número,#00}-{6,número,#00}h:{7,número,#00}
Esse será o título da visualização vue3. Assim como na visualização vue2, esse título contém o código XML. Ele também contém parâmetros formatados para exibir os horários do intervalo,
![]() |
- linhas 19-20: a visualização vue3 é exibida,
- linhas 23-24: a visualização vueErreurs é exibida caso tenham ocorrido problemas.
9.12. Validação de um compromisso
A validação de um compromisso segue a seguinte sequência:
![]() |
Na [1], a vista vue3 e, na [2], a vista vue2 após a adição de um compromisso.
O código [vue3.xhtml] da vista3 é 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:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- Vista 3 -->
<pm:header title="#{msg['page.titre']}" swatch="b"/>
<pm:content>
<h:form id="form3">
<p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>
<div align="center">
<h3><h:outputText value="#{form.form3Titre}" style="color: blue" escape="false"/></h3>
</div>
<pm:field>
<h:outputLabel value="#{msg['form3.client']}" for="choixClient"/>
<h:selectOneMenu id="choixClient" value="#{form.idClient}">
<f:selectItems value="#{form.clients}" var="client" itemLabel="#{client.titre} #{client.prenom} #{client.nom}" itemValue="#{client.id}"/>
</h:selectOneMenu>
</pm:field>
<div align="center">
<p:commandButton inline="true" value="#{msg['form3.valider']}" action="#{form.validerRv}" update=":vue2, :vueErreurs" icon="check"/>
</div>
</h:form>
</pm:content>
</html>
- linha 16: gera o título da visualização [3]. Observe-se o valor do atributo escape, que permite a interpretação dos caracteres XHTML no título,
- linhas 18-23: geram a lista suspensa de clientes [4],
- linha 25: gera o botão [Valider] [5]. O método [Form].validerRv está associado a esse botão:
// validação da reserva
public String validerRv() {
try {
// recuperamos uma instância do horário selecionado
Creneau creneau = metier.getCreneauById(idCreneauChoisi);
// adiciona-se o compromisso
metier.ajouterRv(jour, creneau, hClients.get(idClient));
// atualiza-se a agenda
agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
// exibe o form2
setForms(false, true, false, false);
return "pm:vue2";
} catch (Throwable th) {
// visualização de erros
prepareVueErreur(th);
return "pm:vueErreurs";
}
}
Esse código já foi encontrado na versão 01. Observa-se apenas a exibição das visualizações:
- a visualização vue2 (linhas 11-12) se tudo tiver ocorrido normalmente,
- a visualização vueErreurs (linhas 15-16) caso contrário.
9.13. Cancelamento de um agendamento
Isso corresponde à seguinte sequência:
![]() |
O botão [1] na visualização [vue3.xhtml] é o seguinte:
<p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>
O método [Form].showVue2 é, portanto, chamado. Ele se limita a exibir a vista2:
public String showVue2() {
// visualização 2
setForms(false, true, false, false);
return "pm:vue2?reverse=true";
}
9.14. Navegação no calendário
Na vista2, há botões que permitem navegar pelo calendário:
Dia anterior:
![]() |
Dia seguinte:
![]() |
Hoje:
![]() |
Embora não apareça nas capturas de tela acima, a agenda é atualizada e exibe os compromissos do novo dia selecionado.
As tags dos três botões em questão são as seguintes no [vue2.xhtml]:
<pm:buttonGroup orientation="horizontal">
<p:commandButton inline="true" icon="back" value=" " action="#{form.showVue1}" update=":vue1"/>
<p:commandButton inline="true" icon="minus" value=" " action="#{form.getPreviousAgenda}" update=":form2"/>
<p:commandButton inline="true" icon="home" value=" " action="#{form.getTodayAgenda}" update=":form2"/>
<p:commandButton inline="true" icon="plus" value=" " action="#{form.getNextAgenda}" update=":form2"/>
</pm:buttonGroup>
Os métodos [Form].getPreviousAgenda, [Form].getNextAgenda, [Form].today foram analisados no exemplo 03.
9.15. Alteração do idioma de exibição
A alteração do idioma é controlada por um botão na página inicial:
![]() |
O código do botão é o seguinte:
<p:button icon="gear" value=" " href="#config" />
Portanto, ele alterna para a visualização de configuração [2]. A visualização [config.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:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- Visão 1 -->
<pm:header title="#{msg['page.titre']}" swatch="b">
<f:facet name="left">
<p:button icon="back" value=" " href="#vue1?reverse=true" />
</f:facet>
</pm:header>
<pm:content>
<h:form id="frmConfig">
<div align="center">
<h3><h:outputText value="#{msg['config.titre']}" style="color: blue"/></h3>
</div>
<pm:field>
<h:outputLabel value="#{msg['config.langue']}" for="langue"/>
<h:selectOneRadio id="langue" value="#{form.locale}">
<f:selectItem itemLabel="#{msg['config.langue.francais']}" itemValue="fr"/>
<f:selectItem itemLabel="#{msg['config.langue.anglais']}" itemValue="en" />
</h:selectOneRadio>
</pm:field>
<p:commandButton value="#{msg['config.valider']}" action="#{form.configurer}" update=":vue1"/>
</h:form>
</pm:content>
</html>
- linha 11: exibe [3],
- linha 13: exibe o botão [4]. Esse botão permite retornar à vista vue1,
- linha 17: o formulário da vista,
- linha 19: exibe o título da visualização [5],
- linhas 21-27: exibem os botões de opção. O valor (itemValue) do botão de opção selecionado será enviado para o modelo [Form].locale (atributo value da linha 23),
- linha 28: exibe o botão [Valider]. A chamada AJAX atualiza a visualização vue1 (atributo update). O método chamado é [Form].configurer:
public String configurer(){
// após a configuração — exibe-se novamente a vista 1
redirect();
return null;
}
private void redirect() {
// redireciona 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);
}
}
O método configurer (linha 1) limita-se a redirecionar o navegador do celular para o URL do aplicativo. Portanto, será carregada a página [index.xhtml]:
<f:view xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:pm="http://primefaces.org/mobile"
contentType="text/html"
locale="#{form.locale}">
<pm:page title="#{msg['page.titre']}">
<pm:view id="vue1">
...
</pm:view>
...
</pm:page>
</f:view>
- linha 8: a visualização utilizará o idioma que acabou de ser alterado (atributo `locale`) e exibirá a visualização 1 (linha 11).
9.16. Conclusion
Vamos relembrar a arquitetura da aplicação que acabamos de construir:
![]() |
A transição para uma interface móvel exigiu a reescrita das páginas XHTML. O modelo, por outro lado, sofreu poucas alterações. As camadas inferiores [métier], [DAO] e [JPA], por sua vez, não sofreram nenhuma alteração.
9.17. Testes no Eclipse
Assim como fizemos nas versões anteriores do aplicativo de exemplo, mostramos como testar esta versão 05 com o Eclipse. Primeiramente, importamos para o Eclipse os projetos Maven do exemplo 05 [1]:
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]: as camadas [DAO] e [JPA],
- [mv-rdvmedecins-ejb-metier]: a camada [métier],
- [mv-rdvmedecins-pfmobile]: a camada [web] implementada pelo PrimeFaces Mobile,
- [mv-rdvmedecins-pfmobile-app]: o projeto pai do projeto corporativo [mv-rdvmedecins-pfmobile-app-ear]. Ao importar o projeto pai, o projeto filho é importado automaticamente,
- em [2], executa-se o projeto corporativo [mv-rdvmedecins-pfmobile-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 ou simulador de celular:
![]() |




































