vlambda博客
学习文章列表

读书笔记《digital-java-ee-7-web-application-development》构建JSF表单

Chapter 3. Building JSF Forms

 

“这就是全部。事物在许多不同层面上的实际运作方式。最终,当然,设计定义了我们的许多体验。”

 
  --Sir Jony Ive, Senior Vice President of Design, Apple USA

JavaServer Faces 是面向组件的 Web 应用程序框架的一个示例,与 Java EE 8 MVC 不同(参见 Chapter 9, Java EE MVC 框架)、WebWork 或 Apache Struts,它们被称为面向请求的 Web 应用程序框架。

面向请求的框架是信息流从 Web 请求到响应的框架。此类框架为您提供了高于 javax.servlet.http.HttpServletRequest 和 javax.servlet.http.HttpServletResponse 的能力和结构对象,但没有特殊的用户界面组件。在额外的帮助下,应用程序用户必须对参数和属性到数据实体模型的映射进行编程。因此,开发人员必须编写解析逻辑。

了解面向组件的框架(例如 JSF)有其批评者,这一点很重要。对代码的快速检查类似于独立客户端(如 Java Swing 甚至 JavaFX)中的组件,但完全相同的 HttpServletRequest 和 HttpServletResponse< /code> 潜伏在幕后。因此,一个称职的 JSF 开发人员必须了解 Servlet API 和底层 servlet 范围。这在 2004 年是一个有效的批评,在数字营销时代,数字开发人员不仅要了解 Servlet,而且我们可以假设他们愿意学习其他技术,例如 JavaScript。基于从Chapter 2中获得的知识,JavaServer Faces Lifecycle< /span>,我们将学习如何构建 JSF 表单。

Create, Retrieve, Update, and Delete


在这一章节中,我们将解决一个使用JSF 的日常问题。 Java EE 框架和企业应用程序旨在解决数据输入问题。与使用不同架构和非功能性要求(例如可伸缩性、性能、无状态和最终一致性)构建的社交网络软件不同,Java EE 应用程序是为有状态工作流设计的,如下面的屏幕截图所示:

读书笔记《digital-java-ee-7-web-application-development》构建JSF表单

用于创建联系人详细信息的页面视图的屏幕截图

前面的屏幕截图是 JSF 应用程序 jsf-crud,它显示了创建联系人详细信息表单。

提醒一下,您可以在本书的源代码中找到该应用程序的完整代码。

通常,企业应用程序从网络用户那里获取信息,将其存储在数据存储中,并允许检索和编辑这些信息。通常有一个删除用户信息的选项。在软件工程中,我们将此习惯用法称为创建、检索、更新和删除 (CRUD)。

Note

什么构成了用户和客户数据的实际删除,这最终会影响到企业主,他们面临着遵守定义隐私和数据保护的当地和国际法律的压力。

A basic create entity JSF form

让我们创建一个基本的 表单,该表单捕获用户的姓名、电子邮件地址、和出生日期。我们将使用 HTML5 编写此代码,并利用 Bootstrap 的 用于现代 CSS 和 JavaScript。请参阅 http://getbootstrap.com/getting-started/。

以下是 JSF Facelet 视图,createContact.xhtml:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:p="http://xmlns.jcp.org/jsf/passthrough" xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <meta charset="utf-8"/>
        <title>Demonstration Application </title>
        <link href="#{request.contextPath}/resources/styles/bootstrap.css" rel="stylesheet"/>
        <link href="#{request.contextPath}/resources/styles/main.css" rel="stylesheet"/>
    </h:head>

    <h:body>
        <div class="main-container">
            <div class="header-content">
                <div class="navbar navbar-inverse" role="navigation">
                  ...
                </div>
            </div><!-- headerContent -->

            <div class="mainContent">
                <h1> Enter New Contact Details </h1>

                <h:form id="createContactDetail" styleClass="form-horizontal" p:role="form">
                    ...
                </h:form>

            </div><!-- main-content -->

            <div class="footer-content">

            </div> <!-- footer-content -->
        </div> <!-- main-container -->
    </h:body>
    <script src="#{request.contextPath}/resources/javascripts/jquery-1.11.0.js"></script>
    <script src="#{request.contextPath}/resources/javascripts/bootstrap.js"></script>
    <script src="#{request.contextPath}/resources/app/main.js"> </script>
</html>

你应该已经认识<h :head> 和 <h:body> JSF 自定义标签。由于类型是 Facelet 视图 (*.xhtml),因此该文档实际上必须像 XML 文档一样具有良好的格式。您应该已经注意到某些 HTML5 元素标签,例如 <meta> 是关闭并完成的; XHTML 文档必须在 JSF 中格式正确。

Tip

始终关闭 XHTML 元素

典型的电子商务应用程序具有标准 HTML 的网页,其中包含 <meta>、<link> 和 < code class="literal"><br> 标签。在 XHTML 和 Facelet 视图中,网页 设计者通常保持打开和挂起状态的这些标签必须关闭。 Extensible Mark-up Language (XML) 不那么宽容,而且从 XML 派生的 XHTML 必须格式正确。

新的 <h:form> 标记是对应于 HTML 表单元素的 JSF 自定义标记。 JSF 表单元素共享 HTML 伙伴的许多属性。你可以看到 id 属性是一样的。但是,我们在 JSF 中使用 styleClass 属性而不是 class 属性,因为在 Java 中,java.lang.Object.getClass() 方法是保留的,因此不能被覆盖。

Tip

什么是 JSF 请求上下文路径表达式?

样式表、JavaScript 和其他资源的链接周围的奇怪 标记是表达式语言:#{request.上下文路径}。表达式引用确保将 Web 应用程序路径添加到 JSF 资源的 URL。 Bootstrap CSS 本身依赖于特定文件夹中的字体字形图标。 JSF 图像、JavaScript 模块文件和 CSS 文件应放在 web 根目录的资源文件夹中。

p:role 属性 是 JSF passthrough 属性的示例,它通知 JSF 渲染工具包通过键和值发送到渲染输出。 passthrough 属性是 JSF 2.2 中的关键新增功能,它是 Java EE 7 的一部分。它们允许 JSF 与最新的 HTML5 框架(例如 Bootstrap 和 Foundation (http://foundation.zurb.com/)。

以下是 呈现的 HTML 源输出的摘录:

<h1> Enter New Contact Details </h1>
<form id="createContactDetail" name="createContactDetail" method="post" action="/jsf-crud-1.0-SNAPSHOT/createContactDetail.xhtml" class="form-horizontal" enctype="application/x-www-form-urlencoded" role="form">
<input type="hidden" name="createContactDetail" value="createContactDetail" />

JSF 是在 Twitter 中创建 Bootstrap 之前实现的。 JSF 设计者如何改进框架以兼容最近的 HTML5、CSS3 和 JavaScript 创新?这就是 passthrough 属性有帮助的地方。通过使用 URI http://xmlns.jcp.org/ 在 XHTML 中声明 XML 命名空间jsf/直通。我们可以为页面视图启用该功能。如您所见,属性名称和值 role="form" 只是简单地传递到输出。 passthrough 属性允许 JSF 轻松处理 HTML5 功能,例如文本输入字段中的占位符,我们将从现在开始利用这些功能。

Tip

如果您是 Web 开发的新手,您可能会害怕看起来过于复杂的标记。有很多很多的 DIV HTML 元素,它们通常是由页面设计人员和界面开发人员创建的。这是历史效应,也是 HTML 和 Web 随时间演变的方式。 2002年的做法对2016年没有影响,建议大家阅读附录C,< em>敏捷性能——在数字团队中工作。

让我们以 a 更深入的了解 < h:form> 并填写缺失的详细信息。这是提取的代码:

<h:form id="createContactDetail" styleClass="form-horizontal" p:role="form">
  <div class="form-group">
    <h:outputLabel for="title" class="col-sm-3 control-label">
        Title</h:outputLabel>
    <div class="col-sm-9">
      <h:selectOneMenu class="form-control" id="title" value="#{contactDetailController.contactDetail.title}">
        <f:selectItem itemLabel="--" itemValue="" />
        <f:selectItem itemValue="Mr" />
        <f:selectItem itemValue="Mrs" />
        <f:selectItem itemValue="Miss" />
        <f:selectItem itemValue="Ms" />
        <f:selectItem itemValue="Dr" />
      </h:selectOneMenu>
    </div>
  </div>
  <div class="form-group">
    <h:outputLabel for="firstName" class="col-sm-3 control-label">
      First name</h:outputLabel>
    <div class="col-sm-9">
      <h:inputText class="form-control" value="#{contactDetailController.contactDetail.firstName}" id="firstName" placeholder="First name"/>
    </div>
  </div>
  ... Rinse and Repeat for middleName and lastName ...
  <div class="form-group">
    <h:outputLabel for="email" class="col-sm-3 control-label">
    Email address </h:outputLabel>
    <div class="col-sm-9">
      <h:inputText type="email" class="form-control" id="email" value="#{contactDetailController.contactDetail.email}" placeholder="Enter email"/>
    </div>
  </div>
  <div class="form-group">
    <h:outputLabel class="col-sm-3 control-label">
       Newsletter
    </h:outputLabel>
    <div class="col-sm-9 checkbox">
      <h:selectBooleanCheckbox id="allowEmails" value="#{contactDetailController.contactDetail.allowEmails}">
          Send me email promotions
      </h:selectBooleanCheckbox>
    </div>
  </div>
  <h:commandButton styleClass="btn btn-primary" action="#{contactDetailController.createContact()}" value="Submit" />
</h:form>

这个 表单是使用Bootstrap CSS 样式构建的,但是我们将忽略无关的详细信息并完全专注于 JSF 自定义标签。

<h:selectOneMenu> 标记是对应于 HTML 表单选择元素的 JSF 自定义标记。 <f:selectItem> 标记对应于 HTML 表单选择选项元素。 <h:inputText> 标签对应于 HTML 表单输入元素。 <h:selectBooleanCheckbox> 标签是一种特殊的自定义标签,用于表示只有一个复选框元素的 HTML 选择。最后,<h:commandButton> 代表 HTML 表单提交元素。

The JSF HTML output label

<h:outputLabel> 标签 以下列方式呈现 HTML 表单标签元素:

<h:outputLabel for="firstName" class="col-sm-3 control-label"> First name</h:outputLabel>

开发人员应该更喜欢将此标记与其他相关的 JSF 表单输入标记结合使用,因为特殊的 for 属性针对元素的正确糖化标识符。

这是渲染的输出:

<label for="createContactDetail:firstName" class="col-sm-3 control-label"> First name</label>

我们可以 使用 value 属性编写标签,如下所示:

<h:outputLabel for="firstName" class="col-sm-3 control-label" value="firstName" />

此时也可以利用国际化;所以为了说明,我们可以重写页面内容如下:

<h:outputLabel for="firstName" class="col-sm-3 control-label" value="${myapplication.contactForm.firstName}" />

有关 JSF 中的国际化和资源包的更多信息,请参阅 附录 A,带有 HTML5、资源和面孔流的 JSF。让我们继续输入字段。

The JSF HTML input text

<h:inputText> 标签 允许以文本一样的形式输入数据,如图在以下代码中:

<h:inputText class="form-control" value="#{contactDetailController.contactDetail.firstName}" id="firstName" placeholder="First name"/>

value 属性表示一种 JSF 表达式语言,其线索是求值字符串以散列字符开头。表达式语言值引用了一个名为 contactDetailController 的作用域支持 bean ContactDetailController.java。在 JSF 2.2 中,现在有方便的属性来支持 HTML5 支持,因此标准的 id、class 和 placeholder 属性按预期工作。

渲染的输出如下:

<input id="createContactDetail:firstName" type="text" name="createContactDetail:firstName" class="form-control" />

请注意,加糖的 createContactDetails:firstName 标识符与 <h:outputLabel> 标记的输出相匹配。

The JSF HTML select one menu

<h:selectOneMenu> 标记生成单个选择下拉列表。事实上,它是选择类型自定义标签系列 的一部分。请参阅下一节中的 JSF HTML 选择布尔复选框。

在代码中,我们有以下代码:

<h:selectOneMenu class="form-control" id="title" value="#{contactDetailController.contactDetail.title}">
     <f:selectItem itemLabel="--" itemValue="" />
     <f:selectItem itemValue="Mr" />
     <f:selectItem itemValue="Mrs" />
     <f:selectItem itemValue="Miss" />
     <f:selectItem itemValue="Ms" />
     <f:selectItem itemValue="Dr" />
</h:selectOneMenu>

<h:selectOneMenu> 标记对应于 HTML 表单选择标记 value 属性同样是 JSF 表达式语言字符串。

在 JSF 中,我们可以使用另一个新的自定义标签,<f:selectItem>,它添加了一个子 javax.faces.component.UISelectItem 到最近的父 UI 组件。 <f:selectItem> 标签接受 itemLabel 和 itemValue 属性.如果您设置了 itemValue 而未指定 itemLabel,则该值将成为标签。因此,对于第一项,选项设置为 -- 但提交给表单的值是一个空白字符串,因为我们想提示用户有一个值应该选择的。

呈现的 HTML 输出具有指导意义,如下所示:

<select id="createContactDetail:title" size="1" name="createContactDetail:title" class="form-control">  
  <option value="" selected="selected">--</option>
  <option value="Mr">Mr</option>
  <option value="Mrs">Mrs</option>
  <option value="Miss">Miss</option>
  <option value="Ms">Ms</option>
  <option value="Dr">Dr</option>
</select>

The JSF HTML select Boolean checkbox

<h:selectBooleanCheckbox> 自定义标签是选择的一种特殊情况,其中只有一个 item用户可以选择。通常,在 Web 应用程序中,您会在最终条款和条件表单中找到此类元素,或者通常在电子商务应用程序的营销电子邮件部分中找到此类元素。

在目标托管 bean 中,唯一的值必须是布尔类型,如以下代码所示:

<h:selectBooleanCheckbox for="allowEmails" value="#{contactDetailController.contactDetail.allowEmails}"> Send me email promotions
</h:selectBooleanCheckbox>

此自定义标记的渲染输出如下所示:

<input id="createContactDetail:allowEmails" type="checkbox" name="createContactDetail:allowEmails" />

The JSF HTML command button

<h:commandButton> 自定义标签对应于 HTML 表单提交元素。它们接受 JSF 中的动作属性,该属性引用支持 bean 中的方法。语法再次使用 JSF 表达式语言:

<h:commandButton styleClass="btn btn-primary" action="#{contactDetailController.createContact()}" value="Submit" />

当用户按下这个 Submit 按钮时,JSF 框架会找到与 contactDetailController 然后调用无参数方法:createContact()。

Note

在表达式语言中,需要注意括号不是必需的,因为解释器或 Facelet 会自动内省含义是动作 (MethodExpression) 还是值定义 (<代码类="literal">ValueExpression)。请注意,现实世界中的大多数示例都没有将括号添加为简写形式。

value 属性表示表单 Submit 按钮的文本。我们可以用另一种方式编写标签并达到相同的结果,如下所示:

<h:commandButton styleClass="btn btn-primary" action="#{contactDetailController.createContact()}" >
    Submit
</h:commandButton>

该值取自自定义标签的正文内容。标签的渲染输出如下所示:

<input type="submit" name="createContactDetail:j_idt45" value="Submit" class="btn btn-primary" />
<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="-3512045671223885154:3950316419280637340" autocomplete="off" />

上述代码说明了 Mojarra 实现中 JSF 渲染器的输出 (https://javaserverfaces.java. net/),这是参考实现。您可以清楚地看到,渲染器在输出中写入了 HTML 提交和隐藏元素。隐藏元素捕获有关视图状态的信息,这些信息被回发到 JSF 框架(回发),从而允许它恢复视图。

最后,这是联系方式表格的截图:

读书笔记《digital-java-ee-7-web-application-development》构建JSF表单

带有附加 DOB 字段的联系人详细信息输入 JSF 表单

还有 更多的JSF 自定义标签需要考虑,您将在本章后面找到所有标签的完整列表。现在,让我们检查也称为控制器的支持 bean。

The backing bean controller

对于我们的 简单的 POJO 表单,我们需要一个支持 bean 或者,在现代JSF 开发人员用语,一个托管 bean 控制器。

以下是 ContactDetailController 的完整代码:

package uk.co.xenonique.digital;
import javax.ejb.EJB;
import javax.inject.Named;
import javax.faces.view.ViewScoped;
import java.util.List;

@Named("contactDetailController")
@ViewScoped
public class ContactDetailController {
  @EJB ContactDetailService contactDetailService;

  private ContactDetail contactDetail =
    new ContactDetail();

  public ContactDetail getContactDetail() {
      return contactDetail;
  }

  public void setContactDetail(
    ContactDetail contactDetail) {
      this.contactDetail = contactDetail;
  }

  public String createContact() {
      contactDetailService.add(contactDetail);
      contactDetail = new ContactDetail();
      return "index.xhtml";
  }

  public List<ContactDetail> retrieveAllContacts() {
      return contactDetailService.findAll();
  }
}

对于这个托管bean,让我们向您介绍几个新注释。第一个注解称为 @javax.inject.Named,它将这个 POJO 声明为 CDI 管理的 bean,同时还声明了一个 JSF 控制器。在这里,我们将显式声明托管 bean 名称的值为 contactDetailController。这实际上是托管 bean 的默认名称,所以我们可以省略它。

我们还可以写一个替代名称,如下所示:

@Named("wizard")
@ViewScoped
public class ContactDetailController { /* .. . */ }

然后,JSF 会给我们一个名为 wizard 的 bean。托管 bean 的名称有助于表达式语言语法。

Tip

当我们谈论 JSF 时,我们可以自由地将术语支持 bean 与托管 bean 互换。许多专业的 Java Web 开发人员都明白这两个术语的意思是一样的!

@javax.faces.view.ViewScoped 注解表示控制器已经确定了视图的生命周期。范围视图设计用于应用程序 数据仅保留一个 page 直到用户导航到另一个页面。一旦用户导航到另一个页面,JSF 就会销毁 bean。 JSF 从其内部数据结构中删除对视图范围 bean 的引用,并将对象留给垃圾收集器。

@ViewScoped 注释是 Java EE 7 和 JSF 2.2 中的新注释,它修复了 Faces 和 CDI 规范之间的错误。这是因为 CDI 和 JSF 是独立开发的。通过查看 Javadoc,您会发现一个较旧的注释:@javax.faces.bean.ViewScoped,它来自 JSF 2.0,不是 CDI 规范的一部分。

目前,如果您选择编写 @ViewScoped 注释控制器,您可能应该使用 @ManagedBean。我们将在本章后面解释 @ViewScoped bean。

ContactDetailController 还依赖于 Enterprise Java Bean ( EJB) 服务端点:ContactDetailService,最重要的是,有一个 bean 属性:ContactDetail。注意 getter 和 setter 方法,我们还将确保属性是 在施工期间实例化。

我们现在将注意力转向方法,如下所示:

  public String createContact() {
    contactDetailService.add(contactDetail);
    contactDetail = new ContactDetail();
    return "index.xhtml";
  }

  public List<ContactDetail> retrieveAllContacts() {
    return contactDetailService.findAll();
  }

createContact() 方法使用 EJB 创建新的联系人详细信息。它返回一个字符串,即下一个 Facelet 视图:index.xhtml。此方法由 <h:commandButton> 引用。

retrieveAllContacts() 方法调用数据服务来获取实体的列表集合。此方法将 被另一个页面引用。

Data service

控制器依赖于一个实体 bean:ContactDetail。下面是这个 bean 的代码,它已经简化了 :

package uk.co.xenonique.digital;
import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name="CONTACT")
@NamedQueries({
  @NamedQuery(name="ContactDetail.findAll",
    query = "select c from ContactDetail c " +
            "order by c.lastName, c.middleName, c.firstName")
})
public class ContactDetail {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="CONTACT_ID", nullable = false,
          insertable = true, updatable = true,
          table = "CONTACT")
  private long id;

  private String title="";
  private String firstName;
  private String middleName;
  private String lastName;
  private String email;

  @Temporal(TemporalType.DATE)
  private Date dob;

  private Boolean allowEmails;

  public long getId() { return id; }
  public void setId(long id) { this.id = id; }

  public String getTitle() { return title; }
  public void setTitle(String title) { this.title = title; }

  // Other getters and setters omitted

  // equals, hashCode, toString omitted
}

它使用 Java Persistence API (JPA) 注解来映射Java 属性到关系 数据库。

有一组注释针对实体本身声明。 @Entity 注释将此 POJO 标记为具有持久性的对象。 @Table 注解覆盖了实体的默认数据库表名——而不是 CONTACT_DETAIL,它变成了 联系方式。 @NameQueries 和 @NameQuery 注释 定义Java Persistence Query Language (JPQL)中的名称查询。

其余注释声明与数据库表列关联的元数据。 @Id 注释指定将成为主键的属性,即 id 字段。 @GenerationValue 注解声明主键是自动生成的。如果存在 0 或 null 值,JPA 提供程序会生成唯一值。此属性上的另一个注释 - @Column - 将默认数据库表列名称从 ID 重命名为 CONTACT_ID 并设置某些约束。

最后,JPA 必须使用 @Temporal 注释为字段指定日期时间类型。注释值可以是 Temporal.DATE、Temporal.TIME 或 Temporal.TIMESTAMP< /代码>。

您将在我的《Java EE 7 开发人员手册》一书中了解 JPA,其中有几个关于该主题的简洁而专门的章节。但是,这本书涉及 Web 应用程序开发。

现在应该很明显,实体 bean 可以直接以 JSF 形式使用。您还记得表单属性的 JSF 表达式语言吗?查看以下名字字段:

<h:inputText class="form-control" value="#{contactDetailController.contactDetail.firstName}" id="firstName" placeholder="First name"/>

由于 JSF 框架通过名称知道 contactDetailController,其类类型为 ContactDetailController,因此它可以在对象图中移动并确定财产。控制器有一个名为 contactDetail 的属性,属于 ContactDetail 类型,它有一个 firstName 属性。

控制器的关键要求是实体应该在提交表单时以及从表单中检索到 remembered 值的数据时实例化。让我们看一下下面的代码:

private ContactDetail contactDetail = new ContactDetail();

对于大型对象层次结构,开发人员可以利用多种可能性。在这些情况下,数据结构的延迟加载和延迟创建会有所帮助。

现在让我们看看企业服务bean,ContactDataService:

package uk.co.xenonique.digital;
import javax.ejb.Stateful;
import javax.persistence.*;
import java.util.List;

@Stateful
public class ContactDetailService {
  @PersistenceContext(unitName = "applicationDB",
      type = PersistenceContextType.EXTENDED)
  private EntityManager entityManager;

  public void add(ContactDetail contactDetail) {
    entityManager.persist(contactDetail);
  }

  public void update(ContactDetail contactDetail) {
    ContactDetail contactDetailUpdated
         = entityManager.merge(contactDetail);
    entityManager.persist(contactDetailUpdated);
  }

  public void delete(ContactDetail contactDetail) {
    entityManager.remove(contactDetail);
  }

  public List<ContactDetail> findAll() {
    Query query = entityManager.createNamedQuery(
            "ContactDetail.findAll");
    return query.getResultList();
  }
}

此类是有状态会话 EJB 的示例,它本质上是应用服务器中具有会话状态的可池化远程服务端点。有状态会话 bean 与调用客户端相关联。

ContactDetailService 依赖于 JPA 提供程序,正如我们通过 @PersistenceContext 注释注入实体管理器所看到的。请注意,我们使用的是持久化上下文的扩展种类,因为对话可以存在多个请求-响应周期。

在非扩展的持久性对话中,EntityManager 仅在存在 JTA 事务时才会存在。在 Java EE 模型中完成事务后,所有持久性 对象都与 EntityManager 分离,并且他们变得不受管理。

扩展持久性对话是 EntityManager 可以超越 Java Transaction API (JTA< /strong>) 交易。事实上,它可以在多次交易中存活下来。在这种情况下,persistence 对象不会与 EntityManager 分离;只有当数据被显式刷新或通过应用程序服务器围绕有状态会话 bean 提供的特殊状态划分时,数据才会保存到数据库中。因此,扩展的持久性上下文只能在有状态会话 bean 中使用。

有关权限和有状态会话 bean 的更多信息,请参阅我的姊妹书 Java EE 7 开发人员手册。

目前,我们应该只关注 ContactDataService 中的方法。 add() 方法在数据库中插入一条新记录。 update() 方法修改现有记录,delete() 删除记录。 findAll() 方法从底层数据库中检索所有 ContactDetail 记录。它使用命名的 JPQL 查询:Contact.findAll。

您可能想知道在用户界面中的哪里是设置 Date of Birth (DOB) 属性的 JSF 字段,如 ContactDetail 所示 实体 bean。稍后我们将添加这些字段。

JSF custom tags


正如您所见,JSF 带有大量自定义标记库。为了充分利用框架,数字开发人员应该了解它们及其能力。正如我们之前所见,标签可以分为命名空间。

The HTML render kit custom tags

JSF 2.2 中的第一组 标记与 HTML 元素的呈现有关.它们在名称空间中:http://xmlns.jcp.org/jsf/html 。 JSF框架中渲染工具包的默认实现包含javax.faces.component.UIComponent。

这是 HTML 渲染工具包标签的表格:

JSF 自定义标签

描述

<h:column>

这将呈现 javax.faces.component.UIColumn 的实例,该实例表示父 UIData 组件中的单列数据。此自定义标记用于 <h:dataTable>。

<h:commandButton>

这 呈现一个带有提交或休息类型的 HTML 输入元素。

<h:commandLink>

这 呈现一个HTML 锚元素,其执行类似于提交按钮,因此,必须在< h:form> 标签。

<h:dataTable>

这 呈现一个包含行和列的 HTML 表格,包括表格标题和表格列单元格。

<h:form>

这 呈现一个HTML 表单元素。

<h:graphicImage>

这 呈现一个HTML 图像元素。

<h:inputFile>

此 呈现具有文件类型的 HTML 表单输入元素,并允许应用程序从客户端的操作系统上传文件。

<h:inputHidden>

此 呈现具有隐藏类型的 HTML 表单输入元素。

<h:inputSecret>

此 呈现一个带有密码类型的 HTML 表单输入元素。

<h:inputText>

此 呈现一个带有文本类型的 HTML 表单输入元素。

<h:inputTextarea>

此 呈现 HTML 表单文本区域元素。

<h:link>

这 呈现一个HTML 锚元素,该元素对应用程序执行HTTP GET 请求。

<h:outputFormat>

此 标记使用格式化参数呈现参数化文本。

<h:outputLabel>

这 呈现一个 HTML 标签元素。

<h:outputLink>

这 呈现一个通常用于非JSF 应用程序链接的HTML 锚元素。

<h:outputText>

这个 标记将输出呈现给视图。

<h:message>

这将单个消息呈现到特定组件的页面。该标签允许通过资源包进行国际化。

<h:messages>

这 将一系列消息从 Faces 上下文呈现到页面。

<h:panelGrid>

此 自定义标记将组件呈现为网格。默认的 JSF 实现使用 HTML 表格元素。

<h:panelGroup>

此 自定义标记将嵌套的 JSF 标记组织到定义的组中,布局在这些组中生成并生成单个实体。

<h:selectBooleanCheckbox>

这 呈现一个带有复选框类型的 HTML 输入元素,专为布尔属性而设计。

<h:selectManyCheckbox>

这 呈现类型为复选框的 HTML 输入元素列表。

<h:selectManyListbox>

这 呈现 HTML 选择选项元素的列表。

<h:selectManyMenu>

这 呈现 HTML 选择选项元素的列表。

<h:selectOneListbox>

这 呈现HTML 选择选项元素的列表。

<h:selectOneMenu>

这 呈现 HTML 选择选项元素的列表。

<h:selectOneRadio>

这 呈现 HTML 输入元素列表,类型为 radio。

JSF HTML 标签 分为不同的种类,例如命令、输入、输出和类型,以便处理项目的选择。还有额外的标签来处理特殊情况,例如 <h:graphicImage> 来呈现 <img> 标签和<h:dataTable> 来渲染 <table> 信息。

The core JSF custom tags

核心 JSF 自定义标签添加了独立于 HTML 呈现工具包标签的特性。这些标签的 命名空间是 http://xmlns.jcp.org/jsf/core。 JSF 框架是可扩展的。如果你想要一个替代的 render kit,那么你所要做的就是添加它。 核心 JSF 自定义标签仍然有效。

以下是 JSF Core 标记表:

JSF 自定义标签

描述

<f:actionListener>

这个 注册了一个ActionListener 实例。

<f:attribute>

这 为 UIComponent 添加了一个属性与最近的父 UIComponent代码>动作。

<f:convertDateTime>

这 将DateTimeConverter 注册到UIComponent。

<f:convertNumber>

这个将NumberConverter注册到UIComponent。

<f:converter>

此 呈现一个 HTML 锚元素,其作用类似于提交按钮,因此,必须在 。

<f:facet>

这 向组件添加了一个方面。

<f:loadBundle>

这 加载一个针对当前视图的Locale 进行本地化的资源包,并将属性存储为java.util.Map< /代码>。

<f:metadata>

这 声明了此视图的元数据方面。

<f:param>

这 向UIComponent 添加了一个参数。

<f:phaseListener>

这个 向页面注册了一个 PhaseListener 实例。

<f:selectItem>

这个 为一个选择一个或选择多个组件指定一个项目。

<f:selectItems>

这 为一个选择一个或选择多个组件指定项目。

<f:setProperty-ActionListener>

这 将 ActionListener 注册到特定属性的组件。

<f:subview>

这 创建了另一个 JSF 命名上下文(参见 <f:view>)。

<f:validateDoubleRange>

这 将DoubleRangeValidator 注册到组件。

<f:validateLength>

此 将 LengthValidator 注册到组件。

<f:validateLongRange>

此 将 LongRangeValidator 注册到组件。

<f:validateRegex>

这个向组件注册了一个正则表达式验证器。如果整个模式匹配,那么它是有效的。

<f:validateRequired>

这确保提交表单时组件中的值存在。

<f:validator>

这 向组件注册了一个命名的 Validator 实例。

<f:valueChangeListener>

此 将 ValueChangeListener 注册到组件。

<f:verbatim>

此 将标记添加到 JSF 页面并允许正文内容直接传递到呈现的输出。

<f:view>

这个设置页面的JSF当前命名上下文的参数。使用此标记覆盖语言环境、编码或内容类型。

<f:viewParam>

这 将视图参数添加到构面的元数据中,以便页面有权在 GET 请求中查询参数。此标记只能在 <f:metadata> 中使用。

许多核心 JSF 标记的 目的是增强和配置 UIComponent 实例。您已经在前面的 <h:selectOneMenu> 中看到了此示例与 <f:selectItem> 标记一起使用代码示例,createContact.xhtml。 (参见 基本 JSF 表单部分)。

在大多数情况下,开发人员可以使用核心 JSF 标记向组件添加属性、侦听器、转换器、方面、参数和选择。

The template composition custom tags

模板JSF自定义标签的库为您提供了编写页面的能力与其他页面的内容。模板允许在整个 JSF 应用程序中重用和共享内容。最重要的是,可以通过 指定参数来调整模板,从而在混合中具有适应性和灵活性。这些标签的命名空间是 http://xmlns.jcp.org/jsf/facelets< /a>,它强调了幕后 Facelet 视图的技术。

以下是 JSF 2.2 中的模板标签列表:

JSF 自定义标签

描述

<ui:component>

这定义了一个模板组件并指定了该组件的文件名。

<ui:composition>

这 定义了一个页面组合,它封装了可选择使用模板的JSF 内容。

<ui:debug>

这 创建一个特殊组件并将其添加到当前页面,以允许显示调试输出。

<ui:define>

这 定义了由合成模板插入到页面中的 JSF 内容。

<ui:decorate>

这 定义了装饰JSF 页面特定区域的内容。

<ui:fragment>

这个 以类似于 <ui:composition> 标签的方式定义了一个模板片段,除了此标记保留正文之外的内容而不是丢弃它。

<ui:include>

这 包括将另一个 JSF 页面插入到当前页面中。

<ui:insert>

这 将命名内容定义插入当前页面。此标记与 <ui:define> 结合使用。

<ui:param>

此 将参数传递给使用 <ui:include> 或模板引用指定的包含文件例如 <ui:composition> 或 <ui:include>。

<ui:repeat>

这 从 bean 属性或方法迭代列表集合。此标记是循环遍历集合的另一种方法,类似于 <h:dataTable> 或 <c:forEach> .

<ui:remove>

这 从页面中删除特定的标记内容。

我们看过<ui:composition>,的操作章节中的 <ui:define> 和 <ui:insert> 2,JavaServer Faces 生命周期。我们肯定会在本书关于 JSF 的其余部分使用模板 JSF 标记。

Common attributes

JSF 标准标签共享许多公共属性。下表是一个参考,其中一些属性可用于大多数 HTML 渲染工具包标签:

属性名称

描述

id

这 指定 HTML 元素标识符。 JSF 开发人员应该每次都使用这个属性。

绑定

这 将标签绑定到托管 bean 中的组件实例。 JSF 框架将组件树中的组件引用绑定到一个作用域变量。

立即

这个指定一个布尔值,如果设置true,它会导致JSF框架跳过处理JSF 生命周期中应用请求值阶段之后的验证、转换和事件。

渲染

这个指定一个布尔值,通常默认为true,是否应该渲染组件。

必需

这 指定一个布尔值是否需要此输入元素进行输入验证。

styleClass

这 指定呈现标签的 HTML 类属性。

stylestyle

此 指定呈现标签的 HTML 样式属性。

valuevalue

这 指定一个字符串值或表达式语言引用。

现在我们已经看到了 JSF 标记,我们将回到我们的 CRUD 示例。

Displaying a list collection of objects


对于 CRUD 示例,我们经常面临在用户可以理解的有意义的上下文中显示应用程序中的数据的实际问题。最简单的方法之一是为相当简单的数据打印出项目列表。另一种方法是显示数据的表格视图。如果您的数据是树结构或图形,还有其他值得考虑的解决方案。

对于我们的案例,我们 将选择第二条路径并在表格中显示联系人详细信息列表。在 JSF 中,我们可以使用 <h:dataTable> HTML 组件。此自定义标记遍历列表中的每个对象并显示指定的值。 <h:dataTable> 组件是一个非常强大且灵活的标签,因为 Java Web 工程师可以对其进行配置,以便在各种布局中呈现自定义样式。

让我们看一下 jsf-crud 项目中的另一个 JSF Facelet 视图 index.html。提醒一下,我们正在使用 Bootstrap CSS 来设置样式。现在,这是提取的代码,如下所示:

<div class="main-content">
  ...
  <h2> List of Contact Details </h2>

  <h:dataTable id="contactTable" value="#{contactDetailController.retrieveAllContacts()}" styleClass="table-striped table-bordered user-table" var="contact">
    <h:column>
      <f:facet name="header">
        <h:outputText value="Title" />
      </f:facet>
        <h:outputText value="#{contact.title}"/>
    </h:column>
    <h:column>
        <f:facet name="header">
            <h:outputText value="First name" />
        </f:facet>
        <h:outputText value="#{contact.firstName}"/>
    </h:column>

    ... (repeat for Middle name and Last Name) ...

    <h:column>
      <f:facet name="header">
        <h:outputText value="Email" />
      </f:facet>
        <h:outputText value="#{contact.email}"/>
    </h:column>
    <h:column>
      <f:facet name="header">
        <h:outputText value="D.O.B" />
      </f:facet>
        <h:outputText value="#{contact.dob}"/>
    </h:column>
    <h:column>
      <f:facet name="header">
        <h:outputText value="Allow emails?" />
      </f:facet>
        <h:outputText value="#{contact.allowEmails}"/>
    </h:column>
  </h:dataTable>

  <hr class="subfeaturette-divider" />
</div><!-- main-content -->

您会注意到的第一个 标记是 <h:dataTable> 标记接受值属性,这是对控制器的 retrieveAllContacts() 方法的 JSF 表达式语言引用。 ContactDetailController 将此请求委托给 ContactDetailService,即我们之前看到的有状态会话 EJB。

var 属性指定 JSF 范围变量的名称,它是每次组件遍历列表集合时创建的元素。视图中元素的类型是实体 bean:ContactDetail。

styleClass 属性添加了来自 Bootstrap 框架的特定 CSS 样式,当然,每个组件都可以有一个 id 属性。

<h:dataTable> 组件需要描述 表格的列数据。

如果您想要表格的标题行,那么您必须在 < 中放置并添加一个名为 <f:facet> 的核心 JSF 标签。 h:column> 标签。必须为该标签名称指定一个特殊的名称属性,该属性具有 header 值。如果你问我:为什么我必须使用不同的 XML 命名空间来编写标签?那么我的回答是,这是 JSF 设计者预见到核心标签可以重用于其他渲染工具包的方式。因此,标签名称是 <f:facet> 而不是 <h:headerColumn>。

为了向用户显示每一行的信息,我们使用 <h:outputText> 元素。该标记接受另一种表达式语言语句,即对实体 bean 中属性的引用,例如 #{contact.firstName}。

这是 index.html 列表视图的屏幕截图:

读书笔记《digital-java-ee-7-web-application-development》构建JSF表单

CRUD 应用程序列表视图的屏幕截图

Enhanced date time entry

如果您注意到,我们忽略了添加 JSF 控件,以便用户可以将他或她的出生日期添加到 联系方式表单。假设我们有来自敏捷团队中 UX 人员的指令,并且输入必须在两个下拉列表中。企业需要两个下拉元素分别代表一个月中的几天和一年中的几个月。他们还想要该年度的文本条目。

到目前为止,我们已经在 J​​SF 之旅中介绍了一些 HTML 选择自定义标签,例如 <h:selectOneMenu> 和 <h :selectBooleanCheckbox>。现在,我们将学习如何从我们的托管 bean 以编程方式为这些标签生成数据。如果我们能提供帮助——而且我们肯定能——我们真的不想在 JSF 视图中重复编写 <f:selectItem> 31 次。

我们需要向ContactDetailController添加额外的逻辑。这些是对 JSF 托管 bean 的增强,它提供了通过表达式语言访问页面视图的方法,如下所述:

@ManagedBean(name = "contactDetailController")
@ViewScoped
public class ContactDetailController {
  // ... same as before

  public String createContact() {
      Calendar cal = Calendar.getInstance();
      cal.set(Calendar.DAY_OF_MONTH, dobDay);
      cal.set(Calendar.MONTH, dobMonth-1 );
      int year = Integer.parseInt(dobYear);
      cal.set(Calendar.YEAR, year);
      contactDetail.setDob(cal.getTime());
      contactDetailService.add(contactDetail);  
      contactDetail = new ContactDetail();
      return "index.xhtml";
  }

  // ...

  private int dobDay;
  private int dobMonth;
  private String dobYear;

  public int getDobDay() { return dobDay; }
  public void setDobDay(int dobDay) {
      this.dobDay = dobDay; }
  // ... getter and setter for dobMonth and dobYear

  private static List<Integer> daysOfTheMonth
    = new ArrayList<>();
  private static Map<String,Integer> monthsOfTheYear
    = new LinkedHashMap<>();

  static {
    for (int d=1; d<=31; ++d) {
        daysOfTheMonth.add(d);
    }

    DateFormatSymbols symbols =
            new DateFormatSymbols(Locale.getDefault());
    for (int m=1; m<=12; ++m) {
        monthsOfTheYear.put(symbols.getMonths()[m-1], m );
    }
  }

  public List<Integer> getDaysOfTheMonth() {
      return daysOfTheMonth;
  }
  public Map<String,Integer> getMonthsOfTheYear() {
      return monthsOfTheYear;
  }
}

我们将添加 三个新的 bean 属性:dobDay、dobMonth 和 dobYear 到控制器。请注意,dobYear 是一个字符串,而另外两个是整数,因为年份字段是一个文本字段。当使用整数时,前端显示的默认值是0,这会分散和混淆用户的注意力。我们希望用户看到一个空的文本字段。这些新属性有 getter 和 setter。

我们增强了 createContact() 方法以考虑三个独立字段的出生日期,并使用 java 将它们转换为 DOB 值.util.Calendar 实例。在将实体 bean 保存到数据库之前,我们将使用 java.util.Date 类型的计算值在实体上设置一个属性。

有两个 bean 属性方法,getDaysOfTheMonth() 和 getMonthsOfTheYear(),它们将返回内置的静态集合类的静态初始化器。 daysOfTheMonth 字段是从 1 到 31 的整数的列表集合,monthsOfTheYear 字段是条目的映射集合和与整数相关的字符串,表示一年中的月份。

我们使用 JDK 的 DateFormatSymbols 类来检索设置为应用程序默认语言环境的月份的长名称。

通过这些后端更改,我们可以调整 JSF 视图以添加设置申请人出生日期的功能。

以下是 JSF 视图中的更新更改,createContactDetails.xhtml:

<label class="control-label"> Your Date of Birth</label>
<div class="row my-group-border">
  <div class="col-sm-3">
    <label class="control-label" for="dobDay">Day</label>
    <div class="controls">
      <h:selectOneMenu id="dobDay" value="#{contactDetailController.dobDay}" label="Registration Day">
        <f:selectItem itemLabel="----" itemValue=""/>
        <f:selectItems value="#{contactDetailController.daysOfTheMonth}" var="day" itemLabel="#{day}" itemValue="#{day}" />
        <f:validateRequired/>
      </h:selectOneMenu>
      <h:message for="dobDay" styleClass="form-error"/>
    </div>
  </div>
  <div class="col-sm-3">
    <label class="control-label" for="dobMonth">Month</label>
    <div class="controls">
      <h:selectOneMenu id="dobMonth" value="#{contactDetailController.dobMonth}" label="Registration Month">
        <f:selectItem itemLabel="----" itemValue=""/>
        <f:selectItems value="#{contactDetailController.monthsOfTheYear}" />
        <f:validateRequired/>
      </h:selectOneMenu>
      <h:message for="dobMonth" styleClass="form-error"/>
    </div>
  </div>
  <div class="col-sm-3">
    <label class="control-label" for="dobYear">Year</label>
    <div class="controls">
      <h:inputText id="dobYear" value="#{contactDetailController.dobYear}" label="Registration Year">
        <f:validateRequired/>
      </h:inputText>
      <h:message for="dobYear" styleClass="form-error"/>
    </div>
  </div>
</div>
<h:commandButton styleClass="btn btn-primary" action="#{contactDetailController.createContact()}" value="Submit" />

好吧,希望我没有吓跑你,让你跑上山!我们在这里使用 Bootstrap CSS v3.11,所以这就是你在 HTML <div> 元素的原因indexterm"> 带有专门命名的 CSS 选择器,例如 control-label、col-sm-6 和 <代码类="literal">行。 Bootstrap 是 HTML5、CSS 和 JavaScript 的流行框架,可帮助设计人员和开发人员构建响应式网站。

作为一个组件框架,JSF 提供了封装<div> 层、CSS 和JavaScript 的基础。有一些方法可以提供帮助。首先,团队可以开发他们的自定义组件;其次,他们可以利用具有所需功能和自定义的第三方组件系统,最后,团队可以充当库编写者,从而创建自己的定制 HTML 渲染工具包。自定义组件更容易编程,我们将在 Chapter 5, < em>对话和旅程。

如果您的团队对组件库感兴趣,那么您可能需要查看供应商 解决方案,例如 Rich Faces (http://richfaces.jboss.org/) 和 尤其是 Prime Faces (http://primefaces.org/)。

让我们专注于 <h:selectOneMenu> 标签。这个来自 JSF 命名空间的 HTML 自定义标记指定了一个下拉选择列表,用户只能在其中选择一项。 value 属性引用了控制器 bean 中的一个属性。所以,第一个字段的表达语言是#{contactDetailController.dobDay}。

在父标记中,您会看到 <f:selectItem> 和 <f:selectItems> 自定义标记。 <f:selectItem> 标签定义了一个菜单项。它接受 itemLabel 和 itemValue 属性。我们可以使用它来定义一个默认的空选项。

<f:selectItems> 标签定义了许多菜单项并接受另一个值属性,即表达式语言 #{contactDetailController.daysOfTheMonth}< /代码>。此表达式引用控制器 getter 方法 getDaysOfTheMonth(),它返回 List 。我们将使用 var、itemLabel 和 itemValue 来配置这个集合的方式呈现每个菜单选项,如下所示。

<f:selectItems value="#{contactDetailController.daysOfTheMonth}" var="day" itemLabel="#{day}" itemValue="#{day}" />

就像 <h:dataTable> 标记一样,我们可以使用 var 属性定义一个 JSF 范围变量并有效地迭代通过收藏。

<f:selectMenu> 中的月份下拉菜单的标记略有不同。由于 getMonthsOfTheYear() 已经返回一个 Map 集合,因此无需提供标签的配置和价值观。自定义标签已经知道它必须呈现地图集合。

DOB 年的最后一个字段是 <h:inputText>,现在,您已经知道这些标签 工作。您可能已经注意到了一些惊喜。

<f:validateRequired>标签是一个验证自定义标签,它指定bean属性必须在提交表单的时候定义。 <h:message> 标签指定 HTML 中我们希望出现特定验证错误的区域,如下所示:

<h:message for="dobYear" styleClass="form-error"/>

<h:message> 标记接受引用 JSF HTML 表单属性的强制属性。我们可以使用 styleClass 属性设置 CSS 样式,这是 Bootstrap 的表单错误。在下一章中,我们将正确地查看验证。

这是新表单的屏幕截图:

读书笔记《digital-java-ee-7-web-application-development》构建JSF表单

联系人数据应用程序的创建页面视图的屏幕截图

Editing data


现在,让我们再添加一个 JSF index.xhtml 以允许用户编辑和删除联系人详细信息。在我们可以编辑联系人详细信息之前,我们必须将一些 JSF 链接添加到列表视图,以便 用户可以导航到编辑和删除页面。

让我们修改 index.xhtml 视图中的 <h:dataTable> 部分并添加一个额外的列。代码如下所示:

<h:dataTable id="contactTable" ... other columns as before ... <h:column>
    <f:facet name="header">
      <h:outputText value="Action" />
    </f:facet>
    <h:link styleClass="btn" outcome="editContactDetail.xhtml?id=#{contact.id}">
      <f:param name="id" value="#{contact.id}" />
      <span class="glyphicon glyphicon-edit"></span>
    </h:link>
    <h:link styleClass="btn" outcome="removeContactDetail.xhtml?id=#{contact.id}">
      <f:param name="id" value="#{contact.id}" />
      <span class="glyphicon glyphicon-trash"></span>
    </h:link>
  </h:column>

</h:dataTable>

我们有两个 <h:link> 标签生成两个指向两个新页面的 HTML 锚元素链接:editContactDetail.xhtml 和removeContactDetail.xhtml。

<h:link> 自定义标签有一个使用 JSF 导航规则生成 URL 的结果属性。 value 属性指定链接上的文本,或者您可以指定正文。该标签足够聪明,如果链接不存在,那么它将生成一个 <span> 元素。这是原型设计的有用功能。

以下是 <h:link> 的一些渲染输出:

<td>
  <a href="/jsf-crud-1.0-SNAPSHOT/editContactDetail.xhtml?id=5" class="btn">
    <span class="glyphicon glyphicon-edit"></span></a>
  <a href="/jsf-crud-1.0-SNAPSHOT/deleteContactDetail.xhtml?id=5" class="btn">
    <span class="glyphicon glyphicon-trash"></span></a>
</td>

glyphicon、glyphicon-edit 和 glyph-trash 类是标记从 Bootstrap 显示 图标按钮。

有了链接,我们现在必须允许在服务器端编辑合同细节。我们将为 ContactDetailController 调整新的属性和方法。我们将介绍的第一个属性是 id 以便我们可以跟踪数据库中联系人 ID 的主键。我们还需要 JSF 框架的 getter 和 setter。

再三考虑,允许用户取消任务会很好。因此,我们将在控制器中引入一个 cancel() 方法。我们还将添加几个方法:findByContactId() 和 editContact()。

这是 ContactDetailController 现在的以下代码:

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public class ContactDetailController {
  // ... as before ...
  private int id;

  public int getId() { return id; }
  public void setId(int id) { this.id = id; }

  public String cancel() {
      return "index.xhtml";
  }

  public void findContactById() {
    if (id <= 0) {
      String message =
        "Bad request. Please use a link from within the system.";
      FacesContext.getCurrentInstance().addMessage(null,
        new FacesMessage(
          FacesMessage.SEVERITY_ERROR, message, null));
      return;
    }

    ContactDetail item = contactDetailService.findById(id).get(0);
    if (item == null) {
      String message =
        "Bad request. Unknown contact detail id.";
      FacesContext.getCurrentInstance().addMessage(null,
        new FacesMessage(
          FacesMessage.SEVERITY_ERROR, message, null));
    }
    contactDetail = item;
    Calendar cal = Calendar.getInstance();
    cal.setTime(contactDetail.getDob());
    dobDay = cal.get(Calendar.DAY_OF_MONTH);
    dobMonth = cal.get(Calendar.MONTH)+1;
    dobYear = Integer.toString(cal.get(Calendar.YEAR));
  }

  public String editContact() {
      Calendar cal = Calendar.getInstance();
      cal.set(Calendar.DAY_OF_MONTH, dobDay);
      cal.set(Calendar.MONTH, dobMonth-1);
      int year = Integer.parseInt(dobYear);
      cal.set(Calendar.YEAR, year);
      contactDetail.setDob(cal.getTime());
      contactDetail.setId(id)
      contactDetailService.update(contactDetail);
      contactDetail = new ContactDetail();
      return "index.xhtml";
  }

  // ...
}

cancel() 方法只返回下一个视图:index.xhtml。它什么也不做,这不是代码中的错误,但实际上是目的:回到起点。

findContactById() 方法使用 id 属性使用 ContactDataService 查找联系人详细信息 EJB。此方法利用 Calendar 实例将 dob 属性与 分开dobDay、dobMonth 和 dobYear 中的 ContactDetail 实体> 属性。

javax.faces.context.FacesContext 类型是存储当前请求和响应信息的聚合对象。 FacesContext 只能使用工厂方法检索。在示例中,我们将向 Faces 响应添加一条错误消息,该消息可以显示在视图中。 javax.faces.application.FacesMessage 类型是错误验证的表示,或者它可以定义为来自外部资源包的消息资源。请参阅 附录 A、带有 HTML5、资源和面孔流的 JSF 。

editContact() 方法与 createContect() 几乎相同,因为它重构了 dob 实体中的属性。区别在于实体中的 id 属性是从控制器属性设置的:id。设置正确的主键非常重要,因为用户不想看到重复的条目。 editContect() 方法现在使用 update() 而不是 create() 调用数据库 。

现在,我们将 用一个新的命名查询来调整 ContactDetail 实体。以下是修改:

@Entity
@Table(name="CONTACT")
@NamedQueries({
  @NamedQuery(name="ContactDetail.findAll", query = "select c from ContactDetail c " + "order by c.lastName, c.middleName, c.firstName"), @NamedQuery(name="ContactDetail.findById", query = "select c from ContactDetail c where c.id = :id"),
})
public class ContactDetail { /* ... as before ... */ }

命名的 ContactDetail.findById 查询使用带有键参数的 JPQL 语句,在字符串中表示为 :id。我们现在将向 EJB 添加一个额外的方法。

这是附加的 ContactDetailService 方法,代码如下:

@Stateful
public class ContactDetailService {
  // ... as before ...
  
  public List<ContactDetail> findById(Integer id) {
    Query query = entityManager.createNamedQuery("ContactDetail.findById").setParameter("id", id);
    return query.getResultList();
  }
}

findById() 方法使用命名查询并调用 JPA 查询以检索 ContactDetail 的列表集合元素。根据定义,集合中应该只有一个元素,因为我们通过主键进行查询。

随着后端的这些变化,我们只需要对页面视图进行一些更改,这与 createContactDetail.xhtml 几乎相同。

这是 Facelet 视图的摘录,editContactDetail.xhtml:

<h:body>
  <f:metadata>
    <f:viewParam name="id" value="#{contactDetailController.id}" />
    <f:event type="preRenderView" listener="#{contactDetailController.findContactById()}"/>
  </f:metadata>

  ...

  <div class="main-content">
    <h1> Edit Contact Details </h1>
    <h:form id="editContactDetail" styleClass="form-horizontal" p:role="form">
        <h:inputHidden value="${contactDetailController.id}" />

        <div class="form-group">
          ...
        </div>

        <h:commandButton styleClass="btn btn-primary" action="#{contactDetailController.editContact()}" value="Submit" />
        &#160;
        &#160;
        <h:commandButton styleClass="btn btn-default" action="#{contactDetailController.cancel()}" immediate="true" value="Cancel"/>
    </h:form>

    <hr class="subfeaturette-divider" />

  </div><!-- "main-content" -->
  ...
</h:body>

这里使用了 JSF 自定义标签。 <f:metadata> 标签是一个容器标签,它为当前页面声明一个元数据方面。

<f:viewParam> 标记附加页面的 GET 请求参数作为当前视图的元数据。我们将使用它将查询参数附加到控制器属性。 name 属性指定查询参数名称。 value 属性指定 JSF 表达式语言参考。提供诸如 /jsf-crud-1.0-SNAPSHOT/editContactDetail.xhtml?id=4 之类的 URL 请求将导致框架填充 id ContactDetailController 中的 属性,其值为 4。此调用发生在 JSF 生命周期的恢复视图阶段。

Tip

由于 <f:metadata> 标签声明了单个页面视图的元数据,它必须放置在页面的根元素视图附近。如果在 JSF 模板中使用 <f:metadata> 标签,则必须将其放在 <ui:define> 中作品。在这个例子中,标签就在 <h:body> 之后。

<f:event> 自定义标记将 JSF Faces 事件与组件相关联。描述这个标签的官方文档说它在页面的目标组件上安装了一个 ComponentSystemEventListener 实例。在这里,我们可以简单地说标签将预渲染事件与findByContactId()方法相关联控制器。换句话说,<f:event> 使用来自底层数据库的数据预填充表单。

在 <h:form> 内容中,我们将使用 <h:hidden> 自定义标签来存储联系人详细信息的当前 ID。 value 属性是一个表达式引用。这样,当用户提交表单时,标识符就会传播回控制器。

最后,有两个 <h:submit> 按钮,它们引用 editContact() 和 cancel() 方法分别在控制器中。第二个 <h:submit> 按钮中的中间属性指定 JSF 生命周期应该跳过 Process Validation 状态。然后,JSF 在提交表单时不应用验证。相反,生命周期从 Apply Request Values 直接移动到 Render Response 状态。

Tip

将 HTML 实体字符添加到 XHTML

Facelets 仅支持五个预定义的 XML 实体字符:&lt、&gt、& ;amp、&quot 和 &apos。添加 HTML 元素的唯一方法是通过十六进制或八进制表示法。 &#160 实体表示用于分隔空间的 Unicode 字符 &nbsp。

这是 editContactDetail.xhtml 视图的屏幕截图:

读书笔记《digital-java-ee-7-web-application-development》构建JSF表单

联系人详细信息应用程序的编辑页面视图的屏幕截图

Removing data


我们的用户能够创建联系方式,她现在可以更新条目。为了完成我们客户的旅程,我们应该允许她以良好的网络公民身份删除条目。为什么有这么多公司想要通过冒险或额外的麻烦来阻止删除用户数据的访问,以使如此简单的任务变得如此困难,这超出了我的理解!但是,我们可以为我们的联系方式应用程序执行此操作,现在它很简单,因为我们已经准备好了构建块。

我们将向 ContactDetailController 添加一个 removeDetail() 方法。这是额外的方法:

public class ContactDetailController {
  // ... as before ...
  public String removeContact() {
    contactDetail = contactDetailService.findById(id).get(0);
    contactDetailService.delete(contactDetail);
    contactDetail = new ContactDetail();
    return "index.xhtml";
  }
}

此方法 通过新的 id 搜索 contactDetail . id 字段是控制器的属性,设置在隐藏的表单字段中。通过在表单提交时调用数据服务 findById() 方法,我们将确保从持久化上下文中检索到最新信息。也许用户去吃午饭然后回来然后提交表单。找到实体后,我们可以调用数据服务将其删除。

以下是 removeContactDetail.xhtml 视图的摘录:

<div class="main-content">
  <h1> Delete Contact Details </h1>
  <table class="table table-striped table-bordered">
    <tr>
      <th> Item</th> <th> Value</th>
    </tr>
    <tr>
      <td> Title </td>
      <td>
      #{contactDetailController.contactDetail.title} </td>
    </tr>
    <tr>
      <td> First Name </td>
      <td>
      #{contactDetailController.contactDetail.firstName} </td>
    </tr>
    <tr>
      <td> Middle Name </td>
      <td>
      #{contactDetailController.contactDetail.middleName} </td>
    </tr>
    <tr>
      <td> Last Name </td>
      <td>
      #{contactDetailController.contactDetail.lastName} </td>
    </tr>
    <tr>
      <td> Allows Email? </td>
      <td>
      #{contactDetailController.contactDetail.allowEmails} </td>
    </tr>
    <tr>
      <td> Email </td>
      <td> #{contactDetailController.contactDetail.email} </td>
    </tr>
    <tr>
      <td> Date of Birth </td>
      <td>
        <h:outputText value="#{contactDetailController.contactDetail.dob}" >
          <f:convertDateTime type="date" pattern="dd-MMM-yyyy"/>
        </h:outputText>
      </td>
    </tr>
  </table>

  <h:form id="editContactDetail" styleClass="form-horizontal" p:role="form">
    <h:inputHidden value="${contactDetailController.id}" />

      <h:commandButton styleClass="btn btn-primary" action="#{contactDetailController.removeContact()}" value="Submit" />
      &#160;
      &#160;
      <h:commandButton styleClass="btn btn-default" action="#{contactDetailController.cancel()}" immediate="true" value="Cancel"/>
  </h:form>
</div>

如果您仔细查看,您会看到显示属性的<table>元素ContactDetail 实体的;但是等一下,<h:outputText> 元素去哪儿了?嗯,在 JSF 2 中,您不再需要编写 <h:outputText>,只需输出 JSF 托管 bean 的创作内容,您就可以立即将表达式直接写入地方。

因此,简单地写如下:

<td> #{contactDetailController.contactDetail.title} </td>

代替:

<td>
  <h:outputText value="#{contactDetailController.contactDetail.title}"/>
</td>

您更愿意使用 前面的创作内容中的哪一个进行编程?

但是,DOB 是我们将使用 <h:outputText> 元素的字段。 <f:convertDateTime> 标签将 java.util.Date 类型格式化为可读格式。 pattern 属性指定日期格式模式。这个标签依赖于 java.text.SimpleDateFormat 类。

<h:form> 标签仍然是必需的,以允许用户提交表单。它包含两个 <h:commandButton> 标签。提交表单时,JSF 调用控制器中的 removeContact() 方法。

最后,该页面还需要前面在 编辑数据中提到的 <f:metadata> 节/span> 部分,以便在呈现页面之前获取联系人详细信息。

通过这个基本的默认数字 JSF 示例,我们已经结束了客户的旅程。我们可以使用 Web 表单从数据库中创建、检索、更新和删除联系人详细信息。这真的很简单。我们还利用了像 Bootstrap 这样的 HTML5 框架,因此,我们可以快速调整我们的应用程序以适应响应式网页设计。

这是 deleteContent.xhtml 视图的屏幕截图:

读书笔记《digital-java-ee-7-web-application-development》构建JSF表单

联系人详细信息应用程序的删除页面视图

在我们 结束本章之前,我们将简短地介绍一下 JSF 和 CDI 范围。

JSF and CDI scopes


在 Java EE 7 之前,关于哪些注释是正确声明托管 bean 存在一些混淆。问题是 JavaServer Faces 规范早于 CDI 的后来标准,并且范围界定 重叠。作用域的历史来源于最初的设计和servlet容器的定义,为应用开发者提供了便利。范围只是名称/值对的映射集合。将它们视为 java.util.Map 类型的哈希映射集合会有所帮助。示波器的使用寿命不同。

对于 CDI, 包 名称为 javax .enterprise.context 和 对于 JSF 托管 bean,包是 javax.faces.bean。

Bean scopes

@RequestScoped 注释表示具有生命周期的控制器,该生命周期具有 Faces 请求和响应的持续时间。请求范围是短暂的。它从 web 客户端提交 HTTP 请求开始,然后由 处理小服务程序容器。当响应发送回客户端时,范围结束。

@SessionScoped 注释表示许多请求和响应的生命周期。会话范围旨在绕过 HTTP 的无状态协议。 servlet 容器增强了 HTTP 协议,使其能够存储和检索比一个请求和响应周期更长的对象。出于这个原因,会话范围很长。会话范围可能会在超时后过期,或者如果重新启动服务可能会变得无效。

@ApplicationScoped 注释表示只要 Web 应用程序正在运行且可用,生命周期就存在。更重要的是,应用程序范围在所有请求、会话、对话和自定义范围之间共享。此范围在 Web 应用程序启动后立即开始。它在 Web 应用程序关闭时结束。

请求、会话和应用程序范围是范围模型的经典版本。 JSF 和 CDI 也有额外的作用域。

@javax.enterprise.context.ConversationScoped 注释表示一个生命周期,其持续时间大于一个或多个请求和响应周期,但比会话范围短。 CDI 定义了一个称为会话范围的范围。它是请求范围和会话范围之间的范围,但也具有与其封装的 bean 的上下文关联。我们将在后面的章节中讨论对话范围。

JSF 2.0 定义了一个名为 @javax.faces.bean.ViewScoped 的范围,它与会话范围类似,因为它的生命周期也比请求范围长。视图范围从客户端提交 HTTP 请求开始。它一直存在,直到用户导航到另一个页面。这使得 @ViewScoped bean 成为托管 bean 控制器比 @RequestScoped 种类更广泛和更合理的选择。 @ViewScoped 注释适用于管理单用户故事,正如我们在 CRUD 示例中看到的那样。

Tip

@ViewScoped 注释对 CDI bean 不可用。如果您使用的是 2.2 和 Java EE 7 之前的 JSF 版本,那么此注释将不适用于 @javax.inject.Named 注释 bean。您必须改用 @javax.faces.bean.ManagedBean 。

在 JSF 2.2 中,有 @javax.faces.flow.FlowScoped 注释,它是 CDI 认可的扩展。流范围也类似于会话范围,其生命周期比请求范围长,但比会话范围短;但是,它 是为工作流管理操作而设计的。 flow 范围允许开发人员创建一组具有明确定义的入口和出口点的页面。可以认为此范围适用于向导数据输入应用程序。

最后,让我们了解剩下的两个作用域。有 POJO 注释 @javax.faces.beanCustomScoped,它允许托管 bean 在运行时评估值。对于自定义范围,JSF 实现将遵循实现,因此,任何 EL 表达式都可以根据基于代码的值进行自定义。 @javax.faces.bean.NoneScoped 注释是一个特殊的范围,它暗示托管 bean 根本没有范围。 JSF 将在每次引用这些类型的无范围托管 bean 时实例化它们。您可能想知道为什么 JSF 应该批准这种类型的 bean?无范围 bean 在安全上下文中或在您不希望 bean 维护状态的情况下可能很有用。

Tip

其他 HTML 标签在哪里?

Internet 上有许多旧版JSF 示例。您可能想知道为什么我们没有看到像 h:panel 和 h:panelGrid 这样的标签。这些标签用于对内容进行布局,尤其是在 JSF 1.x 中。这些标签的默认 HTML 实现使用 HTML 表格元素生成内容。现代数字工程师知道,不建议使用无处不在的表格元素来构建网站。出于这个原因,我选择不使用这些标签构建我的示例。

Summary


在关于 JSF 表单的这一章中,我们探索了 HTML 和核心 JSF 自定义标签来构建 Internet 上最热门问题之一的答案:作为数字开发人员,我到底如何编写 CRUD 应用程序?令人惊讶的是,这个简单的想法被认为难以编程。

我们构建了一个数字 JSF 表单,该表单最初创建了一个联系详细信息。我们看到了 Facelet 视图、托管 bean 控制器、有状态会话 EJB 和实体。我们之所以现代,是因为我们利用了最新的 HTML5 进步,例如 Bootstrap CSS 框架。我们展示了一个带有 <h:dataTable> 标签的对象列表集合,这是一个强大而灵活的组件。然后,我们添加了从应用程序中编辑和删除联系人详细信息的功能。

在下一章中,我们将广泛地研究表单验证,并在 JSF 中混合 AJAX 通信。我们已经——在某种程度上——用 <f:validateRequired>, <h:messages> 研究了验证领域> 和 <h:message>。

Exercises


这些是第 3 章的问题:

  1. HTML5 渲染工具包和 JSF 中的核心自定义标签有什么区别?

  2. JSF 自定义标记之间共享的公共属性有哪些?

  3. 企业的 Web 应用程序往往有两种类型:数据提交和案例工作。数据提交只是捕获数据并进行一些有限的验证。另一种模式让您可以完全控制输入新记录、修改它们以及经常删除数据。您认为这两种类型的原因是什么?

  4. 成语 Create Read Update Delete (CRUD) 是必需的对于面临电子商务应用程序的企业。您在哪里遇到过这些应用程序?这些应用程序是网络独有的吗?如果有第二次机会,可以做些什么来提高这些应用程序的技术水平?更好的数字化转型如何帮助这些企业,更重要的是,帮助他们的客户?

  5. 编写一个简单的 JSF 应用程序,它基本上使用 HTML 表单元素 <h:form> 和命令按钮 <h:commandButton> ; 。你的任务是为当地的小说爱好者爱好读书俱乐部写一份注册申请。您的参与者必须先在线注册才能参加。从支持 bean(托管控制器)开始。想想你需要记录的属性。 (您的 Registration.java POJO 将需要联系方式,例如姓名、年龄和电子邮件。)在这个阶段,您不必将任何信息保存到数据库中,但是如果您创建一个包含属性 title (String) 的书籍数据记录 (Book.java) 将会很有帮助, author (String), genre (String), publisher (String) , 和 出版年份 (整数)。使用 MVC 设计模式编写设计规范。

  6. 在与假想的涉众的第一次迭代中,您只需要编写一个简单的 JSF 表单。创建一个捕获书名和作者的支持 bean。您将需要 <h:outputLabel> 和 <h:inputText>。在本书的源站点中,您会发现一个空的项目目录,其中包含空的 JSF 占位符页面以及已经设置好的 Bootstrap CSS 和 JavaScript 库,例如 jQuery。您可以复制并重命名此文件夹以更快地开始。

  7. 为了在 JSF 中使用 Bootstrap CSS,我们可以将几乎所有的 JSF HTML 组件应用到 styleClass 属性。其他共同属性是什么?

  8. 使用 Hobby Book Club 应用程序并添加一些其他组件,例如下拉列表:<h:selectManyMenu>。您需要将属性添加到支持 bean。 (这可能是书籍的类型,例如犯罪、科幻、惊悚或浪漫)。您将需要一个 POJO 作为注册者的数据记录(也许类名 Registrant.java 可以很好地为我们服务)。

  9. 如果您发现一本与流派不符的稀有书籍会怎样?您将如何建模此属性 bean 以及您将使用哪些 JSF HTML 自定义标记?

  10. 调整您的爱好应用程序以使用 JSF HTML 自定义标记的其他元素,例如 <h:selectBooleanCheckbox>。您可以将布尔值添加到属性中,以捕获组中有人审阅图书时的状态。

  11. <h:selectOneMenu> 和 <h:selectManyCheckbox> 有什么区别?解释客户在遇到 <h:selectOneListbox> 和 <h:selectManyListbox> 时会看到什么?

  12. 在现代数字网页设计中,我们为什么要避免使用 <h:panelGroup> 元素来组成网页用户界面?

  13. 为了完成爱好书应用程序,我们可能允许注册用户在他们的申请表中添加评论。他们想说说他们的特殊专长是什么,从未来的赛博朋克神秘博士到围绕古希腊和罗马的历史海战,可以是任何东西。 <h:inputText> 和 <h:inputTextArea> 有什么区别?你能用现代 CSS 框架优化这个控件吗?

  14. 当两个客户想要在 Web 数据库中编辑相同的联系人详细信息记录时会发生什么?认为应用程序应该如何表现?您会添加哪些功能?您认为客户会如何看待您的想法?