Skip to content

5. PrimeFaces 组件库简介

5.1. PrimeFaces 在 JSF 应用程序中的作用

让我们回到本文开头所探讨的 JSF 应用程序架构:

JSF 页面是通过以下三个标签库构建的:

1
2
3
4
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  • 第 2 行:来自 [http://java.sun.com/jsf/html] 命名空间的 <h:x> 标签,它们对应于 HTML 标签,
  • 第 3 行:来自 [http://java.sun.com/jsf/core] 命名空间的 <f:y> 标签,对应于 JSF 标签,
  • 第 4 行:来自 [http://java.sun.com/jsf/facelets] 命名空间的 <ui:z> 标签,对应于 Facelet 标签。

为了构建 JSF 页面,我们将添加第四个标签库,即 PrimeFaces 组件的标签库。

1
2
3
<html xmlns="http://www.w3.org/1999/xhtml"
      ...
xmlns:p="http://primefaces.org/ui">
  • 第 3 行:来自 [http://primefaces.org/ui] 命名空间的 <p:z> 标签对应于 PrimeFaces 组件。

这是唯一需要进行的更改。因此,它会出现在视图中。事件处理程序和模型与 JSF 中的保持一致。这一点非常重要,需要理解。

使用 PrimeFaces 组件,您可以创建更用户友好的 Web 界面——得益于该库中的众多组件,以及其原生支持的 AJAX 技术带来的更流畅的交互体验此类界面被称为富界面或 RIA(富互联网应用程序)。

原有的 JSF 架构将转变为以下 PF(PrimeFaces)架构:

5.2. PrimeFaces 的优势

PrimeFaces 网站 [http://www.primefaces.org/showcase/ui/home.jsf] 提供了可在 PF 页面中使用的组件列表:

在接下来的示例中,我们将使用 PrimeFaces 的前两个功能:

  • 其中约一百个组件中的部分,
  • 以及它们原生的 AJAX 行为。

在可用的组件中:

在我们的示例中,我们将仅使用其中大约十五个,但这足以帮助您理解构建 PrimeFaces 页面的基本原理。

5.3. 学习 PrimeFaces

PrimeFaces 为每个组件都提供了使用示例。只需点击链接即可。让我们来看一个示例:

  • 在 [1] 中,是 [Spinner] 组件的示例,
  • 在 [2] 中,是点击 [Submit] 按钮后显示的对话框。

这里有三个新功能:

  • [Spinner]组件,该组件在JSF中默认并不存在,
  • 对话框也是如此,
  • 最后,由 [Submit] 按钮触发的 POST 请求是通过 AJAX 处理的。如果你在 POST 过程中仔细观察浏览器,你会发现没有出现沙漏图标。页面并未重新加载,而是被简单地修改了:一个新的组件——在本例中是对话框——出现在页面上。

让我们来看看这一切是如何运作的。该示例的 XHTML 代码如下:


<h:form>
       <p:panel header="Spinners">
           <h:panelGrid id="grid" columns="2" cellpadding="5">
                <h:outputLabel for="spinnerBasic" value="Basic Spinner: " />
                <p:spinner id="spinnerBasic" value="#{spinnerController.number1}"/>
                <h:outputLabel for="spinnerStep" value="Step Factor: " />
                <p:spinner id="spinnerStep" value="#{spinnerController.number2}" stepFactor="0.25"/>
                <h:outputLabel for="minmax" value="Min/Max: " />
                <p:spinner id="minmax" value="#{spinnerController.number3}" min="0" max="100"/>
                <h:outputLabel for="prefix" value="Prefix: " />
                <p:spinner id="prefix" value="0" prefix="$" min="0" value="#{spinnerController.number4}"/>
           <h:outputLabel for="ajaxspinner" value="Ajax Spinner: " />
           <p:outputPanel>
                   <p:spinner id="ajaxspinner" value="#{spinnerController.number5}">
                      <p:ajax update="ajaxspinnervalue" process="@this" />
               </p:spinner>
               <h:outputText id="ajaxspinnervalue" value="#{spinnerController.number5}"/>
            </p:outputPanel>
           </h:panelGrid>
       </p:panel>
    <p:commandButton value="Submit" update="display" oncomplete="dialog.show()" />
    
    <p:dialog header="Values" widgetVar="dialog" showEffect="fold" hideEffect="fold">
        ...
     </p:dialog>
</h:form>

首先,请注意这里出现了标准的 JSF 标签:第 1 行的 <h:form>、第 3 行的 <h:panelGrid> 以及第 4 行的 <h:outputLabel>。PF 复用了部分 JSF 标签并对其进行了增强:第 21 行的 <p:commandButton>。 接下来,我们看到 PF 格式化标签:第 2 行的 <p:panel>、第 13 行的 <p:outputPanel> 以及第 23 行的 <p:dialog>。最后,还有输入标签:第 5 行的 <p:spinner>。

让我们结合视图来分析这段代码:

  • 在 [1] 中,第 2 行使用 <p:panel> 标签创建的组件,
  • 在 [2] 中,第 6 行和第 7 行通过组合 <p:outputLabel> 和 <p:spinner> 标签创建的输入字段,
  • 在 [3] 中,第 21 行使用 <p:commandButton> 标签创建的 POST 按钮,
  • 在 [4] 中,第 23–25 行生成的对话框,
  • 在 [5] 中,是一个用于容纳两个组件的不可见容器。它由第 13 行中的 <p:outputPanel> 标签创建。

让我们分析以下实现 AJAX 操作的代码:


           <h:outputLabel for="ajaxspinner" value="Ajax Spinner: " />
           <p:outputPanel>
                   <p:spinner id="ajaxspinner" value="#{spinnerController.number5}">
                      <p:ajax update="ajaxspinnervalue" process="@this" />
               </p:spinner>
               <h:outputText id="ajaxspinnervalue" value="#{spinnerController.number5}"/>
</p:outputPanel>

此代码生成以下视图:

  • 第 1 行:显示文本 [1]。它同时作为 id 为 ajaxspinner 的组件的标签(用于 id 属性)。该组件即第 3 行中的组件(id 属性),
  • 第 3–5 行:显示组件 [2]。该组件是一个与模型 #{spinnerController.number5} 关联的输入/显示组件(value 属性),
  • 第 6 行:显示组件 [3]。该组件是一个显示组件,关联到模型 #{spinnerController.number5}value 属性),
  • 第 4 行:<p:ajax> 标签为旋转按钮添加了 AJAX 行为。每次旋转按钮的值发生变化时,都会向 #{spinnerController.number5} 模型发送一个包含该值的 POST 请求(process="@this" 属性)。 操作完成后,页面将被更新(update 属性)。该属性的值为页面上某个组件的 ID,本例中即第 6 行中的组件。update 属性所指向的组件随后将根据模型进行更新。该模型仍是 #{spinnerController.number5},即旋转按钮的当前值。因此,字段 [3] 的值将跟随字段 [2] 的输入。

这是一种 AJAX 行为,AJAX 是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)的首字母缩写。一般而言,AJAX 行为的工作原理如下:

  • 浏览器显示一个包含 JavaScript 代码(即 AJAX 中的“J”)的 HTML 页面。该页面的元素构成一个名为 DOM(文档对象模型)的 JavaScript 对象,
  • 服务器托管着生成该页面的 Web 应用程序,
  • 在[1]中,页面上发生了一个事件。例如,加载图标发生了变化。该事件由JavaScript处理,
  • 在 [2],JavaScript 向 Web 应用程序发送一个 POST 请求。该请求以异步方式发送(即 AJAX 中的“A”)。用户可以继续操作该页面。页面不会被冻结,但在必要时可以冻结。POST 请求根据提交的值更新页面模型,此处模型为 #{spinnerController.number5}
  • 在 [3],Web 应用程序将 XML(AJAX 中的 X)或 JSON(JavaScript 对象表示法)响应发回给 JavaScript,
  • 在 [4] 中,JavaScript 使用此响应更新 DOM 的特定区域,此处为 id=ajaxspinnervalue 的区域。

在使用 JSF 和 PrimeFaces 时,JavaScript 代码由 PrimeFaces 生成。该库依赖于 jQuery JavaScript 库。同样,PrimeFaces 组件也依赖于 jQuery UI(用户界面)组件库中的组件。因此,jQuery 构成了 PrimeFaces 的基础。

让我们回到我们的示例,现在来看来自 [Submit] 按钮的 POST 请求:

与该 POST 请求相关的代码如下:


<p:commandButton value="Submit" update="display" oncomplete="dialog.show()" />
    
    <p:dialog header="Values" widgetVar="dialog" showEffect="fold" hideEffect="fold">
        <h:panelGrid id="display" columns="2" cellpadding="5">
            <h:outputText value="Value 1: " />
            <h:outputText value="#{spinnerController.number1}" /> 
            
            <h:outputText value="Value 2: " />
            <h:outputText value="#{spinnerController.number2}" /> 
            
            <h:outputText value="Value 3: " />
            <h:outputText value="#{spinnerController.number3}" /> 
            
            <h:outputText value="Value 4: " />
            <h:outputText value="#{spinnerController.number4}" /> 
            
            <h:outputText value="Value 5: " />
            <h:outputText value="#{spinnerController.number5}" /> 
        </h:panelGrid>
     </p:dialog>
                
</h:form>
  • 第 1 行:POST 请求由第 1 行的按钮触发。在 PrimeFaces 中,触发 POST 请求的标签默认通过 AJAX 调用实现。这就是为什么这些标签具有 `update` 属性,用于指定在收到服务器响应后需要更新的区域。在此处,待更新的区域是第 4 行的 panelGrid。因此,当 POST 响应返回时,该区域将使用提交到模型的值进行更新。 然而,这些值被封装在一个默认隐藏的对话框中。正是第 1 行中的 oncomplete 属性将其显示出来。该事件发生在 POST 处理结束时。该属性的值是 JavaScript 代码。在此,我们显示 id=dialog 的对话框,即第 3 行(widgetVar 属性)中定义的那个,
  • 第 3 行:我们可以看到对话框的各种属性。您需要通过实验来了解它们的功能。

我们之前提到了模型,但尚未展示。如下所示:

public class SpinnerController {

    private int number1;
    private double number2;
    private int number3;
    private int number4;
    private int number5;

    // getters and setters
...
}

一般而言,您可以按照以下步骤操作:

  • 确定您想要使用的 PrimeFaces 组件,
  • 研究其示例。PrimeFaces 的示例编写精良且通俗易懂。

5.4. 第一个 PrimeFaces 项目:mv-pf-01

让我们使用 NetBeans 构建一个 Maven Web 项目:

  • [1, 2, 3]:创建一个类型为 [Web Application] 的 Maven 项目,
  • [4]:服务器将使用 Tomcat,
  • 在 [5] 中,生成的项目,
  • 在 [6] 中,删除 [index.jsp] 文件和 Java 包,
  • 在 [7, 8] 中:在项目属性中,我们添加了对 Java Server Faces 的支持,
  • 在 [9] 中,在 [组件] 选项卡中,选择 PrimeFaces 组件库。NetBeans 还支持其他组件库:ICEFaces 和 RichFaces。
  • 在 [10] 中,生成的项目。在 [11] 中,请注意对 PrimeFaces 的依赖。

简而言之,PrimeFaces 项目是一个标准 JSF 项目,其中添加了对 PrimeFaces 的依赖。仅此而已。

既然我们已经理解了这一点,接下来我们将修改 [pom.xml] 文件,以便支持最新版本的库:


    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.1.8</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.primefaces</groupId>
      <artifactId>primefaces</artifactId>
      <version>3.3</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <repositories>
    <repository>
      <id>jsf20</id>
      <name>Repository for library Library[jsf20]</name>
      <url>http://download.java.net/maven/2/</url>
    </repository>
    <repository>
      <id>primefaces</id>
      <name>Repository for library Library[primefaces]</name>
      <url>http://repository.primefaces.org/</url>
    </repository>
</repositories>

第 26–30 行:请注意 PrimeFaces 的 Maven 仓库。完成这些更改后,构建项目以开始下载依赖项。随后您将获得该项目 [12]。

现在,让我们尝试重现我们所研究的示例。[index.html] 页面将变为如下内容:


<?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">
  <h:head>
    <title>Spinner</title>
  </h:head>
  <h:body>
    <!-- form -->
    <h:form>
      <p:panel header="Spinners">
        <h:panelGrid id="grid" columns="2" cellpadding="5">
          <h:outputLabel for="spinnerBasic" value="Basic Spinner: " />
          <p:spinner id="spinnerBasic" value="#{spinnerController.number1}"/>
          <h:outputLabel for="spinnerStep" value="Step Factor: " />
          <p:spinner id="spinnerStep" value="#{spinnerController.number2}" stepFactor="0.25"/>
          <h:outputLabel for="minmax" value="Min/Max: " />
          <p:spinner id="minmax" value="#{spinnerController.number3}" min="0" max="100"/>
          <h:outputLabel for="prefix" value="Prefix: " />
          <p:spinner id="prefix" prefix="$" min="0" value="#{spinnerController.number4}"/>
          <h:outputLabel for="ajaxspinner" value="Ajax Spinner: " />
          <p:outputPanel>
            <p:spinner id="ajaxspinner" value="#{spinnerController.number5}">
              <p:ajax update="ajaxspinnervalue" process="@this" />
            </p:spinner>
            <h:outputText id="ajaxspinnervalue" value="#{spinnerController.number5}"/>
          </p:outputPanel>
        </h:panelGrid>
      </p:panel>
      <p:commandButton value="Submit" update="display" oncomplete="dialog.show()" />
      <!-- dialog box -->
      <p:dialog header="Values" widgetVar="dialog" showEffect="fold" hideEffect="fold">
        <h:panelGrid id="display" columns="2" cellpadding="5">
          <h:outputText value="Value 1: " />
          <h:outputText value="#{spinnerController.number1}" /> 
          <h:outputText value="Value 2: " />
          <h:outputText value="#{spinnerController.number2}" /> 
          <h:outputText value="Value 3: " />
          <h:outputText value="#{spinnerController.number3}" /> 
          <h:outputText value="Value 4: " />
          <h:outputText value="#{spinnerController.number4}" /> 
          <h:outputText value="Value 5: " />
          <h:outputText value="#{spinnerController.number5}" /> 
        </h:panelGrid>
      </p:dialog>
    </h:form>
  </h:body>
</html>

别忘了第 5 行,该行声明了 PrimeFaces 标签库的命名空间。将作为页面模板的 Bean 添加到项目中:

  

该 Bean 如下所示:


package beans;
 
import javax.faces.bean.RequestScoped;
import javax.faces.bean.ManagedBean;
 
@ManagedBean
@RequestScoped
public class SpinnerController {
 
  // model
  private int number1;
  private double number2;
  private int number3;
  private int number4;
  private int number5;
 
  // getters and setters
  ...
}

该类是一个 Bean(第 6 行),且具有请求作用域(第 7 行)。由于未指定名称,该 Bean 将采用类名的首字母小写形式:spinnerController

运行项目后,我们将得到以下结果:

 

我们刚刚演示了如何测试一个来自 PrimeFaces 网站的示例。所有示例都可以通过这种方式进行测试。

接下来,我们将仅关注某些 PrimeFaces 组件。首先,我们将重新审视之前使用 JSF 学习的示例,并将某些 JSF 标签替换为 PrimeFaces 标签。页面的外观会略有变化;它们将具备 AJAX 行为,但相关的 Bean 无需更改。在接下来的每个示例中,我们将仅展示页面的 XHTML 代码及相关截图。 建议读者亲自测试这些示例,以找出 JSF 页面与 PF 页面之间的差异。

5.5. 示例 mv-pf-02:事件处理程序 – 国际化 – 页面导航

本项目是对 JSF 项目 [mv-jsf2-02](第 2.4 节第 41 页)的移植:

NetBeans 项目结构如下:

[index.html] 页面如下:


<?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="#{changeLocale.locale}">
    <h:head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </h:head>
    <body>
      <h:form id="formulaire">
        <h:panelGrid columns="2">
          <p:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}" ajax="false"/>
          <p:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}" ajax="false"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
        <p:commandLink value="#{msg['welcome.page1']}" action="page1" ajax="false"/>
      </h:form>
    </body>
  </f:view>
</html>

在第 15、16 和 19 行中,<h:commandLink> 标签已被替换为 <p:commandLink> 标签。该标签默认具有 AJAX 行为,可通过设置 ajax="false" 属性将其禁用。因此,此处的 <p:commandLink> 标签的行为与 <h:commandLink> 标签相同:点击这些链接时,页面将重新加载。

5.6. 示例 mv-pf-03:使用 Facelets 进行页面布局

本项目演示了如何使用示例 [mv-jsf2-09](第 2.11 节)中的 Facelets 模板创建 XHTML 页面:

 

NetBeans 项目如下:

  • 在 [1] 中,JSF 项目的配置文件,
  • 在[2]中,XHTML页面,
  • 在 [3] 中,语言切换的支持 Bean,
  • 在 [4] 中,消息文件,
  • 在 [5] 中,依赖项。

项目页面基于 [layout.xhtml] 页面:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <f:view locale="#{changeLocale.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: 600px">
          <tr>
            <td colspan="2" bgcolor="#ccccff">
              <ui:include src="entete.xhtml"/>
            </td>
          </tr>
          <tr>
            <td style="width: 100px; height: 200px" 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>
  • 第 9 行:一个 <f:view> 标签包裹了整个页面,以利用其提供的国际化功能,
  • 第 15 行:一个带有表单 ID 的表单该表单构成页面的主体。在此主体内,仅有一个动态部分,即第 28–30 行。页面中可变的部分将插入在此处:
  • 上方的框选区域将通过 AJAX 调用进行更新。为了标识该区域,我们将其包含在由 <p:outputPanel> 标签生成的 PrimeFaces 容器中(第 27 行)。该容器被命名为 `content`(id 属性)。由于它位于名为 `form` 的表单容器内,因此该动态区域的完整名称为 `form:content`。 第一个冒(:)表示从文档根节点开始,然后进入名为“form”的容器,再进入名为“content”的容器。AJAX 的一大挑战在于如何为 AJAX 调用要更新的区域命名。最简单的方法是查看接收到的 HTML 页面的源代码:

1
2
3
            <td><span id="formulaire:contenu">
                  <h2>Contenu</h2></span>
</td>

在上文中,我们可以看到 <h:outputPanel> 标签生成了一个 HTML 标签 <span>。在此示例中,相对名称 form:content(不带前导冒号)和完整名称 :form:content(带前导冒号)都指向同一个对象。

请注意,用于更新动态区域的 AJAX 调用(<p:commandButton>、<p:commandLink>)将具有 update=":form:content" 属性。

[index.xhtml] 页面是该项目显示的唯一页面:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <ui:fragment rendered="#{requestScope.page1 || requestScope.page2==null}">
        <ui:include src="page1.xhtml"/>
      </ui:fragment>
      <ui:fragment rendered="#{requestScope.page2}">
        <ui:include src="page2.xhtml"/>
      </ui:fragment>
    </ui:define>
  </ui:composition>
</html>
  • 第 8 行:[index.xhtml] 的模板就是我们刚才讨论过的 [layout.xhtml] 页面。
  • 第 9 行,这是由 [index.html] 更新的内容 ID 区域。该区域包含两个片段:
    • 第 11 行的 [page1.xhtml] 片段;
    • 第 14 行的 [page2.xhtml] 片段。

这两个片段互斥。

  • 第 10 行:如果请求中的 page1 属性设置为 true,或者 page2 属性不存在,则显示 [page1.xhtml] 片段。这适用于首次请求,因为此时请求中不会包含这两个属性中的任何一个。在这种情况下,将显示 [page1.xhtml] 片段,
  • 第 11 行:如果请求中 page2 属性设置为 true,则显示 [page2.xhtml] 片段

[page1.xhtml] 片段内容如下:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
 
  <body>
    <h:panelGrid columns="2">
      <p:commandLink value="#{msg['page1.langue1']}" actionListener="#{changeLocale.setFrenchLocale}" ajax="true" update=":formulaire:contenu"/>
      <p:commandLink value="#{msg['page1.langue2']}" actionListener="#{changeLocale.setEnglishLocale}" ajax="true" update=":formulaire:contenu"/>
    </h:panelGrid>
    <h1><h:outputText value="#{msg['page1.titre']}" /></h1>
     <p:commandLink value="#{msg['page1.lien']}" update=":formulaire:contenu">
      <f:setPropertyActionListener value="#{true}" target="#{requestScope.page2}" />  
    </p:commandLink>
  </body>
</html>

并显示以下内容:

  • 第 11 行和第 12 行:两个用于切换语言的链接。这两个链接会触发 AJAX 调用(ajax=true)。这是默认值。因此,您无需添加 ajax=true 属性。今后我们也不会这样做。请注意,这两个链接会更新 :form:content 区域(update 属性),即上文中突出显示的区域,
  • 第 15 行:一个 AJAX 导航链接,同样用于更新 :form:content 区域,
  • 第 16 行:我们使用 <h:setPropertyActionListener> 标签将请求中的 page2 属性设置为 true。这将导致 [page2.xhtml] 片段(下文第 6 行)在 [index.xhtml] 页面中显示:

  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <ui:fragment rendered="#{requestScope.page1 || requestScope.page2==null}">
        <ui:include src="page1.xhtml"/>
      </ui:fragment>
      <ui:fragment rendered="#{requestScope.page2}">
        <ui:include src="page2.xhtml"/>
      </ui:fragment>
    </ui:define>
</ui:composition>

片段 [page2.xhtml] 与此类似:

[page2.xhtml] 的代码如下:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
 
  <body>
    <h1><h:outputText value="#{msg['page2.entete']}"/></h1>
    <p:commandLink value="#{msg['page2.lien']}" update=":formulaire:contenu">
      <f:setPropertyActionListener value="#{true}" target="#{requestScope.page1}" />  
    </p:commandLink>
  </body>
</html>

通过这个示例,我们将牢记以下几点以备后用:

  • 我们将使用 [layout.xhtml] 模板作为页面模板,
  • 动态区域将通过 id:form:content 标识,并通过 AJAX 调用进行更新。

5.7. 示例 mv-pf-04:输入表单

本项目是 JSF2 项目 [mv-jsf2-03](参见第 2.5 节)的移植版本:

NetBeans 项目结构如下:

上文中的 [1] 处是该项目的 XHTML 页面。布局由前文提到的 [layout.xhtml] 模板提供。[index.xhtml] 页面是该项目的唯一页面。它显示在 :form:content 区域中。其代码如下:


<?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:include src="page1.xhtml"/>
    </ui:define>
  </ui:composition>
</html>

它仅显示片段 [page1.xhtml]。这相当于示例 [mv-jsf2-03] 中讨论的表单。请回顾,该示例的目的是介绍 JSF 输入标签。此处已将这些标签替换为 PrimeFaces 标签。

PanelGrid

为了对 [page1.xhtml] 中的元素进行格式化,我们使用 <p:panelGrid> 标签。例如,对于两个语言链接:


<!-- languages -->
    <p:panelGrid columns="2">
      <p:commandLink value="#{msg['form.langue1']}" actionListener="#{changeLocale.setFrenchLocale}" update=":formulaire:contenu"/>
      <p:commandLink value="#{msg['form.langue2']}" actionListener="#{changeLocale.setEnglishLocale}" update=":formulaire:contenu"/>
    </p:panelGrid>

这将生成以下输出:

 

<p:panelGrid> 标签的另一种形式如下:


<p:panelGrid>
 
      <f:facet name="header">  
        <p:row>  
          <p:column colspan="3"><h:outputText value="#{msg['form.titre']}"/></p:column>  
        </p:row>  
        <p:row>  
          <p:column><h:outputText value="#{msg['form.headerCol1']}"/></p:column>  
          <p:column><h:outputText value="#{msg['form.headerCol2']}"/></p:column>  
          <p:column><h:outputText value="#{msg['form.headerCol3']}"/></p:column>  
        </p:row>  
      </f:facet>        
 
      <p:row>
        <p:column>
          <h:outputText value="inputText"/>
        </p:column>
        <p:column>
          <h:outputLabel for="inputText" value="#{msg['form.loginPrompt']}" />  
          <p:inputText id="inputText" value="#{form.inputText}"/>
        </p:column>
        <p:column>
          <h:outputText id="inputTextValue" value="#{form.inputText}"/>
        </p:column>
      </p:row>
...
     <f:facet name="footer">
        <p:row>
          <p:column colspan="3">
            <div align="center">
              <p:commandButton value="#{msg['form.submitText']}" update=":formulaire:contenu"/>
            </div>
          </p:column>
        </p:row>
      </f:facet>    
</p:panelGrid>

表格的行和列由 <p:row> 和 <p:column> 标签标识。

第 3–12 行定义了表格的表头:

 

第 14–25 行定义了表格的一行:

 

第 27–35 行定义了表格的页脚:

 

inputText


      <p:row>
        <p:column>
          <h:outputText value="inputText"/>
        </p:column>
        <p:column>
          <h:outputLabel for="inputText" value="#{msg['form.loginPrompt']}" />  
          <p:inputText id="inputText" value="#{form.inputText}"/>
        </p:column>
        <p:column>
          <h:outputText id="inputTextValue" value="#{form.inputText}"/>
        </p:column>
</p:row>
 

密码


<p:row>
        <p:column>
          <h:outputText value="inputSecret"/>
        </p:column>
        <p:column>
          <h:outputLabel for="inputSecret" value="#{msg['form.passwdPrompt']}"/>
          <p:password id="inputSecret" value="#{form.inputSecret}" feedback="true"   
               promptLabel="#{msg['form.promptLabel']}" weakLabel="#{msg['form.weakLabel']}"  
               goodLabel="#{msg['form.goodLabel']}" strongLabel="#{msg['form.strongLabel']}" />  
        </p:column>
        <p:column>
          <h:outputText id="inputSecretValue" value="#{form.inputSecret}"/>
        </p:column>
      </p:row>

第 7 行:feedback=true 属性会提供有关密码强度的反馈 [1]。

inputTextArea


<p:row>
        <p:column>
          <h:outputText value="inputTextArea"/>
        </p:column>
        <p:column>
          <h:outputLabel for="inputTextArea" value="#{msg['form.descPrompt']}"/>
          <p:editor id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
        </p:column> 
        <p:column>
          <h:outputText id="inputTextAreaValue" value="#{form.inputTextArea}"/>
        </p:column>
      </p:row>

第 7 行:<p:editor> 标签会显示一个富文本编辑器,允许您对文本进行格式设置(字体、大小、颜色、对齐方式等)。发送到服务器的内容是所输入文本的 HTML 代码 [2]。

selectOneListBox


<p:row>
        <p:column>
          <h:outputText value="selectOneListBox"/>
        </p:column>
        <p:column>
          <h:outputLabel for="selectOneListBox1" value="#{msg['form.selectOneListBox1Prompt']}"/>
          <p:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}">
            <f:selectItem itemValue="1" itemLabel="un"/>
            <f:selectItem itemValue="2" itemLabel="deux"/>
            <f:selectItem itemValue="3" itemLabel="trois"/>
          </p:selectOneListbox>
        </p:column>
        <p:column>
          <h:outputText id="selectOneListBox1Value" value="#{form.selectOneListBox1}"/>
        </p:column>
      </p:row>
 

下拉菜单


<p:row>
        <p:column>
          <h:outputText value="selectOneMenu"/>
        </p:column>
        <p:column>
          <h:outputLabel for="selectOneMenu" value="#{msg['form.selectOneMenuPrompt']}"/>
          <p:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
            <f:selectItem itemValue="1" itemLabel="un"/>
            <f:selectItem itemValue="2" itemLabel="deux"/>
            <f:selectItem itemValue="3" itemLabel="trois"/>
            <f:selectItem itemValue="4" itemLabel="quatre"/>
            <f:selectItem itemValue="5" itemLabel="cinq"/>
          </p:selectOneMenu>
        </p:column>
        <p:column>
          <h:outputText id="selectOneMenuValue" value="#{form.selectOneMenu}"/>
        </p:column>
      </p:row>
 

selectManyMenu


<p:row>
        <p:column>
          <h:outputText value="selectManyMenu"/>
        </p:column>
        <p:column>
          <h:outputLabel for="selectManyMenu" value="#{msg['form.selectManyMenuPrompt']}"/>
          <p:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
            <f:selectItem itemValue="1" itemLabel="un"/>
            <f:selectItem itemValue="2" itemLabel="deux"/>
            <f:selectItem itemValue="3" itemLabel="trois"/>
            <f:selectItem itemValue="4" itemLabel="quatre"/>
            <f:selectItem itemValue="5" itemLabel="cinq"/>
          </p:selectManyMenu>
          <p:commandLink value="#{msg['form.buttonRazText']}" actionListener="#{form.clearSelectManyMenu()}" update=":formulaire:selectManyMenu" style="margin-left: 10px"/>
        </p:column>
        <p:column>
          <h:outputText id="selectManyMenuValue" value="#{form.selectManyMenuValue}"/>
        </p:column>
      </p:row>
 

第 14 行:请注意,[Reset] 链接会对 :form:selectManyMenu 字段执行 AJAX 更新,该字段对应第 6 行中的组件。但需要注意的是,在 AJAX POST 过程中,所有表单值都会被提交。因此,整个模型都会被更新。然而,在此模型中,仅 :form:selectManyMenu 字段会被更新

selectBooleanCheckbox


<p:row>
        <p:column>
          <h:outputText value="selectBooleanCheckbox"/>
        </p:column>
        <p:column>
          <h:outputLabel for="selectBooleanCheckbox" value="#{msg['form.selectBooleanCheckboxPrompt']}"/>
          <p:selectBooleanCheckbox id="selectBooleanCheckbox" value="#{form.selectBooleanCheckbox}"/>
        </p:column>
        <p:column>
          <h:outputText id="selectBooleanCheckboxValue" value="#{form.selectBooleanCheckbox}"/>
        </p:column>
      </p:row>
 

selectManyCheckbox


<p:row>
        <p:column>
          <h:outputText value="selectManyCheckbox"/>
        </p:column>
        <p:column>
          <h:outputLabel for="selectManyCheckbox" value="#{msg['form.selectManyCheckboxPrompt']}"/>
          <p:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
            <f:selectItem itemValue="1" itemLabel="rouge"/>
            <f:selectItem itemValue="2" itemLabel="bleu"/>
            <f:selectItem itemValue="3" itemLabel="blanc"/>
            <f:selectItem itemValue="4" itemLabel="noir"/>
          </p:selectManyCheckbox>
        </p:column>
        <p:column>
          <h:outputText id="selectManyCheckboxValue" value="#{form.selectManyCheckboxValue}"/>
        </p:column>
      </p:row>
 

单选按钮


<p:row>
        <p:column>
          <h:outputText value="selectOneRadio"/>
        </p:column>
        <p:column>
          <h:outputLabel for="selectOneRadio" value="#{msg['form.selectOneRadioPrompt']}"/>
          <p:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}" >
            <f:selectItem itemValue="1" itemLabel="voiture"/>
            <f:selectItem itemValue="2" itemLabel="vélo"/>
            <f:selectItem itemValue="3" itemLabel="scooter"/>
            <f:selectItem itemValue="4" itemLabel="marche"/>
          </p:selectOneRadio>
        </p:column>
        <p:column>
          <h:outputText id="selectOneRadioValue" value="#{form.selectOneRadio}"/>
        </p:column>
      </p:row>
 

5.8. 示例:mv-pf-05:动态列表

本项目是 JSF2 项目 [mv-jsf2-04](参见第 2.6 节)的移植版本:

Image

与前一个项目相比,本项目未引入任何新的 PrimeFaces 标签。因此,我们将不再对此进行说明。该示例已收录在文档网站上供读者查阅的示例列表中。

5.9. 示例:mv-pf-06:导航 – 会话 – 异常处理

该项目是 JSF2 项目 [mv-jsf2-05](参见第 2.7 节)的移植版本:

同样,本示例并未引入任何新的 PrimeFaces 标签。我们仅对上文标出的链接表进行说明:


<p:panelGrid columns="6">
  <p:commandLink value="1" action="form1?faces-redirect=true" ajax="false"/>
  <p:commandLink value="2" action="#{form.doAction2}" ajax="false"/>
  <p:commandLink value="3" action="form3?faces-redirect=true" ajax="false"/>
  <p:commandLink value="4" action="#{form.doAction4}" ajax="false"/>
  <p:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}" ajax="false"/>
  <p:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}" ajax="false"/>
</p:panelGrid>
  • 所有链接都设置了 ajax=false 属性。因此,页面会正常加载。
  • 请注意第 2 行和第 4 行,了解如何执行重定向。

5.10. 示例:mv-pf-07:输入数据的验证与转换

本项目是 JSF2 项目 [mv-jsf2-06](参见第 2.8 节)的移植版本:

Image

该应用程序引入了两个新标签,即 <p:messages> 标签:


<p:messages globalOnly="true"/>

Image

以及 <p:message> 标签:


<p:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<p:message for="saisie1" styleClass="error"/>

与 JSF 的 <h:message> 标签相比,PF 的 <p:message> 标签引入了以下变化:

  • 错误消息的外观有所不同 [1],
  • 错误的输入字段会被红色边框包围 [2]。

5.11. 示例:mv-pf-08:与组件状态变化相关的事件

本项目是 JSF2 项目 [mv-jsf2-07] 的移植版本(参见第 2.9 节):

Image

JSF 项目引入了监听器listener)的概念。PrimeFaces 对监听器的管理方式则有所不同。

在 JSF 中:

1
2
3
4
5
6
7
        <!-- line 1 -->
        <h:outputText value="#{msg['combo1.prompt']}"/>
        <h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
          <f:selectItems value="#{form.combo1Items}"/>
        </h:selectOneMenu>
        <h:panelGroup></h:panelGroup>
<h:outputText value="#{form.combo1}"/>

使用 PrimeFaces:

<h:outputText value="#{msg['combo1.prompt']}"/>
        <p:selectOneMenu id="combo1" value="#{form.combo1}" styleClass="combo">
          <f:selectItems value="#{form.combo1Items}"/>
          <p:ajax update=":formulaire:combo2"/>  
        </p:selectOneMenu>
        <h:panelGroup></h:panelGroup>
        <h:outputText value="#{form.combo1}"/>

        <h:outputText value="#{msg['combo2.prompt']}"/>
        <p:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
          <f:selectItems value="#{form.combo2Items}"/>
        </p:selectOneMenu>
  • 第 2 行:不带 valueChangeListener 属性的 <h:selectOneMenu> 标签,
  • 第 4 行:<p:ajax> 标签为其父级 <h:selectOneMenu> 标签添加了 AJAX 行为。默认情况下,它会响应 combo1 列表的“值变化”事件。当发生此事件时,其所属表单的值将通过 AJAX 调用提交至服务器。因此,模型随之更新。 我们使用这个新模型来更新标识为 combo2 的下拉列表(第 10 行)。请注意,在第 4 行,AJAX 调用并未执行任何模型方法。在此处这是没有必要的。我们只是希望通过提交输入的值来更新模型。

5.12. 示例:mv-pf-09:辅助输入

该项目展示了 PrimeFaces 特有的输入标签,这些标签有助于输入特定类型的数据:

5.12.1. NetBeans 项目

NetBeans 项目如下:

该项目的价值在于:

  • 它所显示的单页 [index.html],
  • 以及该页面的模板 [Form.java]。

5.12.2. 该模板

该表单包含四个输入字段,与以下模板相关联:


package forms;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.faces.bean.RequestScoped;
import javax.faces.bean.ManagedBean;
 
@ManagedBean
@SessionScoped
public class Form implements Serializable {
 
  private Date calendrier;
  private Integer slider = 100;
  private Integer spinner = 1;
  private String autocompleteValue;
 
  public Form() {
  }
 
  public List<String> autocomplete(String query) {
    ...
  }
  // getters and setters
...
}

这四个输入与第14至17行的字段相关联。

5.12.3. 表单

表单如下:


<?xml version='1.0' encoding='UTF-8' ?>
<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">
      <h2><h:outputText value="#{msg['app.titre']}"/></h2>
      <p:growl id="messages" autoUpdate="true"/>
      <p:panelGrid columns="3" columnClasses="col1,col2,col3,col4">
        <h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
        <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
 
        <!-- calendar -->
              ...
 
        <!-- slider -->
              ...
 
        <!-- spinner -->
              ...
 
        <!-- autocomplete -->
              ...
 
      </p:panelGrid>
    </ui:define>
  </ui:composition>
</html>

让我们来看看这四个输入字段。

5.12.4. 日历

<p:calendar> 标签允许您从日历中选择日期。该标签支持多种属性。


<h:outputText value="#{msg['calendar.prompt']}"/>
        <p:calendar id="calendrier" value="#{form.calendrier}" pattern="dd/MM/yyyy" timeZone="Europe/Paris"/>
        <h:outputText id="calendrierValue" value="#{form.calendrier}">
          <f:convertDateTime pattern="dd/MM/yyyy" type="date" timeZone="Europe/Paris"/>
        </h:outputText>

第 2 行指定日期应以“dd/mm/yyyy”格式显示,且时区为巴黎。当您将光标置于输入框中时,会显示一个日历:

 

5.12.5. 滑块

<p:slider> 标签允许您通过沿滑块拖动来输入一个整数:

 

该标签的代码如下:


        <h:outputText value="#{msg['slider.prompt']}"/>
        <h:panelGrid columns="1" style="margin-bottom:10px">  
          <p:inputText id="slider" value="#{form.slider}" required="true" requiredMessage="#{msg['slider.required']}" validatorMessage="#{msg['slider.invalide']}">  
            <f:validateLongRange minimum="100" maximum="200"/>
          </p:inputText>
          <p:slider for="slider" minValue="100" maxValue="200"/>  
        </h:panelGrid>  
<h:outputText id="sliderValue" value="#{form.slider}"/>
  • 第 3 行:这是一个标准的 <p:inputText> 标签,允许您输入一个整数。该值也可以通过滑块输入,
  • 第 4 行:<p:slider> 标签与 <p:inputText> 输入标签相关联(for 属性)。我们为其设置了最小值和最大值。

5.12.6. 旋转按钮

我们已经介绍过这个组件:


        <h:outputText value="#{msg['spinner.prompt']}"/>
        <p:spinner id="spinner" min="1" max="12" value="#{form.spinner}" required="true" requiredMessage="#{msg['spinner.required']}" validatorMessage="#{msg['spinner.invalide']}">
          <f:validateLongRange minimum="1" maximum="12"/>
        </p:spinner>
<h:outputText id="spinnerValue" value="#{form.spinner}"/>

第 3 行:旋转按钮允许您输入 1 到 12 之间的整数。您可以直接在旋转按钮的输入框中输入数字,也可以使用箭头按钮增加或减少输入的数值。

 

5.12.7. 自动完成

自动完成功能是指在输入框中输入前几个字符后,系统会通过下拉列表显示建议选项,用户可从中进行选择。当下拉列表内容过多时,通常会使用该组件来替代下拉列表。例如,若要提供法国城市的下拉列表,涉及的城市数量可能多达数千个。此时,若允许用户输入城市名称的前三个字符,系统便能根据这些字符,向用户提供以这些字符开头的精简城市列表。

 

该组件的代码如下:


        <h:outputText value="#{msg['autocomplete.prompt']}"/>
        <p:autoComplete value="#{form.autocompleteValue}" completeMethod="#{form.autocomplete}" required="true" requiredMessage="#{msg['autocomplete.required']}"/>
        <h:outputText id="autocompleteValue" value="#{form.autocompleteValue}"/>
        <h:panelGroup/>
        <h:panelGroup>
        <center><p:commandLink value="#{msg['valider']}" update="formulaire:contenu"/></center>
        </h:panelGroup>
<h:panelGroup/>

第 2 行中的 <p:autoComplete> 标签用于启用自动完成功能。这里需要关注的参数是 completeMethod 属性,其值是模型中一个方法的名称,该方法负责根据用户输入的字符生成建议。该方法定义如下:


  public List<String> autocomplete(String query) {
    List<String> results = new ArrayList<String>();
 
    for (int i = 0; i < 10; i++) {
      results.add(query + i);
    }
 
    return results;
}

  • 第 1 行:该方法接收用户在输入字段中输入的字符串作为参数。它返回一个建议列表,
  • 第4–6行:利用作为参数接收的字符,并向每个字符添加0到9之间的一个数字,构建一个包含10个建议项的列表。

5.12.8. <p:growl> 标签

<p:growl> 标签可作为 <p:messages> 标签的替代方案,用于显示表单错误信息。


      <p:growl id="messages" autoUpdate="true"/>

在上例中,未使用 id 属性。autoUpdate=true 属性表示每次表单 POST 时,都必须刷新错误消息列表。

假设我们提交以下表单 [1]:

  • 在 [2] 中,<p:growl> 标签随后会显示与错误输入相关的错误信息。

5.13. 示例:mv-pf-10: dataTable - 1

本项目引入了 <p:dataTable> 标签,用于显示数据列表

Image

5.13.1. NetBeans 项目

NetBeans 项目如下:

该项目的吸引力在于:

  • 项目所展示的单页 [index.html],
  • 该页面的 [Form.java] 模板,以及 [Person] Bean。

5.13.2. 消息文件

[messages_fr.properties] 文件内容如下:


app.titre=intro-08
app.titre2=DataTable - 1
submit=Valider
personnes.headers.id=Id
personnes.headers.nom=Nom
personnes.headers.prenom=Pr\u00e9nom
layout.hautdepage=Primefaces en fran\u00e7ais
layout.menu=Menu fran\u00e7ais
layout.basdepage=ISTIA, universit\u00e9 d'Angers
form.langue1=Fran\u00e7ais
form.langue2=Anglais
form.noData=La liste des personnes est vide
form.listePersonnes=Liste de personnes
form.action=Action

5.13.3. 该模型

[Person] Bean 表示一个人:


package forms;
 
import java.io.Serializable;
 
public class Personne implements Serializable{
  // data
  private int id;
  private String nom;
  private String prénom;
  
  // manufacturers
  public Personne(){
    
  }
  
  public Personne(int id, String nom, String prénom){
    this.id=id;
    this.nom=nom;
    this.prénom=prénom;
  }
  
  // toString
  public String toString(){
    return String.format("Personne[%d,%s,%s]", id,nom,prénom);
  }
  
  // getter and setters
...
}

[index.xhtml] 页面的模板是以下 [Form] 类:


package forms;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
 
@ManagedBean
@SessionScoped
public class Form implements Serializable{
 
  // model
  private List<Personne> personnes;
  private int personneId;
 
  // manufacturer
  public Form() {
    // initialization of the list of persons
    personnes = new ArrayList<Personne>();
    personnes.add(new Personne(1, "dupont", "jacques"));
    personnes.add(new Personne(2, "durand", "élise"));
    personnes.add(new Personne(3, "martin", "jacqueline"));
  }
 
  public void retirerPersonne() {
...
  }
  
  // getters and setters
...  
}
  • 第 9-10 行:该 Bean 具有会话作用域,
  • 第 18–24 行:构造函数创建了一个包含三人的列表,该列表将在不同请求之间保持持久化,
  • 第 15 行:要从列表中移除的人员的 ID,
  • 第 26–28 行:delete 方法。

5.13.4. 表单

表单如下所示 [index.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
      <p:dataTable value="#{form.personnes}" var="personne" emptyMessage="#{msg['form.noData']}">
        <f:facet name="header">  
          #{msg['form.listePersonnes']}  
        </f:facet>  
        <p:column>
          <f:facet name="header">
            #{msg['personnes.headers.id']}
          </f:facet>
          #{personne.id}
        </p:column>
        <p:column>
          <f:facet name="header">
            #{msg['personnes.headers.nom']}
          </f:facet>
          #{personne.nom}
        </p:column>
        <p:column>
          <f:facet name="header">
            #{msg['personnes.headers.prenom']}
          </f:facet>
          #{personne.prénom}
        </p:column>
        <p:column>
          <f:facet name="header">
            #{msg['form.action']}
          </f:facet>
          <p:commandLink value="Retirer" action="#{form.retirerPersonne}" update=":formulaire:contenu">
            <f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
          </p:commandLink>
        </p:column>
      </p:dataTable>
    </ui:define>
  </ui:composition>
</html>

这将生成以下视图(如下所示):

  • 第 12 行:生成上文框中所示的表格。value 属性指定表格显示的集合,在此示例中即模型中的人员列表。emptyMessage 属性是可选的,用于指定列表为空时显示的消息。默认情况下,该消息为“未找到记录”。在此处,它将显示为:
 
  • 第 13–15 行:生成标题 [1],
  • 第 16–21 行:生成列 [2],
  • 第 22–27 行:生成列 [3],
  • 第 28–33 行:生成列 [4],
  • 第 34–41 行:生成列 [5]。

[Remove] 链接允许您将某人从列表中移除。在第 [38] 行,[Form].removePerson 方法执行此任务。它需要知道要移除的人的 ID。该 ID 在第 39 行提供。在第 38 行,我们使用了 action 属性。在其他情况下,我们曾使用过 actionListener 属性。 我不确定是否完全理解这两个属性的功能差异。但在实践中,我们注意到:由 <setPropertyActionListener> 标签设置的属性是在 action 属性指定的方法执行之前设置的,而 actionListener 属性则并非如此。简而言之,只要需要向被调用的操作传递参数,就必须使用 action 属性。

删除人员的代码如下:


...
@ManagedBean
@SessionScoped
public class Form implements Serializable{
 
  // model
  private List<Personne> personnes;
  private int personneId;
 
  public void retirerPersonne() {
    // search for the selected person
    int i = 0;
    for (Personne personne : personnes) {
      // current person = selected person?
      if (personne.getId() == personneId) {
        // delete the current person from the list
        personnes.remove(i);
        // we're done
        break;
      } else {
        // next person
        i++;
      }
    }
  }
...  
}

5.14. 示例:mv-pf-11: 数据表 - 2

该项目包含一个表格,用于显示数据列表,其中可以选中某一行:

在表格中选中某一行时,系统会在 POST 请求期间将该行信息发送至模型。因此,不再需要为每位用户单独设置 [删除] 链接,仅需一个针对整个表格的链接即可。

该 NetBeans 项目与前一个项目基本相同,仅有细微差异:表单及其模型。[index.xhtml] 表单如下所示:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
      <p:dataTable value="#{form.personnes}" var="personne" emptyMessage="#{msg['form.noData']}"
                   rowKey="#{personne.id}"  selection="#{form.personneChoisie}" selectionMode="single">
        ...
      </p:dataTable>
      <p:commandLink value="Retirer" action="#{form.retirerPersonne}" update=":formulaire:contenu"/>
    </ui:define>
  </ui:composition>
</html>
  • 第 13 行:selectionMode 属性允许您在单选和多选模式之间进行选择。此处,我们选择仅选择一行,
  • 第 13 行:rowkey 属性指定了显示元素的一个属性,以便对其进行唯一选择。此处,我们选择了被选中人员的 ID
  • 第 13 行:selection 属性指定了将接收被选人员引用值的模型属性。 得益于前面的 rowkey 属性,可以在服务器端计算出被选中人员的引用。我们并不了解所用方法的具体细节。可以设想,系统会顺序遍历集合以查找与所选 rowkey 对应的元素。这意味着,如果将 rowkey选择关联的方法较为复杂,那么该方法便无法使用,

话虽如此,[Form].removePerson 方法的演变如下:


...
 
@ManagedBean
@SessionScoped
public class Form implements Serializable {
 
  // model
  private List<Personne> personnes;
  private Personne personneChoisie;
 
  // manufacturer
  public Form() {
  ...
  }
 
  public void retirerPersonne() {
    // we remove the chosen person
    personnes.remove(personneChoisie);
  }
 
  // getters and setters
...
}
  • 第 9 行:每次 POST 请求时,第 9 行中的引用会初始化为第 8 行列表中选定人员的引用,
  • 在第 18 行:这简化了删除行操作。我们在前一个示例中进行的搜索是使用 <dataTable> 标签实现的。

5.15. 示例: mv-pf-12: dataTable - 3

该项目与前一个项目类似。视图方面尤为相似:

Image

NetBeans 项目与上一个完全相同,仅有几处细微差异,我们将逐一说明。[index.xhtml] 表单的更改如下:


...
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
      <p:dataTable value="#{form.personnes}" var="personne" emptyMessage="#{msg['form.noData']}"
                   selectionMode="single" selection="#{form.personneChoisie}">
  ...
      </p:dataTable>
      <p:commandLink value="Retirer" action="#{form.retirerPersonne}" update=":formulaire:contenu"/>
    </ui:define>
  </ui:composition>
</html>
  • 第 6 行:rowkey 属性已被移除,但 selection 属性保留。rowkeyselection 属性之间的关联现在通过一个类来建立。第 5 行中 value 属性的值 现在持有 PrimeFaces SelectableDataModel<T> 接口的一个实例。该模型的 [Form].getPeople 方法更新如下:

  public DataTableModel getPersonnes() {
    return new DataTableModel(personnes);
}

因此,该项目中新增了一个 Bean:

该 Bean 如下所示:


package forms;
 
import java.util.List;
import javax.faces.model.ListDataModel;
import org.primefaces.model.SelectableDataModel;
 
public class DataTableModel extends ListDataModel<Personne> implements SelectableDataModel<Personne> {
 
  // manufacturers
  public DataTableModel() {
  }
 
  public DataTableModel(List<Personne> personnes) {
    super(personnes);
  }
 
  @Override
  public Object getRowKey(Personne personne) {
    return personne.getId();
  }
 
  @Override
  public Personne getRowData(String rowKey) {
    // list of persons
    List<Personne> personnes = (List<Personne>) getWrappedData();
    // the key is an integer 
    int key = Integer.parseInt(rowKey);
    // search for the selected person
    for (Personne personne : personnes) {
      if (personne.getId() == key) {
        return personne;
      }
    }
    // we found nothing
    return null;
  }
}
  • 第 7 行:该类是 SelectableDataModel 接口的一个实例。至少有两个类实现了该接口:ListDataModel(其构造函数接受列表作为参数)和 ArrayDataModel(其构造函数接受数组作为参数)。在此,我们的 Bean 继承了 ListDataModel 类,
  • 第 13–15 行:构造函数将我们管理的人员列表作为参数。该参数会被传递给父类,
  • 第 18 行:getRowKey 方法承担了已被移除的 rowkey 属性的作用。它必须返回一个能唯一标识人员的对象,在本例中即人员的 ID,
  • 第 23 行:getRowData 方法必须根据 rowkey 返回所选对象。因此,此处它根据 ID 返回一个人员对象。通过这种方式获得的引用将被赋值给 dataTable 标签中 selection 属性的目标对象,即此处的 selection="#{form.personneChoisie}"该方法的参数是用户所选对象的 rowkey,以字符串形式呈现,
  • 第 24–35 行:返回 ID 与接收值匹配的 person 对象的引用。该引用将被赋值给 [Form].personneChoisie 模型。因此,[retirerPersonne] 方法保持不变:

  public void retirerPersonne() {
    // on enlève la personne choisie
    personnes.remove(personneChoisie);
  }

**rowkey**selection 属性之间的关系并非简单的属性到对象关系(即 **rowkey****selection**)时,应使用此方法。

5.16. 示例:mv-pf-13: dataTable - 4

该项目与前一个项目类似,只是选择要删除的人员的方法有所不同:

Image

上图中,我们可以看到对象是通过上下文菜单(右键单击)选中的。系统会要求确认删除:

 

[index.xhtml] 表单的变化如下:


...
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
 
      <!-- title -->
      <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
 
      <!-- contextual menu -->
      <p:contextMenu for="personnes">  
        <p:menuitem value="#{msg['form.supprimer']}" onclick="confirmation.show()"/>
      </p:contextMenu>  
 
      <!-- dialog box -->
      <p:confirmDialog widgetVar="confirmation" message="#{msg['form.suppression.confirmation']}"  
                       header="#{msg['form.suppression.message']}" severity="alert" >                   
        <p:commandButton value="#{msg['form.supprimer.oui']}" update=":formulaire:contenu"                          action="#{form.retirerPersonne}" oncomplete="confirmation.hide()"/>
        <p:commandButton value="#{msg['form.supprimer.non']}" onclick="confirmation.hide()" type="button" />                
      </p:confirmDialog>  
 
      <!-- dataTable-->
      <p:dataTable id="personnes" value="#{form.personnes}" var="personne" emptyMessage="#{msg['form.noData']}"
                   selection="#{form.personneChoisie}" selectionMode="single">
        ...
      </p:dataTable>
    </ui:define>
  </ui:composition>
</html>
  • 第 9–11 行:为第 21 行(id 属性)中的 dataTable 定义一个上下文菜单(for 属性)。当您右键单击“人员”表时,该上下文菜单会显示出来,
  • 第 10 行:我们的菜单仅包含一个选项(menuItem 标签)。当点击此选项时,onclick 属性中的 JavaScript 代码将被执行。JavaScript 代码 [confirmation.show()] 会显示第 14 行(widgetVar 属性)中的对话框。具体内容如下:
  • 第 14 行:message 属性显示 [3],header 属性显示 [1],severity 属性显示图标 [2],
  • 第 16 行:显示 [4]。点击后,该人员将被删除(action 属性),随后对话框关闭(oncomplete 属性)。oncomplete 属性是 JavaScript 代码,在服务器端操作执行完成后执行,
  • 第 17 行:显示 [5]。点击后,对话框关闭,且该人员不会被删除。

5.17. 示例:mv-pf-14:dataTable - 5

本项目演示了在执行 AJAX 调用后,可以从服务器接收响应。为此,我们使用了 AJAX 调用的 oncomplete 属性:

 

[index.xhtml] 表单的更改如下:


...
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
...
<!-- dialog box 1 -->
      <p:confirmDialog widgetVar="confirmation" ... >                   
        <p:commandButton value="#{msg['form.supprimer.oui']}" update=":formulaire:contenu" action="#{form.retirerPersonne}" oncomplete="handleRequest(xhr, status, args);confirmation.hide()"/>
        <p:commandButton ... />                
      </p:confirmDialog>  
 
      <!-- Javascript -->
      <script type="text/javascript">  
        function handleRequest(xhr, status, args) {  
          // erreur ?
          if(args.msgErreur) {  
            alert(args.msgErreur);  
          }  
        }  
      </script> 
...
      </p:dataTable>
    </ui:define>
  </ui:composition>
</html>
  • 第 7 行:oncomplete 属性调用第 13–18 行中的 JavaScript 函数,
  • 第 13 行:方法签名必须是这个。args 是一个字典,服务器端模型可以为其赋值,
  • 第 15 行:我们检查 args 字典中是否存在名为 'msgError' 的属性。如果存在,则将其显示(第 16 行)。

在模型中,[removePerson] 方法的实现如下:


public void retirerPersonne() {
    // suppression aléatoire
    int i = (int) (Math.random() * 2);
    if (i == 0) {
      // on enlève la personne choisie
      personnes.remove(personneChoisie);
    } else {
      // on renvoie une erreur
      String msgErreur = Messages.getMessage(null, "form.msgErreur", null).getSummary();
      RequestContext.getCurrentInstance().addCallbackParam("msgErreur", msgErreur);
    }
  }
  • 第 3 行:生成一个随机数 0 或 1,
  • 第 4–6 行:如果结果为 0,则将用户选定的人从人员列表中移除,
  • 第9行:否则,构建一条国际化错误消息:

form.msgErreur=La personne n'a pu \u00eatre supprim\u00e9e. Veuillez r\u00e9essayer ult\u00e9rieurement.
form.msgErreur_detail=La personne n'a pu \u00eatre supprim\u00e9e. Veuillez r\u00e9essayer ult\u00e9rieurement.
  • 第 10 行:一个复杂的语句,它将名为 'msgError' 的属性添加到我们之前提到的 args 字典中,其值由第 9 行构建的 msgError 提供。随后,[index.xhtml] 中的 JavaScript 方法会检索该属性:

      <!-- Javascript -->
      <script type="text/javascript">  
        function handleRequest(xhr, status, args) {  
          // erreur ?
          if(args.msgErreur) {  
            alert(args.msgErreur);  
          }  
        }  
</script> 

5.18. 示例:mv-pf-15:工具栏

在本项目中,我们将构建一个工具栏:

该工具栏即上图框中所示的组件。它是使用以下 XHTML 代码 [index.xhtml] 创建的:


 
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <!-- title -->
      <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
 
      <!-- toolbar-->
      <p:toolbar>
        <p:toolbarGroup align="left">  
          ...  
        </p:toolbarGroup>
        <p:toolbarGroup align="right">  
          ...  
        </p:toolbarGroup>  
      </p:toolbar>
    </ui:define>
  </ui:composition>
</html>
  • 第 15–22 行:工具栏,
  • 第 16–18 行:定义工具栏左侧的组件组,
  • 第19–21行:同上,用于定义工具栏右侧的组件。

工具栏左侧的组件如下:


        <p:toolbarGroup align="left">  
          <h:outputText value="#{msg['form.etudiant']}"/>
          <p:spacer width="50px"/>
          <p:selectOneMenu value="#{form.personneId}" effect="fade">  
            <f:selectItems value="#{form.personnes}" var="personne" itemLabel="#{personne.prénom} #{personne.nom}" itemValue="#{personne.id}"/>  
          </p:selectOneMenu>              
          <p:separator/>
          <p:commandButton id="delete-personne" icon="ui-icon-trash" action="#{form.supprimerPersonne}" update=":formulaire:contenu"/>  
          <p:tooltip for="delete-personne" value="#{msg['form.delete.personne']}"/>  
</p:toolbarGroup>

它们显示以下视图:

  • 第2行:显示 [1],
  • 第 3 行:显示 30 像素的空格 [2],
  • 第4–6行:显示一个包含人员列表的下拉列表 [3],
  • 第 7 行:显示一个分隔符 [4],
  • 第 8 行:显示一个按钮 [5],用于删除下拉列表中选中的人员。该按钮带有图标。这些图标来自 jQuery UI。您可以在以下网址 [http://jqueryui.com/themeroller/] [6] 找到图标列表:
  • 要查找图标名称,只需将鼠标悬停在其上方。该名称随后将用于 <commandButton> 组件的 icon 属性中,例如 icon="ui-icon-trash"。请注意,上文给出的名称是 .ui-icon-trash,而在 icon 属性中需去掉该名称开头的点
  • 第 9 行:为按钮创建工具提示(for 属性)。当鼠标悬停在按钮上时,工具提示信息会显示出来 [7]。

与这些组件关联的模板如下:


package forms;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
 
@ManagedBean
@SessionScoped
public class Form implements Serializable {
 
  // model
  private List<Personne> personnes;
  private int personneId;
 
  // manufacturer
  public Form() {
    // initialization of the list of persons
    personnes = new ArrayList<Personne>();
    personnes.add(new Personne(1, "dupont", "jacques"));
    personnes.add(new Personne(2, "durand", "élise"));
    personnes.add(new Personne(3, "martin", "jacqueline"));
  }
 
  public void supprimerPersonne() {
    // search for the selected person
    int i = 0;
    for (Personne personne : personnes) {
      // current person = selected person?
      if (personne.getId() == personneId) {
        // delete the current person from the list
        personnes.remove(i);
        // we're done
        break;
      } else {
        // next person
        i++;
      }
    }
  }
 
  // getters and setters
  ...
}

工具栏右侧的组件如下:


<p:toolbar>
        <p:toolbarGroup align="left">  
          ... 
        </p:toolbarGroup>
        <p:toolbarGroup align="right">  
          <p:menuButton value="#{msg['form.options']}">  
            <p:menuitem id="menuitem-francais" value="#{msg['form.francais']}" actionListener="#{changeLocale.setFrenchLocale}" update=":formulaire"/>  
            <p:menuitem id="menuitem-anglais" value="#{msg['form.anglais']}" actionListener="#{changeLocale.setEnglishLocale}" update=":formulaire"/>  
          </p:menuButton>  
        </p:toolbarGroup>  
      </p:toolbar>

它们显示如下视图:

 
  • 第6–9行:一个菜单按钮。其中包含菜单选项,
  • 第7行:将表单切换为法语的选项,
  • 第8行:切换为英语的选项。

5.19. 结论

现在我们已经掌握了足够的知识,可以将示例应用程序移植到 PrimeFaces 上。我们仅介绍了大约十五个组件,而该库中包含的组件超过 100 个。建议读者直接在 PrimeFaces 网站上搜索任何缺失的组件。

5.20. 使用 Eclipse 进行测试

这些 Maven 项目可在示例网站 [1] 上获取:

导入 Eclipse 后,即可运行 [2]。在 [3] 中选择 Tomcat。随后,项目内容将显示在 Eclipse 的内置浏览器中 [3]。