9. 示例应用程序 05:rdvmedecins-pfm-ejb
让我们回顾一下为 GlassFish 服务器开发的 JSF/EJB 示例应用程序 01 的结构:
![]() |
除了Web层之外,我们不会对该架构进行任何更改。Web层将在此处使用JSF、PrimeFaces和PrimeFaces Mobile来实现。目标浏览器将是移动浏览器。
![]() |
我们已使用GlassFish开发了两个应用程序:
- 应用程序01采用JSF/EJB架构,界面较为基础,
- 应用程序 03 采用了 PF/EJB 架构,拥有功能丰富的界面。
鉴于 PrimeFaces Mobile 提供的组件数量有限,我们将恢复使用应用程序 01 的基本界面。此外,视图必须能够适应移动设备的小屏幕尺寸。
9.1. 视图
为了让您直观了解,以下是该应用程序在 iPhone 4 模拟器上运行的几张截图:
![]() |
- [1] 中的主页。请注意,这次我们必须指定机器名称(也可以输入其 IP 地址),因为使用 localhost 时屏幕上没有任何显示,
- [2] 中的医生下拉菜单,
- 在 [3] 中,选择预约的日期,
- 在 [4] 中,是请求查看当日日程的按钮,
- 在 [5] 中,新视图显示了医生的可用时段,
- 在 [6] 中,是一组用于浏览日历的按钮,
- 在 [7] 处,显示提醒您医生姓名及日期的提示信息,
- 在 [8] 中,可预订的时间段。开始吧,
![]() |
- 在 [9] 中,即用户的选项视图,
- 在[10]中,一条提醒用户医生、日期及预约时段的消息,
- 在[11]中,客户端下拉菜单,
- 在 [12] 中,确认按钮,
- 在 [13] 中,点击该按钮将返回日历,
- 在 [14] 中,已预订的时间段现已保留。现在我们将删除该预订,
![]() |
- 在[15]中,我们仍处于同一视图,
- 但在[16]中,该预约已被删除,
在首页上,您可以更改语言 [17]、[18]、[19]:
![]() |
最后,我们还添加了一个错误页面:
![]() |
9.2. NetBeans 项目
NetBeans 项目如下:
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]:示例 01 中 [DAO] 和 [JPA] 层的 EJB 项目,
- [mv-rdvmedecins-ejb-metier]:示例 01 [业务] 层的 EJB 项目,
- [mv-rdvmedecins-pfmobile]: [Web] 层项目 / PrimeFaces Mobile – 新增,
- [mv-rdvmedecins-pfmobile-app-ear]:用于在 GlassFish 服务器上部署应用程序的企业级项目 – 新增。
9.3. 该企业项目
该企业项目仅用于将三个模块 [mv-rdvmedecins-ejb-dao-jpa]、[mv-rdvmedecins-ejb-metier]、[mv-rdvmedecins-pfmobile] 部署到 GlassFish 服务器上。NetBeans 项目结构如下:
![]() |
该项目仅用于管理[pom.xml]文件中定义的以下三个依赖项[1]:
<?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>
- 第 6–9 行:企业项目的 Maven 工件,
- 第 14–33 行:该项目的三个依赖项。请注意它们的类型(第 19、25、31 行)。
要运行 Web 应用程序,必须运行此企业项目。
9.4. PrimeFaces Mobile Web 项目
PrimeFaces Mobile Web 项目如下:
![]() |
- 在 [1] 中,是该项目的页面。[index.xhtml] 页面是该项目唯一的页面。它包含五个视图:[vue1.xhtml]、[vue2.xhtml]、[vue3.xhtml]、[vueErreurs.xhtml] 和 [config.xhtml],
- 在 [2] 中,是 Java Bean。其中 [Application] Bean 具有应用程序作用域,而 [Form] Bean 具有会话作用域。[Error] 类封装了一个错误,
- 在 [3] 中,是用于国际化的消息文件,
- 在 [4] 中,是依赖项。Web 项目依赖于 EJB 项目的 [DAO] 层、EJB 项目的 [business] 层以及 PrimeFaces Mobile 的 [web] 层。
9.5. 项目配置
该项目的配置与我们之前学习过的 PrimeFaces 或 JSF 项目相同。我们在此列出配置文件,不再赘述。
![]() | ![]() |
[web.xml]:用于配置 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>
请注意,第 26 行中的 [index.xhtml] 页面是该应用程序的首页。
[faces-config.xml]:配置 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]:内容为空,但 @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]:法语消息文件
# page
page.titre=Les M\u00e9decins Associ\u00e9s
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
# vue1
vue1.header=Les M\u00e9decins Associ\u00e9s - R\u00e9servations
# formulaire 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
# formulaire 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'today
# formulaire 3
form3.client=Client
form3.valider=Valider
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.retour=Retour
# erreur
erreur.titre=Une erreur s'is produced.
# config
config.retour=Retour
config.titre=Configuration
config.langue=Langue
config.langue.francais=Fran\u00e7ais
config.langue.anglais=Anglais
config.valider=Valider
#exception
exception.titre=Application indisponible. Veuillez recommencer ult\u00e9rieurement.
[messages_en.properties]:英文消息文件
# page
page.titre=The Associated Doctors
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
# vue1
vue1.header=The Associated Doctors - Reservations
# formulaire 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
# formulaire 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
# formulaire 3
form3.client=Patient
form3.valider=Validate
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.retour=Back
# erreur
erreur.titre=Some error happened
# config
config.retour=Back
config.titre=Configuration
config.langue=Language
config.langue.francais=French
config.langue.anglais=English
config.valider=Validate
#exception
exception.titre=Application not available. Please try again later.
9.6. [index.xhtml] 页面
![]() |
该项目始终显示同一页面,即以下 [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">
<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>
- 第 8 行:该页面已实现国际化(locale 属性),
- 第 10 行:该页面包含五个视图:第 11 行的 view1、第 19 行的 view2、第 24 行的 view3、第 29 行的 viewErrors 以及第 34 行的 config。在任何给定时刻,这些视图中仅有一个可见。应用程序启动时,将显示 view1。 在此,我们遇到了以下问题:如果应用程序初始化成功,则必须显示 [view1.xhtml];否则,必须显示 [errorView.xhtml]。 我们通过确保 vue1 视图的内容由模型管理来解决此问题,该模型通过设置布尔值 [Form].form1rendered(第 12 行)和 [Form].erreurInit(第 15 行)来确定 vue1 的内容(第 11 行),
在模拟器中,视图 [vue1.xhtml] 渲染为 [1],视图 [vue2.xhtml] 渲染为 [2],视图 [vue3.xhtml] 渲染为 [3]:
![]() |
视图 [vueErreurs.xhtml] 的渲染结果为 [4],视图 [config.xhtml] 的渲染结果为 [5]:
![]() |
9.7. 项目的 Bean
![]() |
[utils] 包中的类已作介绍:[Messages] 类是一个用于简化应用程序消息国际化的类。相关内容已在第 2.8.5.7 节中讨论过。
9.7.1. 应用程序 Bean
[Application.java] Bean 是一个应用程序范围的 Bean。回顾一下,此类 Bean 用于存储可供应用程序所有用户访问的只读数据。该 Bean 如下所示:
package beans;
import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import rdvmedecins.metier.service.IMetierLocal;
@Named(value = "application")
@ApplicationScoped
public class Application {
// business layer
@EJB
private IMetierLocal metier;
public Application() {
}
// getters
public IMetierLocal getMetier() {
return metier;
}
}
- 第 8 行:我们将 Bean 的名称设为 "application",
- 第 9 行:它具有应用程序作用域,
- 第13–14行:应用服务器的EJB容器将向其中注入对[业务]层本地接口的引用。让我们回顾一下应用程序架构:
![]() |
PFM 应用程序和 [Business] EJB 将运行在同一个 JVM(Java 虚拟机)中。因此,[PFM] 层将使用 EJB 的本地接口。仅此而已。[Application] Bean 不再包含其他内容。其他 Bean 若要访问 [Business] 层,将从该 Bean 中获取它。
9.7.2. [Error] Bean
[Error] 类如下所示:
package beans;
public class Erreur {
public Erreur() {
}
// field
private String classe;
private String message;
// manufacturer
public Erreur(String classe, String message){
this.setClasse(classe);
this.message=message;
}
// getters and setters
...
}
- 第 9 行:如果抛出了异常,则显示异常类的名称,
- 第 10 行:错误消息。
9.7.3. [Form] Bean
其代码如下:
package beans;
...
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// bean Application
@Inject
private Application application;
private IMetierLocal metier;
// session cache
private List<Medecin> medecins;
private List<Client> clients;
private Map<Long, Medecin> hMedecins = new HashMap<Long, Medecin>();
private Map<Long, Client> hClients = new HashMap<Long, Client>();
// model
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");
// initially no error
erreurInit = false;
// date formatting
dateFormatter = new SimpleDateFormat(Messages.getMessage(null, "format.date", null).getSummary());
dateFormatter.setLenient(false);
// the current day
strJour = dateFormatter.format(jour);
// recover the business layer
metier = application.getMetier();
// caching doctors and customers
try {
medecins = metier.getAllMedecins();
clients = metier.getAllClients();
} catch (Throwable th) {
// we note the error
erreurInit = true;
prepareVueErreur(th);
return;
}
// list checking
if (medecins.size() == 0) {
// we note the error
erreurInit = true;
erreurs = new ArrayList<Erreur>();
erreurs.add(new Erreur("", "La liste des médecins est vide"));
}
if (clients.size() == 0) {
// we note the error
erreurInit = true;
erreurs = new ArrayList<Erreur>();
erreurs.add(new Erreur("", "La liste des clients est vide"));
}
// mistake?
if (erreurInit) {
// the error view is displayed
setForms(false, false, false, true);
return;
}
// dictionaries
for (Medecin m : medecins) {
hMedecins.put(m.getId(), m);
}
for (Client c : clients) {
hClients.put(c.getId(), c);
}
// view 1
setForms(true, false, false, false);
}
// view display
private void setForms(Boolean form1Rendered, Boolean form2Rendered, Boolean form3Rendered, Boolean erreurRendered) {
this.form1Rendered = form1Rendered;
this.form2Rendered = form2Rendered;
this.form3Rendered = form3Rendered;
this.erreurRendered = erreurRendered;
}
// preparation vueErreur
private void prepareVueErreur(Throwable th) {
// create an error list
erreurs = new ArrayList<Erreur>();
erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
while (th.getCause() != null) {
th = th.getCause();
erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
}
// the error view is displayed
setForms(false, false, false, true);
}
// getters and setters
..
}
- 第 5-7 行:[Form] 类是一个名为“form”且作用域为会话的 Bean。请注意,因此该类必须是可序列化的,
- 第 12–13 行:表单 Bean 持有对应用程序 Bean 的引用。该引用将由应用程序运行的 Servlet 容器注入(@Inject 注解的存在)。
- 第 24–27 行:控制视图 vue1(第 24 行)、vue2(第 25 行)、vue3(第 26 行)和 vueErrors(第 27 行)的显示,
- 第 43–44 行:类实例化后会立即执行 init 方法(存在 @PostConstruct 注解),
- 第 49–50 行:处理日期格式。PrimeFaces Mobile 不提供日历控件。此外,JSF 验证器无法在 PFM 页面中使用。因此,我们需要手动处理日历日期的输入,
- 第 49 行:设置日期格式。该格式取自国际化文件:
format.date=dd/MM/yyyy
format.date_detail=dd/MM/yyyy
- 第 50 行:我们指定在日期处理方面绝不能“宽容”。如果不这样做,像 12/32/2011 这样的输入(这是错误的输入)会被视为有效日期 01/01/2012,
- 第 54 行:我们从 [Application] Bean 中获取 [business] 层的引用,
- 第 57-58 行:我们向 [business] 层请求医生和客户的列表,
- 第 66–91 行:若一切顺利,将构建医生和客户的字典。它们按编号进行索引。随后,将显示 [vue1.xhtml] 视图(第 93 行),
- 第 59 行:若发生错误,则构建页面模板 [errorView.xhtml]。该模板即第 35 行中的错误列表,
- 第 105–115 行:[prepareVueErreur] 方法构建待显示的错误列表。随后 [index.xhtml] 页面显示 [vueErreurs.xhtml] 视图(第 114 行)。
[error.xhtml] 页面内容如下:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- Errors view -->
<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>
它使用 <p:dataList> 标签(第 21–23 行)来显示错误列表。第 13 行的按钮可让您返回 vue1 视图。
![]() |
- [1] 按钮由第 13 行生成。icon 属性用于设置按钮的图标。该按钮将返回 vue1 视图(href 属性),
- 标题 [2] 由第 11–15 行生成,
- 标题 [3] 由第 18 行生成,
- 文本 [4] 由第 22 行中的模板 #{error.class} 生成,
- 文本 [5] 由第 22 行中的模板 #{erreur.message} 生成。
接下来我们将定义应用程序生命周期的各个阶段。针对每个用户操作,我们将分析相关的视图以及其中触发的事件处理程序。
9.8. 显示主页
如果一切正常,首先显示的视图是 [vue1.xhtml]。这将生成以下视图:
![]() |
[vue1.xhtml]视图的代码如下:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- View 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>
- 第 11–15 行:生成标题 [1],
- 第 13 行:创建 [2] 按钮。点击它将显示配置视图(href 属性),
- 第 19 行:显示标题 [3],
- 第 21–26 行:生成医生下拉菜单 [4],
- 第 27–34 行:显示日期输入框 [5]。该输入框使用不带验证器的 <p:inputText> 标签。日期验证将在服务器端进行。如果日期不正确,服务器将设置一条错误消息,由第 30–34 行显示,
![]() |
- 第 35 行:提交表单的按钮。它会更新三个区域:form1(vue1 中的表单)、vue2 和 vueErrors。实际上,如果日期无效,必须更新 form1;如果日期正确,则必须更新 vue2。 最后,如果发生异常(例如数据库连接中断),则必须显示 vueErrors。您可能会想使用 vue1 代替 form1(更新整个视图)。但在这种情况下,应用程序将会崩溃。
该视图由以下模型提供支持:
@Named(value = "form")
@SessionScoped
public class Form implements Serializable {
public Form() {
}
// bean Application
@Inject
private Application application;
private IMetierLocal metier;
// session cache
private List<Medecin> medecins;
private List<Client> clients;
// model
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;
// list of doctors
public List<Medecin> getMedecins() {
return medecins;
}
// agenda
public String getAgenda() {
...
}
}
- 第 16 行的字段用于读取和写入页面第 23 行列表的值。当页面首次显示时,它会设置下拉列表中选中的值,
- 第27–29行的方法生成医生下拉列表(视图的第24行)中的选项。每个生成的选项将使用医生的头衔、姓氏和名字作为标签(itemLabel),并使用医生的ID作为值(itemValue)。
- 第 18 行的字段为页面第 29 行的输入字段提供读写访问权限,
- 第 32–34 行:getAgenda 方法处理页面第 35 行 [Agenda] 按钮的点击事件。其代码如下:
// agenda
public String getAgenda() {
try {
// on vérifie le jour
jour = dateFormatter.parse(strJour);
// pas d'erreur
erreurDate=false;
msgErreurDate = "";
// on crée l'agenda
return getAgenda(jour);
} catch (ParseException ex) {
// msg d'erreur
erreurDate=true;
msgErreurDate = Messages.getMessage(null, "form1.date.invalide", null).getSummary();
// vue1
setForms(true, false, false, false);
return "pm:vue1";
}
}
- 该方法首先检查用户输入的日期是否有效,
- 第 5 行:根据模型实例化时由 init 方法初始化的日期格式解析日期,
- 第 11 行:如果发生异常,则设置错误(第 13 行),构建国际化错误消息(第 14 行),准备 vue1 视图(第 16 行),并显示 vue1 视图(第 17 行),
- 第 10 行:如果日期有效,则执行以下方法:
// agenda
public String getAgenda(Date jour) {
// no slots selected yet
creneauChoisi = null;
try {
// we pick up the chosen doctor
medecin = hMedecins.get(idMedecin);
// title form 2
form2Titre = Messages.getMessage(null, "form2.titre", new Object[]{medecin.getTitre(), medecin.getPrenom(), medecin.getNom(), new SimpleDateFormat("dd MMM yyyy").format(jour)}).getSummary();
// the doctor's diary for a given day
agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
// view 2 is displayed
setForms(false, true, false, false);
return "pm:vue2";
} catch (Throwable th) {
System.out.println(th);
// error view
prepareVueErreur(th);
return "pm:vueErreurs";
}
}
这里我们看到一段之前出现过多次的代码。在第 9 行,为 vue2 视图构建了一条国际化消息:
form2.titre={0} {1} {2}<br/>{3}
form2.titre_detail={0} {1} {2}<br/>{3}
请注意,我们在消息中加入了 XHTML 内容。它将显示如下:
![]() |
9.9. 显示医生的日程安排
医生的日程安排由视图 [vue2.xhtml] 显示:
![]() |
[vue2.xhtml] 视图的代码如下:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- View 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>
- 第 11 行:生成标题 [1],
- 第 15–20 行:生成按钮组 [2],
- 第 21 行:生成标题 [3]。请注意 escape 属性的值。正是这个属性使得我们放置在 form2Titre 中的 XHTML 代码能够被正确解析,
- 第 24 行:使用 dataList 显示时间段,
- 第 28–33 行:生成时间段标签 [4],
- 第 34–37 行:如果时间段内有预约,则显示摘要,
- 第 36 行:显示预约客户的姓名,
- 第 41–45 行:若该时段有预约,则显示 [删除] 按钮,
- 第 46–50 行:若无预约,则显示 [预约] 按钮。
此视图主要由以下模型提供数据:
private AgendaMedecinJour agendaMedecinJour;
该模型在第24行填充了dataList字段。该字段是在从视图1切换到视图2时,由getAgenda方法创建的。
9.10. 删除预约
删除预约的步骤如下:
![]() | ![]() |
此操作涉及的视图如下:
<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>
- 第 2 行:[Delete] 按钮与 [Form].action 方法相关联(action 属性),
- 第 3 行:当前选中的时段 ID 将发送至 [Form].idCreneauChoisi 模型,
- 第 2 行:AJAX 调用将更新 form2(view2 表单)的字段以及 vueErreurs 视图。可能出现两种情况:如果一切正常,将重新显示 vue2 视图;否则,将显示 vueErreurs 视图。
[action]方法如下:
// action on RV
public String action() {
// search for the time slot in the calendar
int i = 0;
Boolean trouvé = false;
while (!trouvé && i < agendaMedecinJour.getCreneauxMedecinJour().length) {
if (agendaMedecinJour.getCreneauxMedecinJour()[i].getCreneau().getId() == idCreneauChoisi) {
trouvé = true;
} else {
i++;
}
}
// have we found?
if (!trouvé) {
// it's weird - form2 is redisplayed
setForms(false, true, false, false);
return "pm:vue2";
} else {
creneauChoisi = agendaMedecinJour.getCreneauxMedecinJour()[i];
}
// we found
// according to desired action
if (creneauChoisi.getRv() == null) {
return reserver();
} else {
return supprimer();
}
}
// reservation
public String reserver() {
...
}
public String supprimer() {
try {
// deleting an appointment
metier.supprimerRv(creneauChoisi.getRv());
// updating the agenda
agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
// form2 is displayed
setForms(false, true, false, false);
return "pm:vue2";
} catch (Throwable th) {
// error view
prepareVueErreur(th);
return "pm:vueErreurs";
}
}
- 第3至12行:我们查找ID与接收到的(第7行)相符的时间段,
- 若未找到(这属于异常情况),则重新显示视图2(第16-17行),
- 第 19 行:若找到,则存储对应的 [CreneauMedecinJour] 对象。该对象使我们能够访问待删除的预约,
- 第 26 行:我们将其删除,
- 第35–48行:如果删除成功(第42–43行),delete方法返回vue2视图;如果出现问题(第46–47行),则返回vueErrors视图。
9.11. 预约
预约操作遵循以下流程:
![]() | ![]() |
我们从 vue2 视图导航至 vue3 视图。此操作涉及的代码如下:
<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>
- 第 2 行:[Book] 按钮关联了 [Form].action 方法(action 属性),因此其实现与 [Delete] 按钮相同。AJAX 调用会根据调用处理过程中是否出现错误,相应地更新 vue3 和 vueErrors 视图。
- 第 3 行:与 [Delete] 按钮一样,时段 ID 会被传递给模型。
处理此操作的模型如下:
// action on RV
public String action() {
...
// according to desired action
if (creneauChoisi.getRv() == null) {
return reserver();
} else {
return supprimer();
}
}
// reservation
public String reserver() {
try {
// title form 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();
// form 3 is displayed
setForms(false, false, true, false);
return "pm:vue3";
} catch (Throwable th) {
// error view
prepareVueErreur(th);
return "pm:vueErreurs";
}
}
- 第 2–10 行:该操作方法获取正在预订的 [CreneauMedecinJour] 对象的 creneauChoisi 引用,然后调用 reserver 方法,
- 第16行:构建一条国际化消息。内容如下:
form3.titre={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
form3.titre_detail={0} {1} {2}<br/>{3}<br/>{4,number,#00}h:{5,number,#00}-{6,number,#00}h:{7,number,#00}
这将是 Vue 3 视图的标题。与 Vue 2 视图类似,该标题包含 XML 内容,同时也包含用于显示时间段的格式化参数,
![]() |
- 第 19-20 行:显示 Vue 3 视图,
- 第23-24行:若遇到问题,则显示 vueErrors 视图。
9.12. 确认预约
预约验证遵循以下步骤:
![]() |
[1] 处为 Vue 3 视图,[2] 处为添加预约后的 Vue 2 视图。
Vue3 的代码 [vue3.xhtml] 如下:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- View 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>
- 第 16 行:生成视图标题 [3]。请注意 escape 属性的值,它允许在标题中解释 XHTML 字符,
- 第 18–23 行:生成客户端下拉列表 [4],
- 第 25 行:生成 [Validate] 按钮 [5]。该按钮关联了 [Form].validateRv 方法:
// rv validation
public String validerRv() {
try {
// retrieve an instance of the chosen slot
Creneau creneau = metier.getCreneauById(idCreneauChoisi);
// we add the Rv
metier.ajouterRv(jour, creneau, hClients.get(idClient));
// updating the agenda
agendaMedecinJour = metier.getAgendaMedecinJour(medecin, jour);
// form2 is displayed
setForms(false, true, false, false);
return "pm:vue2";
} catch (Throwable th) {
// error view
prepareVueErreur(th);
return "pm:vueErreurs";
}
}
该代码在第 01 版中已出现。请注意视图的显示方式:
- 如果一切正常,则显示 vue2 视图(第 11–12 行),
- 否则将显示 vueErreurs 视图(第 15–16 行)。
9.13. 取消预约
这对应于以下流程:
![]() |
视图 [vue3.xhtml] 中的按钮 [1] 如下所示:
<p:commandButton inline="true" value=" " icon="back" action="#{form.showVue2}" update=":vue2"/>
因此调用了 [Form].showVue2 方法。它仅用于显示 vue2:
public String showVue2() {
// vue2
setForms(false, true, false, false);
return "pm:vue2?reverse=true";
}
9.14. 浏览日历
在 view2 中,您可以通过按钮在日历中导航:
上一天:
![]() |
次日:
![]() |
今天:
![]() |
虽然上方的截图中未显示,但日历会自动更新,并显示新选定日期的日程安排。
[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>
方法 [Form].getPreviousAgenda、[Form].getNextAgenda 和 [Form].today 已在示例 03 中介绍过。
9.15. 更改显示语言
通过首页上的按钮可以切换语言:
![]() |
该按钮的代码如下:
<p:button icon="gear" value=" " href="#config" />
因此,它将跳转至 [2] 配置视图。[config.xhtml] 视图如下所示:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:pm="http://primefaces.org/mobile"
xmlns:ui="http://java.sun.com/jsf/facelets">
<!-- View 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>
- 第 11 行:显示 [3],
- 第 13 行:显示按钮 [4]。此按钮允许您返回 vue1 视图,
- 第 17 行:视图的表单,
- 第 19 行:显示视图标题 [5],
- 第 21–27 行:显示单选按钮。所选单选按钮的值(itemValue)将提交至 [Form].locale 模型(第 23 行的 value 属性),
- 第 28 行:显示 [提交] 按钮。AJAX 调用更新 vue1 视图(update 属性)。调用的方法是 [Form].configure:
public String configurer(){
// after configuration - redisplay view1
redirect();
return null;
}
private void redirect() {
// redirect the client to the servlet
ExternalContext ctx = FacesContext.getCurrentInstance().getExternalContext();
try {
ctx.redirect(ctx.getRequestContextPath());
} catch (IOException ex) {
Logger.getLogger(Form.class.getName()).log(Level.SEVERE, null, ex);
}
}
configure 方法(第 1 行)仅将移动浏览器重定向到应用程序的 URL。因此,将加载 [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>
- 第 8 行:视图将使用刚刚更改的语言(locale 属性),并显示 view1(第 11 行)。
9.16. 结论
让我们回顾一下刚刚构建的应用程序的架构:
![]() |
为了过渡到移动界面,需要重写 XHTML 页面。另一方面,模型几乎没有变化。底层 [业务]、[DAO]、[JPA] 层则完全没有改变。
9.17. Eclipse 测试
与之前版本的示例应用程序一样,我们将演示如何使用 Eclipse 测试此 05 版。首先,我们将示例 05 [1] 中的 Maven 项目导入到 Eclipse 中:
![]() |
- [mv-rdvmedecins-ejb-dao-jpa]:[DAO] 和 [JPA] 层,
- [mv-rdvmedecins-ejb-metier]:[业务]层,
- [mv-rdvmedecins-pfmobile]:由 PrimeFaces Mobile 实现的 [Web] 层,
- [mv-rdvmedecins-pfmobile-app]:企业项目 [mv-rdvmedecins-pfmobile-app-ear] 的父项目。导入父项目时,子项目会自动被导入,
- 在 [2] 中,运行企业项目 [mv-rdvmedecins-pfmobile-app-ear],
![]() |
- 在 [3] 中,选择 Glassfish 服务器,
- 在 [4] 中,于 [Servers] 选项卡下,应用程序已部署完成。它不会自动运行。您必须在浏览器或移动模拟器 [5] 中输入其 URL [http://localhost:8080/mv-rdvmedecins-pfmobile/]:
![]() |




































