vlambda博客
学习文章列表

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

Chapter 4. JSF Validation and AJAX

 

“在完成之前似乎总是不可能的。”

 
  --Nelson Mandela

至此,我们已经创建了一个完成常见的创建、检索、更新和删除的数字化客户旅程,也就是著名的 CRUD 需求。结果对利益相关者和产品所有者很有吸引力,但我们团队的用户成员对表格不满意,因为它缺乏验证公众成员输入的数据的能力。

当我们考虑它时,验证对用户来说很重要,因为他或她是在 Web 应用程序中输入数据的人。它为用户节省了时间和挫败感,因为他们在输入数据时就知道输入是错误的。它避免了数据库管理员为修复错误提交的数据而产生的成本。验证提高了在 Internet 上 24/7 全天候运行的 Web 应用程序的效率。随着我们越来越多的日常生活依赖于传统服务的数字化改造,电子商务现在已成为必需品;我们必须在正确的时间向公众提供正确的信息,即在销售点或捕获点。

Validation methods


在本章中,以 基本的JSF 表单为基础,我们将学习如何在服务器端和客户端-服务器端应用验证。这两种策略都有一定的优势;我们将了解这两种方法的优缺点。

Server-side validation

表单验证可以在运行的Java EE应用程序的服务器端实现在应用程序服务器或 servlet 容器上。信息作为正常的 HTTP 表单提交从 Web 浏览器发送到 Web 应用程序。在这种模式下,表单作为传统的 HTML 表单元素提交。 Web 框架(在本例中为 Java EE)验证输入并将响应发送回客户端。如果表单验证失败,则重新显示包含 Web 表单的页面并显示错误消息。

服务器端快速验证是安全的,即使 JavaScript 在 Web 浏览器中被禁用或不可用,它也会保护数据库。另一方面,这种类型的验证需要从客户端到服务器端的往返。用户在提交 表单之前不会收到有关表单数据的反馈。

规则似乎总是有例外。如果服务器端表单验证是使用 AJAX 提交的,那么我们可以绕过响应缓慢的问题。 AJAX 验证是一个很好的折衷方案,因为可以在用户在表单上输入数据时验证表单。另一方面,AJAX 要求在 Web 浏览器中启用 JavaScript。

Client-side validation

我们团队中的用户体验人员非常喜欢客户端验证,但是这种类型的 验证需要浏览器中存在 JavaScript(或等效的类型的动态脚本技术)。客户端验证提供了更灵敏和更丰富的用户与表单的交互。

客户端验证确保在允许用户提交表单之前表单始终正确。由于 JavaScript 是一种渐进式语言,有很多方法可以告知用户如何更好地与表单提交过程进行交互。 jQuery 等技术允许程序员在用户键入数据时添加提示和验证错误消息。

在某些情况下,JavaScript 在 Web 浏览器中被禁用或不可用。我很容易想到沙盒受到严格控制的政府安全或专家中心。当 JavaScript 被用户或设备管理员关闭时,客户端验证肯定会失败,用户可以绕过验证。

Tip

结合客户端和服务器端验证

在企业的专业应用程序中,我强烈建议您将这两种验证方法结合起来,以获得两全其美的效果。客户端验证提供更快、更丰富的体验,服务器端验证保护您的数据和数据库免受不良数据和黑客攻击。

在讨论验证的技术主题之前,我们必须了解消息在 JSF 中是如何表示的。

Faces messages


JSF 提供了两个自定义标记来显示错误消息。 <h:message> 标签显示绑定到特定组件的消息。 <h:messages> 标签显示未绑定到特定组件的消息。

我们在 Chapter 3 中看到了我们第一次使用 <h:message>构建 JSF 表单。标签通常与表单控件相关联。我们可以使用以下内容向我们的 JSF 页面添加消息:

<h:messages globalOnly="false" styleClass="alert alert-danger" />

标签被 添加到内容的顶部。 globalStyle 属性是一个布尔值,它指定标签是否应该显示与组件无关的消息。在这里,我们再次使用 Bootstrap CSS 选择器。

以下是 JSF 标记之间共享的 属性的表格,<h:messages><h:message>

属性

描述

Id

指定唯一标识符

errorClass

指定错误消息的 CSS 类选择器

errorStyle

指定错误消息的样式

infoClass

指定信息消息的 CSS 类选择器

infoStyle

指定信息消息的 CSS 样式

for

指定消息关联的组件

渲染

设置一个布尔值来指定标签是否呈现到页面上

风格

定义所有消息类型的 CSS 选择器

styleClass

定义所有消息类型的 CSS 样式

在幕后,这些标签分别渲染 javax.faces.HtmlMessagesjavax.faces.HtmlMessages 组件的内容,这反过来,依赖于 javax.faces.application.FacesMessage 元素的列表集合。作为一名 JSF 数字开发人员,我们不必过于担心日常的 HtmlMessageHtmlMessages 组件,因为它们位于汽车引擎盖下。如果我们正在编写新的 JSF 渲染器或扩展,那么我们将不得不查看 Javadoc 和 JSF 规范。

第 3 章构建 JSF 表单中,您被引入到应用程序中,FacesMessage,用于创建 JSF CRUD 样式的表单。 在 Controller 中,我们可以创建与表单中的任何 UIComponent 无关的验证错误消息。因此,此验证错误只能通过全局错误消息访问。这是生成此类验证错误的代码:

  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;
    }
    /* ... */
  }

The FacesMessage object represents a validation message with a severity level. We add it to the FacesContext object. The FacesMessage constructor is of the form:

public FacesMessage(Severity severity, String summary, String detail)

严重性可以是 FaceMessages 类中定义的四个静态常量,分别是 SEVERITY_INFO SERVERITY_WARNINGSEVERITY_ERRORSEVERITY_FATAL。这些值实际上是私有内部类 Severity 的实例化,不幸的是,在封闭类之外无法访问它,因此我们可以发明自己的严重性。

Faces 消息还需要消息的摘要,以及关于失效消息的详细信息(可选)。

javax.faces.context.FacesContext 是当前传入请求和潜在响应的聚合持有者。对象实例在初始 JSF 传入请求(Faces Request)上被实例化,它会一直保持活动状态,直到触发后续的 JSF release() 方法,该方法通常在框架。 FacesContext 是添加 FacesMessage 的地方以及可以从中检索消息列表集合的地方。

FacesContext 有几个有趣的方法,包括 isValidationFailed(),这对于在 JSF 生命周期的早期检测任何验证失败很有用。稍后我们将通过 Bean Validation 看到此调用的示例。还有其他方法,例如使用 getViewRoot() 获取视图根,getCurrentPhaseId() 获取当前阶段JSF 生命周期,以及 getRenderKit() 来检索渲染工具包表单。使用 isPostback() 方法,我们可以确定请求是否为 HTML 表单以及 JSF 框架是否即将将数据发送回同一表单。上下文对象还有更多。

将 faces 消息添加到上下文的方法如下所示:

public abstract void addMessage(String clientId, FacesMessage message);

如果 clientId 属性为 null,则该消息是全局可用的消息,并且不与 任何视图关联零件。

现在我们已经了解了如何生成特定于 JSF 的消息,让我们深入研究 JSF 应用程序的验证。

Validation


在服务器端实现验证有两种主要方式。要遵循的一条路线是通过 使用 Java EE 7 规范中的 Bean Validation 版本 1.1,而另一条传统路线将带您通过 JSF 验证。

Constraining form content with Bean Validation

Bean Validation 是一种规范,它允许开发人员注释 POJO 并 实体 bean,然后调用自定义验证器实例来验证属性。验证框架与 Java 注释一起使用,因此,数字工程师可以明确地说明如何验证属性甚至方法。

我在 Java EE 7 开发人员手册 中用了一整章介绍 Bean 验证;不过,我将在这本 Digital Web Application 书中与您一起了解基础知识。 Bean Validation 1.1 标准中有几个注释可以直接使用。但是,如果您的平台允许或者您决定添加 Hibernate Validator,则可以使用更多有用的验证注释。开发人员还可以创建自定义验证。

让我们再次使用 ContactDetail 实体,但是这次我们在属性中添加了 Bean Validation 注解,如下所示:

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

@Entity @Table(name="CONTACT")
/* ... */
public class ContactDetail {
  @Id /* ... */ private long id;

  @NotNull(message = "{contactDetail.title.notNull}")
  private String title;

  @Size(min = 1, max = 64,
        message = "{contactDetail.firstName.size}")
  private String firstName;

  private String middleName;

  @Size(min = 1, max = 64,
        message = "{contactDetail.lastName.size}")
  private String lastName;

  @Pattern(regexp =
      "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
      + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$",
      message = "{contactDetail.email.pattern}")
  private String email;

  @NotNull( message = "{contactDetail.dob.notNull}")
  @Temporal(TemporalType.DATE)
  @Past( message="{contactDetail.dob.past}" )
  private Date dob;

  /* ... as before ... */
}

我们添加了 注释@Pattern, @Past@NotNull@SizeContactDetail 实体的属性.注释可以在 Java 包 javax.validation.constraints 中找到,为 Bean Validation 保留。

以下是重要的 Bean Validation 注释表:

约束名称

描述

允许的类型

@Null

指定 元素必须是空引用指针。

任何

@NotNull

指定 元素不能是空引用指针。

任何

@Min

指定 元素必须是大于或等于提供的最小值的数值。由于浮点算术舍入误差,不支持浮点和双精度。

BigDecimal、BigInteger、byte、short、int 和 long

@Max

指定 元素必须是小于或等于提供的最小值的数值。由于浮点算术舍入误差,不支持浮点和双精度。

BigDecimal、BigInteger、byte、short、int 和 long

@DecimalMin

类似于 @Min 但添加了将值设置为 字符串参数的功能。数值必须大于或等于提供的值。 FP 限制也适用于此。

BigDecimal, BigInteger,

CharSequence、byte、short、int 和 long

@DecimalMax

类似于 @Max 但添加了将值设置为 字符串参数的功能。数值必须小于或等于提供的值。 FP 限制也适用于此。

BigDecimal, BigInteger,

CharSequence、byte、short、int 和 long

@Size

元素的大小必须在提供的包含边界范围内。

CharSequence、Collection、Map 和原始数组

@Past

根据 Java 虚拟机的当前时间, 元素必须是过去的日期。

java.util.Date 和 java.util.Calendar

@Future

元素必须是根据 Java 虚拟机当前时间的未来日期。

java.util.Date 和 java.util.Calendar

@Pattern

元素必须与提供的符合 Java 约定的正则表达式模式匹配。

字符序列

Bean Validation 注解通常接受消息 属性,即验证给用户的消息,也可以是括号内的值,触发验证框架从中搜索消息java.util.ResourceBundle。某些注释,例如 @Min@Max@DecimalMin 和 < code class="literal">@DecimalMax 具有附加属性,例如 minmax 来指定明显的边界。

我们可以在带有验证消息的属性上定义 @NotNull 约束,如下所示:

@NotNull( message = "The office reference must not be null")
private String officeReference;

这是一个很好的方法,可能用于制作网站原型;但正如我们从软件考古学的知识中知道的那样。这可能是维护的噩梦,因为我们正在将数字副本直接写入 Java 代码。最好在属性文件中编写文本副本,该文件可由 Bean Validation 使用的标准 ResourceBundle 获取。我们的常驻数字策略师和文案专家 Jenny 将感谢我们向她发送属性文件而不是 Java 源代码。

所以让我们重写这个属性的约束如下:

@NotNull( message = "{mydataform.officeReference.notNull}")
private String officeReference;

通过将消息放置在特定位置,可以将 Bean Validation 与 JSF 集成。程序员只需在WEB-INF/classes文件夹中创建一个ValidationMessages.properties文件。

以下是 ContactDetail 实体:

contactDetail.title.notNull = Title cannot be blank or empty
contactDetail.firstName.size = First name must be between {min} and {max} characters
contactDetail.middleName.size = Middle name must be between {min} and {max} characters
contactDetail.lastName.size = Last name must be between {min} and {max} characters
contactDetail.email.pattern = You must supply a valid email address
contactDetail.dob.past=Your Date of Birth must be in the past

通过 Bean Validation,我们可以添加用大括号表示的占位符,以丰富用户将看到的 消息。占位符是特定的,例如 {min}{max}。属性文件的另一个优点是 JDK 中的 ResourceBundle 已经处理了不同语言环境的国际化这个棘手的话题。

仅仅依靠使用 JSF 的 Bean Validation 有很大的缺点。它非常适合保护数据库免受输入不良数据的影响,并且使用数字开发人员几乎免费获得的 Java EE 7 应用程序服务器,可以在每条记录之前向数据库添加或修改验证。但是,Bean Validation 与 JSF 前端没有任何联系。该框架与页面作者的内容没有任何关联。在现代软件工程中,我们也不希望表示层和模型层之间存在这种依赖关系。面向对象编程的最佳实践之一是 SOLID 原则。我们当然希望这些层单独负责一个目的;对扩展开放,但对修改关闭,最重要的是,防止由于软件老化而导致软件技术债务的泄漏抽象。

仅依赖 Bean Validation 的另一个缺点是数据的验证完全取决于 Java 数字工程师的技能。这意味着页面作者或设计者在获得更好的以用户为中心的体验的道路上不能创新、编辑或删除验证。

Bean Validation 非常适合在应用程序逻辑中添加验证。如果这是业务要求,您可以确保联系人详细信息的标题永远不会为空。可以实现对属性的复杂和组验证。有关详细信息,请参阅我的书 Java EE 7 开发人员手册

以下 屏幕截图显示了 bean-validation/createContactDetail.xhtml in action 来自本书的源代码。 屏幕截图显示了当 用户只提交表单而不填写表单时会发生什么:

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

联系人详细信息应用程序上的 Bean Validation 屏幕截图

<h:messages> 标签 设置 globalStyle=true 显示框架发现的验证消息的输出在 ContactDetail 实体中。

Validating user input with JSF

自 2004 年成立以来,JSF 一直有一个验证框架。当时将 JSF 与事实上的 Apache Struts Web 框架分开的特性,因为后者没有对验证的内置支持。

记住转换和验证发生在 JavaServer Faces 生命周期的不同阶段会很有帮助(查看 Chapter 2 中的图表,JavaServer Faces Lifecycle执行和渲染生命周期部分)。提醒一下,JSF 将在应用请求值阶段设置组件中的值,然后根据需要使用各种转换将输入字符串值转换为目标对象。验证发生在流程验证阶段,并且此生命周期按照设计进行。为了转换来自 HTML 请求的输入数据,JSF 尝试并检查是否可以在支持 bean 中设置参数。 Update-Model-Values 阶段遵循前面的阶段。如果在生命周期中发生验证或转换错误,那么它会被有效地缩短。 JSF 直接进入 Render-Response 阶段,它将支持 bean 中的属性转换为字符串,以便 Web 客户端可以显示它们。

JSF 提供了一小组预构建的验证器标签,您可以将其应用于标记为页面作者,核心 JSF 自定义标签的表格已在 第 3 章构建 JSF 表单。其中一些如下:<f:validateDoubleRange><f:validateLength><f:validateLongRange> <f:validateRegex><f:validateRequired>

我们可以将这些标签应用于联系人详细信息 CRUD 示例。因此,让我们从 createContact.xhtml 页面开始。以下是该页面的简短摘录:

<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" label="Title" id="title" value="#{contactDetailControllerBV.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" />
        <f:validateRequired/>
      </h:selectOneMenu>
      <h:message for="title" styleClass="alert validation-error"/>
    </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" label="First name" value="#{contactDetailControllerBV.contactDetail.firstName}" id="firstName" placeholder="First name">
          <f:validateRequired/>
          <f:validateLength maximum="64" />
      </h:inputText>
      <h:message for="firstName" styleClass="alert validation-error"/>
    </div>
  </div>
  <!-- . . . -->
  <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" label="Email" class="form-control" id="email" value="#{contactDetailControllerBV.contactDetail.email}" placeholder="Enter email">
          <f:validateRequired/>
          <f:validateLength maximum="64" />
      </h:inputText>
      <h:message for="email" styleClass="alert validation-error"/>
    </div>
  </div>
  <!-- . . . -->

  <label class="control-label"> Your Date of Birth</label>
  <!-- . . . -->
  <div class="row my-group-border">
    <div class="col-sm-12">
      <h:message for="dobDay" styleClass="alert validation-error"/>
    </div>
    <div class="col-sm-12">
      <h:message for="dobMonth" styleClass="alert validation-error"/>
    </div>
    <div class="col-sm-12">
      <h:message for="dobYear" styleClass="alert validation-error"/>
    </div>
  </div>
</h:form>

我们放置了 <f:validateRequired>, <f :validateLength><f:validateLongRange> 标签在正文 的内容JSF HTML 呈现标签,如 <h:inputText><h:selectOneMenu>validateLength 标签验证 String 属性的 长度。标签接受最大参数,但也可以采用最小属性。

我们还在其各自的 HTML 输入字段附近添加了一个 <h:message> 标记。 styleClass 属性指定了一个自定义 CSS 选择器,该选择器强制将验证消息放在单独的新行上。用于此的 CSS 如下所示:

.validation-error {
    display: block;
    margin: 5px 15px 5px 15px;
    padding: 8px 15px 8px 15px;
    color: #a94442;
    background-color: #f2dede;
    border-color: #ebccd1;
}

为了避免像 <jsf_form>:<form_property_name> 这样的 JSF 糖化名称出现在验证错误消息中,这会给出类似 contactDetail 这样的结果:title -Validation Error: Value is required,我们为每个 HTML 渲染标签指定 label 属性。标题输入字段有一个属性设置label="Title"

<f:validateLongRange> 标签检查字符串的实际内容是否是介于最小值和最大值属性之间的数值。我们在 date-of-birth 字段中使用此标签。

以下摘录是 DOB 组中日期字段的创作:

  <h:selectOneMenu id="dobDay" value="#{contactDetailControllerBV.dobDay}" label="Registration Day">
      <f:selectItem itemLabel="----" itemValue=""/>
      <f:selectItems value="#{contactDetailControllerBV.daysOfTheMonth}" var="day" itemLabel="#{day}" itemValue="#{day}" />
      <f:validateRequired/>
      <f:validateLongRange minimum="1" maximum="31" />
  </h:selectOneMenu>

前面的 代码演示了 <f:validateLongRange> 标记如何强制执行日期表单中的字段。我们对其他 DOB 字段进行冲洗并重复

<f:validateRegex> 标记将输入属性字符串与正则表达式匹配。我们将 这个标签用于email 属性。以下是此验证检查的代码:

  <div class="col-sm-9">
    <h:inputText type="email" label="Email" class="form-control" id="email" value="#{contactDetailControllerBV.contactDetail.email}" placeholder="Enter email">
      <f:validateRequired/>
      <f:validateLength maximum="64" />
      <f:validateRegex pattern="^[_A-Za-z0-9-\+]+(\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9]+)*(\.[A-Za-z]{2,})$" />
    </h:inputText>
    <h:message for="email" styleClass="alert validation-error"/>
  </div>

有趣的是,霸道的pattern属性值,正则表达式,和Bean Validation中的@Pattern几乎一模一样。我们必须将双反斜杠字符转换为单反斜杠,因为我们不需要在普通正则表达式中转义文字,而不是在 Java 代码中设置。

以下是页面jsf-validation/createContactDetail.xhtml的截图:

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

演示 JSF 内置验证规则的屏幕截图

Customizing JSF validation

如果您 使用过源代码并运行了示例,我敢打赌您会注意到 JSF 验证存在一些明显的问题。例如,当 email 字段的值不是有效的电子邮件地址时,您将看到如下验证消息:

Regex pattern of '^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$' not matched

很明显,这个非常详细的应用程序信息未能实现以用户为中心的设计和面向公众的简洁语言。我们可以用 JSF 做什么来避免这些消息?

可以将三个属性应用于输入字段的 HTML 呈现标签,例如 <h:inputText><h:selectOneMenu> 。下表显示了可帮助自定义 JSF 验证消息的属性:

属性

描述

requiredMessage

定义一个 基于值的表达式,如果需要该字段,该表达式将用作消息文本。

validatorMessage

定义一个基于值的表达式,如果字段和属性验证失败,该表达式将用作验证文本。

conversionMessage

定义一个基于值的表达式,如果字段可以转换为目标类型,该表达式将用作消息。

有了这些信息,我们可以通过将 requiredMessage 属性应用于我们的字段来轻松解决消息问题:

<h:inputText type="email" label="Email" class="form-control" id="email" value="#{contactDetailControllerBV.contactDetail.email}" validatorMessage="Value must be in the format of an email address" converterMessage="Value should be in the format in an email address" placeholder="Enter email">
  <f:validateRequired/>
  <f:validateLength maximum="64" />
  <f:validateRegex pattern=". . ." />
</h:inputText>

requiredMessagevalidatorMessageconversionMessage 覆盖由服务器端的 JSF 验证器。请注意,这些属性可以接受值表达式。这对于页面作者来说非常有用,可以指定方法。然而,在栅栏的另一边,我们的电子邮件地址字段有两个验证约束,一个正则表达式检查和一个字段长度约束。该消息不适用于 validateLength。因此,如果我们按照这种方法使用不止一种类型的验证器,就会出现问题。

我们可以采取另一种方法。在 JSF 框架中全局覆盖验证消息 怎么样?我们可以配置我们自己版本的 JSF 验证器消息。为了实现这一目标,首先,我们使用有关加载这些消息的位置的信息来配置框架。我们在WEB-INF/faces-config.xml文件中设置了一个消息包,如下:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2">
  <application>
      <message-bundle>
          uk.co.xenonique.digital.JSFVMessages
      </message-bundle>
  </application>
</faces-config>

这个配置文件定义了应用程序的 Faces 资源,在这里我们可以配置一个引用属性文件的消息包。属性文件的路径其实是uk/co/xenonique/digital/JSFVMessages.properties,可以在项目资源ch04/jsf中找到-crud-validation/src/main/resources.

其次,我们提供自己的消息包文件。该属性文件 JSFVMessages.properties 的内容就是以下属性定义:

javax.faces.validator.RegexValidator.NOT_MATCHED = Input value does not conform to according expected format.
javax.faces.validator.RegexValidator.PATTERN_NOT_SET = A pattern must be set for validate.
javax.faces.validator.RegexValidator.MATCH_EXCEPTION = The pattern is not a valid regular expression.
javax.faces.validator.LengthValidator.MAXIMUM = {1}: This field can accept up to ''{0}'' characters long.
javax.faces.validator.LengthValidator.MINIMUM = {1}: This field must have at least ''{0}'' characters long.

javax.faces.validator.LongRangeValidator.MAXIMUM = {1}: This value must be less than or equal to ''{0}''
javax.faces.validator.LongRangeValidator.MINIMUM = {1}: This value must be greater than or equal to ''{0}''
javax.faces.validator.LongRangeValidator.NOT_IN_RANGE = {2}: This value must be between of {0} and {1} inclusive.
javax.faces.validator.LongRangeValidator.TYPE = {0}: Unable to convert this value to a decimal number.

javax.faces.component.UIInput.REQUIRED = {0}: This value is required

如您所见,我们重写了属性 RegexValidator.NOT_MATCHED 以提供新的 消息。原始定义可以在应用程序服务器的 JAR 文件中找到,或者作为第三方 JAR 捆绑在您的 servlet 容器中。定义可以在 JAR 的包中找到 (jsf-api-2.2.jar) javax/faces/Messages.properties

正则表达式验证器的原始定义如下所示:

javax.faces.validator.RegexValidator.NOT_MATCHED = {1}: Validation Error: Value not according to pattern ''{0}''
javax.faces.validator.RegexValidator.PATTERN_NOT_SET = A pattern must be set for validate.
javax.faces.validator.RegexValidator.MATCH_EXCEPTION = The pattern is not a valid regular expression.
javax.faces.validator.LengthValidator.MAXIMUM = {1}: Validation Error: Length is greater than allowable maximum of ''{0}''
javax.faces.validator.LengthValidator.MINIMUM = {1}: Validation Error: Length is less than allowable minimum of ''{0}''

javax.faces.validator.LongRangeValidator.MAXIMUM = {1}: Validation Error: Value is greater than allowable maximum of ''{0}''
javax.faces.validator.LongRangeValidator.MINIMUM = {1}: Validation Error: Value is less than allowable minimum of ''{0}''
javax.faces.validator.LongRangeValidator.NOT_IN_RANGE = {2}: Validation Error: Specified attribute is not between the expected values of {0} and {1}.
javax.faces.validator.LongRangeValidator.TYPE = {0}: Validation Error: Value is not of the correct type.

您可以在 http://svn.apache.org/repos/asf/myfaces/core/branches/2.0.x/api/src/main/resources/javax/面孔/Messages.properties。如您所见,它们非常技术性和用户不友好。消息包中的许多属性定义都接受参数的占位符。 NOT_MATCHED 接受两个参数:第一个参数 {0} 是模式,第二个参数 {1} 是输入字段的标签。

Note

在 Java EE 7 中,JSF 验证中的参数化占位符与 Bean Validation 框架中的占位符不同。 JSF 使用整数索引,而 Bean Validation 可以使用命名占位符。

在撰写本文时,JSF 验证器的参考实现中存在一个错误,这会阻止开发人员在消息属性中使用某些占位符。我们会喜欢这样的属性定义:

javax.faces.validator.RegexValidator.NOT_MATCHED = Input value {1} does not conform to according expected format.

遗憾的是,Mojarra 中的 当前错误阻止了我们将其写为生产代码。

自定义 JSF 验证有另一种策略。我们可以定义自己的验证器来扩展框架的功能。

Custom validation methods

JSF 允许数字工程师在托管 bean 控制器中配置一个方法,该控制器将被调用以验证字段。在 HTML 渲染标签中添加属性验证器就完成了这个策略,它是一个值表达式。

以下是将自定义验证方法添加到联系人详细信息表单的 emailAddress 属性的方法:

  <h:inputText type="email" label="Email" class="form-control" id="email" value="#{contactDetailControllerBV.contactDetail.email}" validator="#{contactDetailControllerJV.validateEmailAddress}" placeholder="Enter email">
      <f:validateRequired/>
  </h:inputText>

属性验证器引用了修改后的 ContactDetailControllerJV bean 中的方法 validateEmailAddress()。此方法如下所示:

public void validateEmailAddress(
    FacesContext context, UIComponent component, Object value) {
    String text = value.toString();
    if ( text.length() > 64 ) {
      throw new ValidatorException(
        new FacesMessage(
          FacesMessage.SEVERITY_ERROR,
          "The value must be less than 64 chars long.", null));
    }
    final String REGEX =
        "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
        + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
    Pattern pattern = Pattern.compile(REGEX);
    Matcher matcher = pattern.matcher(text);
    if ( !matcher.matches() ) {
      throw new ValidatorException(
        new FacesMessage(
          FacesMessage.SEVERITY_ERROR,
          "The value must be a valid email address.", null));
    }
}

在前面的方法validateEmailAddress()中,传入的参数是FacesContext,被验证的组件是UIComponent类型,而要检查的挂起值是 Object 类型。此方法验证两个约束:它检查字段的 长度是否太长以及该字段是否为电子邮件地址。我们使用 JDK 标准库 javax.regex 包来实现这一点。为了断言验证错误(如果有),我们创建 FacesMessage 对象并将它们添加到当前的 FacesContext 实例中。

Defining custom validators

在控制器或名为 bean 的 CDI 中编写验证器是一种有用的策略。但是,缺点是 ,您的应用程序中总是需要间接 POJO。还有另一种策略,JSF 允许我们定义自定义验证器,这些验证器集成在框架中。开发人员可以选择编写使用注释 javax.faces.validator.FacesValidator 声明的 POJO。 POJO 必须实现接口javax.faces.validator.Validator

让我们将电子邮件地址检查代码移动到自定义验证器中。 FacesEmailAddressValidator 的代码如下:

package uk.co.xenonique.digital;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.*;
import java.util.regex.*;

@FacesValidator("emailValidator")
public class FacesEmailAddressValidator implements Validator {
  public static final String EMAIL_REGEX =
    "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
  + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";

  @Override
  public void validate(FacesContext context,
    UIComponent component, Object value)
    throws ValidatorException
  {
    String text = value.toString();
    Pattern pattern = Pattern.compile(EMAIL_REGEX);
    Matcher matcher = pattern.matcher(text);
    if ( !matcher.matches() ) {
      throw new ValidatorException(
        new FacesMessage(
          FacesMessage.SEVERITY_ERROR,        
          "The value must be a valid email address.", null));
    }
  }
}

这个类被注解了@FacesValidator,单个参数标识页面中验证器的名称看法。 validate() 方法在验证器接口中通过约束实现设计。 JSF 传入 FacesContext、与输入值关联的组件以及值本身。

我们将输入值检索为文本字符串。验证电子邮件地址的正则表达式代码与以前几乎相同,但消息键除外。错误键是 {application.emailAddress.pattern}

使用我们的 POJO 自定义验证器,我们可以重写页面视图上的 HTML 以使用它。以下是 login.xhtml 的提取视图:

<div class="form-group">
  <h:outputLabel for="email" class="col-sm-3 control-label">
    Email</h:outputLabel>
  <div class="col-sm-6">
    <h:inputText class="form-control" label="Email" value="#{loginControllerJV.email}" id="email" placeholder="Password" validator="emailValidator">
        <f:validateRequired/>
    </h:inputText>
    <h:message for="email" styleClass="alert validation-error"/>
  </div>
</div>

唯一的区别是 <h:inputText> 元素中的验证器属性。此属性按名称将自定义验证器指定为 emailValidator。正如我们所见,我们可以将自定义验证器与默认的标准验证器结合起来。还有一个 <f:validateRequired> 元素。

以下屏幕截图显示了 LoginControllerJV 的渲染输出:

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

前端页面视图演示双因素安全登录和验证

Validating groups of properties

现在我们已经 了解 JSF 自定义验证器,我们可以编写一个自定义验证器来验证组 Date-of-Birth 输入字段。我们可以实现这个目标,因为 FacesContext 已经通过了。可以独立于上下文单独查找 UI 组件。

我们将在页面视图中使用称为绑定的 JSF 技术。绑定有效地以 JSF 值发布 javax.faces.component.UIInput 的实例,并使其在页面的其他位置可用。 HTML 呈现 JSF 标记上的属性绑定将组件树中的组件引用绑定到范围变量。以下是相关的JSF代码。特别要注意 代码提取自 jsf-validation/createContact 开头的 JSF 隐藏输入元素.xhtml

<label class="control-label"> Your Date of Birth</label>
<h:inputHidden id="aggregateDobHidden" label="hiddenField1" value="true">
  <f:validator validatorId="dateOfBirthValidator" />
  <f:attribute name="dob_dotm" value="#{dob_dotm}" />
  <f:attribute name="dob_moty" value="#{dob_moty}" />
  <f:attribute name="dob_year" value="#{dob_year}" />
</h:inputHidden>

<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="#{contactDetailControllerJV.dobDay}" binding="#{dob_dotm}" label="Registration Day">
        <f:selectItem itemLabel="----" itemValue=""/>
        <f:selectItems value="#{contactDetailControllerJV.daysOfTheMonth}" var="day" itemLabel="#{day}" itemValue="#{day}" />
        <f:validateRequired/>
        <f:validateLongRange minimum="1" maximum="31" />
      </h:selectOneMenu>
    </div>
  </div>
  <div class="col-sm-3">
    ...
        <h:selectOneMenu id="dobMonth" value="#{contactDetailControllerJV.dobMonth}" binding="#{dob_moty}" label="Registration Month">
          ...
        </h:selectOneMenu>
  </div>
  <div class="col-sm-3">
    ...
        <h:inputText id="dobYear" value="#{contactDetailControllerJV.dobYear}" binding="#{dob_year}" label="Registration Year">
          ...
        </h:inputText>
    </div>
  </div>
  ...
  <div class="col-sm-12">
    <h:message for="aggregateDobHidden" styleClass="alert validation-error"/>
  </div>
</div>

我们利用了隐藏字段,它被标识为 aggregateDobHidden 并带有一个虚拟表单参数名称 hiddenField1。它总是发送一个真值。 <f:attribute> 元素将 附加绑定信息附加到此 UI 组件。我们需要三个属性,名称分别为 dob_dotm(月份中的某天)、dob_moty(一年中的月份)和 dob_year。这些属性是名称相似的页面范围变量 #{dob_dotm}#{dob_moty} 的值表达式class="literal">#{dob_year} 分别。

我们为每个 JSF 选择组件添加一个绑定属性。再次查看以下第一个字段:

<h:selectOneMenu id="dobDay"
    value="#{contactDetailControllerJV.dobDay}"
   binding="#{dob_dotm}"

属性绑定将组件与视图相关联,并使其在由文字字符串定义 #{dob_dotm} 定义的页面范围变量中可用。这是 javax.faces.component.UIInput 类的一个实例,它有一个 getSubmittedValue() 方法来获取提交价值。我们重复为其他两个属性添加绑定。在表单提交期间,hiddenField1 记录了每个单独属性的绑定值。此属性不同于单独的日、月和年属性。

这个绑定技巧允许我们将属性组合在一起以进行表单验证。以下源代码显示了服务器端的验证:

package uk.co.xenonique.digital;
import javax.faces.component.*;
import javax.faces.context.FacesContext;
import javax.faces.validator.*;
import java.util.*;

@FacesValidator("dateOfBirthValidator")
public class FacesDateOfBirthValidator implements Validator {
  @Override
  public void validate(FacesContext context,
  UIComponent component, Object value)
  throws ValidatorException {
    UIInput dayComp   = (UIInput)
      component.getAttributes().get("dob_dotm");
    UIInput monthComp = (UIInput)
      component.getAttributes().get("dob_moty");
    UIInput yearComp  = (UIInput)
      component.getAttributes().get("dob_year");

    List<FacesMessage> errors = new ArrayList<>();
    int day = parsePositiveInteger(
      dayComp.getSubmittedValue());
    if ( day < 1 || day > 31 ) {
      errors.add(new FacesMessage(
        FacesMessage.SEVERITY_ERROR,
        "DOB day must be in the range of 1 to 31 ", null));
    }
    int month = parsePositiveInteger(
      monthComp.getSubmittedValue());
    if ( month < 1 || month > 12 ) {
      errors.add(new FacesMessage(
        FacesMessage.SEVERITY_ERROR,
        "DOB month must be in the range of 1 to 12 ", null));
    }

    Calendar cal = Calendar.getInstance();

    cal.setTime(new Date());
    cal.add(Calendar.YEAR, -18);
    Date eighteenBirthday = cal.getTime();

    cal.setTime(new Date());
    cal.add(Calendar.YEAR, -100);
    Date hundredthBirthday = cal.getTime();

    int year = parsePositiveInteger(
      yearComp.getSubmittedValue());
    cal.set(year,month,day);
    Date targetDate = cal.getTime();
    if (targetDate.after(eighteenBirthday) ) {
      errors.add(new FacesMessage(
        FacesMessage.SEVERITY_ERROR,
        "DOB year: you must be 18 years old.", null));
    }
    if ( targetDate.before(hundredthBirthday)) {
      errors.add(new FacesMessage(
        FacesMessage.SEVERITY_ERROR,
        "DOB: you must be younger than 100 years old.", null ));
    }
    if ( !errors.isEmpty()) {
      throw new ValidatorException(errors);
    }
  }

  public int parsePositiveInteger( Object value ) { /*...*/ }
}

POJO FacesDateOfBirthValidator 验证三个 DOB 属性。它使用 JSF 中称为页面视图中的绑定的技术 来实现这一点,我们稍后会看到。绑定允许将 JSF 输入属性传播到另一个可以在页面上其他地方使用的命名范围变量。至于验证器,我们使用 HTML 隐藏元素作为工具来检索这些有界值作为属性。这就是将组件转换为 javax.faces.component.UIInput 然后提取值的代码的目的。

UIInput dayComp   = (UIInput)
      component.getAttributes().get("dob_dotm");
int day = parsePositiveInteger(
      dayComp.getSubmittedValue());

我们有一个辅助方法,parsePositiveInteger(),用于将文本值转换为整数。在此之前,我们创建一个列表集合来存储任何错误消息。然后,我们验证从 1 到 31 的日期边界。对于 month 属性,逻辑几乎相同。

对于年财产,我们采取了不同的步骤。使用 JDK Calendar 和 Date 类,我们构建了两个 Date 实例:一个代表 100 年前的当前日期,另一个代表 18 年前的当前日期。然后我们可以比较用户的进入日期是否在这两个生日限制内。

如果方法 validate() 的末尾有任何错误,那么它会引发带有错误集合的 ValidatorException 异常。请注意,我们选择使用替代构造函数。

为了完成验证器,辅助方法 parsePositiveInteger() 可以写成如下:

  public int parsePositiveInteger( Object value ) {
    if ( value == null ) return -1;
    try {
        return Integer.parseInt( value.toString().trim());
    }
    catch (NumberFormatException nfe) {
        return -1;
    }
  }

以下 是联系人详细信息的屏幕截图,展示了正在使用的组验证器:

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

出生日期验证器的屏幕截图

Converters

标准 JSF 验证器允许数字开发人员实现很多 功能。在某些情况下,要求超出了默认行为。转换器是在字符串和对象之间进行转换的 JSF 类。与注释定义自定义验证器的方式类似,JSF 允许注册自定义转换器。转换器与 JSF 组件相关联。

注释 @java.faces.convert.FacesConverter 表示 POJO 是自定义 JSF 转换器。该类型必须实现 javax.faces.convert.Converter 接口,该接口有以下方法:

public Object getAsObject( FacesContext context,
  UIComponent component, String newValue);

public String getAsString( FacesContext context,
  UIComponent component, Object value);

getAsObject() 方法将字符串表示形式从客户端转换为目标对象。 其他方法 getAsString() 将对象转换为字符串 表示,在客户端浏览器中呈现。

我们将用一个将字符串转换为扑克牌花色的自定义JSF转换器来举例说明。我们可以使用一个简单的 Java 枚举类来编写它:

public enum FrenchSuit {
    HEARTS, DIAMONDS, CLUBS, SPADES
}

以下是自定义转换器 FrenchSuitConverter 类的完整列表:

package uk.co.xenonique.digital;
import javax.faces.application.FacesMessage;
import javax.faces.component.*;
import javax.faces.context.FacesContext;
import javax.faces.convert.*;
import static uk.co.xenonique.digital.FrenchSuit.*;

@FacesConverter("frenchSuitConverter")
public class FrenchSuitConverter implements Converter {
  @Override
  public Object getAsObject(FacesContext context,
    UIComponent component, String value) {
    String text = value.trim();
    if ( text.length() == 0 ) {
      text = ((UIInput)component).getSubmittedValue().toString();
    }
    text = text.toUpperCase();
    switch (text) {
      case "HEARTS": return HEARTS;
      case "DIAMONDS": return DIAMONDS;
      case "CLUBS": return CLUBS;
      case "SPADES": return SPADES;
      default:
        throw new ConverterException(
          new FacesMessage(
            FacesMessage.SEVERITY_ERROR,
            "Unable to convert object to string", null));
    }
  }

  @Override
  public String getAsString(FacesContext context,
    UIComponent component, Object value) {
    if ( value instanceof String ) {
      return value.toString();
    }
    else if ( !(value instanceof FrenchSuit)) {
      throw new ConverterException(
        new FacesMessage(
          FacesMessage.SEVERITY_ERROR,
          "Unable to convert object to string", null));
    }
    switch ((FrenchSuit)value) {
      case HEARTS: return "Hearts";
      case DIAMONDS: return "Diamonds";
      case CLUBS: return "Clubs";
      case SPADES: return "Spades";
    }
    throw new IllegalStateException(
        "PC should never reach here!");
  }
}

POJO 是带注释的@FacesConverter,其值成为页面视图中的标识符。

JSF 调用 getAsObject() 方法,其文本表示为 修剪并转换为大写,以便于比较。在方法开始时,新值可能是一个空白字符串。如果这是真的,那么我们从已经提交的值中检索文本表示。对于这个特定的转换器,空值的用例是可能的,所以我们添加了保护。如果进程出现问题,该方法会引发异常 javax.faces.convert.ConverterException

JSF 调用 getAsString() 方法来将对象表示转换为字符串。根据对象类型,该方法防御不同类型的输入。输入值可能只是一个字符串,也可能是 FrenchSuit 枚举的一个实例。如果输入值不是其中之一,则该方法引发 ConverterException

在现实世界中,我们知道一套扑克牌中总会有四种花色,因此,我们可以对枚举的可维护性充满信心。作为一个数字开发者,你可能没有这样的奢侈品,因此,在转换器和验证器中应用防御性编程原理可以走很长的路追踪错误的方法。

以下是练习自定义转换器的页面 /jsf-validation/french-suit.xhtml 的摘录:

<h:form id="cardForm" styleClass="form-horizontal" p:role="form">
  ...
  <div class="form-group">
    <h:outputLabel for="suit" class="col-sm-3 control-label">
        Card Suit</h:outputLabel>
    <div class="col-sm-9">
      <h:selectOneMenu class="form-control" label="Suit" id="suit" value="#{frenchSuitController.suit}" >
        <f:converter converterId="frenchSuitConverter" />
        <f:selectItem itemLabel="-" itemValue="" />
        <f:selectItem itemValue="#{frenchSuitController.suitEnumValue('HEARTS')}" />
        <f:selectItem itemValue="#{frenchSuitController.suitEnumValue('CLUBS')}" />
        <f:selectItem itemValue="#{frenchSuitController.suitEnumValue('DIAMONDS')}" />
        <f:selectItem itemValue="#{frenchSuitController.suitEnumValue('SPADES')}" />
        <f:validateRequired/>
      </h:selectOneMenu>
      <h:message for="suit" styleClass="alert validation-error"/>
    </div>
  </div>

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

在前面的视图中,我们使用了一个下拉菜单 <h:selectOneMenu>,它允许用户选择卡片套装。这段代码你现在应该很熟悉了。不同之处在于每张牌的值表达式,它们都是带有字符串字面量参数的方法调用。表达式语言允许您使用参数调用方法。因此,表达式:#{frenchSuitController.suitEnumValue('HEARTS')} 转换为控制器上的方法调用。

<h:selectOneMenu>body 内容中,我们明确引用了 通过标识符自定义转换器,并通过以下方式将其与 UI 组件关联:

<f:converter converterId="frenchSuitConverter" />

JSF 然后调用自定义转换器,以便将单个 FrenchSuit 枚举从页面视图转换为字符串。这听起来像是一种显示值列表的迂回方式,但此示例演示了 FrenchSuitConverter 中的方法 getAsString() 是被调用。此外,它还说明了如何以稳健的方式在页面视图和控制器中引用 Java 枚举。

现在让我们检查一下控制器:

package uk.co.xenonique.digital;
import javax.faces.context.Flash;
import javax.faces.context.FacesContext;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named("frenchSuitController")
@ViewScoped
public class FrenchSuitController {
    private String firstName;
    private String lastName;
    private FrenchSuit suit;

    public String doAction() {
        Flash flash = FacesContext.getCurrentInstance().
                getExternalContext().getFlash();
        flash.put("firstName",firstName);
        flash.put("lastName",lastName);
        flash.put("suit", suit);
        return "/jsf-validation/french-suit-complete?redirect=true";
    }

    public String cancel() {
        return "/index.xhtml?redirect=true";
    }

    public FrenchSuit  suitEnumValue( String name ) {
        return FrenchSuit.valueOf(name);
    }

    // Getters and setters omitted
}

FrenchSuitController 的代码稍稍领先。首先,让我提请您注意 suitEnumValue() 方法,它将字符串文字转换为枚举类型 FrenchSuit。这是在页面视图中获取枚举的一个非常方便的技巧,因为表达式语言不允许直接访问 Java 枚举。它特别适用于通过不同项目版本随时间变化的枚举。

doAction()cancel() 方法返回带有特殊查询参数 redirect 的 URI =真。这是 JSF 返回可收藏 URL 的指令;我们将在本章稍后部分讨论这个主题。

doAction() 中,我们第一次在 JSF 中使用 Flash 作用域。 Flash 范围是一个临时上下文,允许控制器将数据传递到下一个导航视图。请记住,视图范围仅对导航到同一页面视图的当前控制器有效。当 FacesContext 移动到下一个页面视图时,@ViewScoped 托管 bean 超出范围。这些方法在 javax.faces.context.Flash 实例中设置键值关联。

拼图的最后一块展示了我们如何在页面视图中使用 Flash 范围。此代码可以在文件 /jsf-validation/french-suit-complete.xhtml 中找到。以下代码是相同的摘录:

<ui:define name="mainContent">
  <h1> House of Card with JSF Validation</h1>
  ...
  <div class="jumbotron">
    <h1> Complete </h1>
    <p>
      Terrific! You completed the French suit action.
      Your first name is <b>#{flash['firstName']}</b> and
      your last name is <b>#{flash['lastName']}</b> and
      you chose <b>#{flash['suit']}</b> as the playcard suit.
    </p>
  </div>
  ...
</ui:define>

在此页面视图中,我们使用 map 表达式从 Flash 范围内检索值。表达式 #{flash['suit']} 是用户选择的西装。读者还可以查看默认 JSF 转换器 javax.faces.convert.EnumConverter 的文档。在同一个包中,还有BigDecimalConverterBigIntegerConverterDateTimeConverter< /code>、ByteConverterLongConverterDoubleConverter

我会给留下表单视图的屏幕截图french-suit.xhtml:

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

带有 JSF 转换器的卡片组截图

以下是显示 french-suit-complete.xhtml 的结束状态的屏幕截图。标记以 Bootstrap 的 CSS jumbotron 样式显示了漂亮的视觉效果。

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

表单提交和验证后纸牌屋示例的第二个屏幕

我们 涵盖了发生在服务器端的大量验证。现在让我们继续进行 AJAX 验证。

Validating immediately with AJAX

异步 JavaScript 和 XMLAJAX)是一组技术它们共同解决了检索网页部分更新的限制,并提供了丰富的交互式用户体验。 AJAX 的关键是术语异步,它建立在万维网联盟 (W3C) 标准,即 XmlHttpRequest 对象。它于 2006 年在 Internet Explorer 中引入,现在所有现代 网络浏览器都支持这个 对象。异步模式允许浏览器在单独的连接上向服务器发出数据传输请求;企业后端响应以数据结果响应,通常是 JSON 或 XML。与每次重新加载整个页面相比,这些 AJAX 数据传输往往更小。

JSF 内置了对 AJAX 请求和响应的支持;开发人员无需了解 XmlHttpRequest 和 JavaScript 编程的更详细的细节即可获得即时响应的好处。数字开发人员可以从用于执行 AJAX 交互的默认 JavaScript 库开始。

很容易在JSF中使用AJAX ="literal"><f:ajax> 标签。这个 Core JSF 标记向 UI 组件注册 AJAX 行为,并用于对字段执行验证。开发人员只需将标签放在代表组件的 HTML JSF 标签的正文内容中,这需要验证。

以下代码显示了如何将此标记与联系详细信息应用程序一起使用:

<f:ajax event="blur" render="firstNameError"/>

tag 属性事件确定框架何时调用 AJAX 验证。模糊值表示它发生在用户从该组件字段移动到下一个输入字段时。因此,当用户在台式计算机上按下 Tab 键或在手机或平板电脑上导航 UI 时,会立即进行验证,因为 JavaScript 会触发对服务器的 AJAX 请求。第二个属性,render,通知框架有关特定 UI 组件的信息,以将错误消息呈现给(如果有)。 JSF 接收到 AJAX 响应,如果出现错误,它会知道 HTML 组件 ID 以使用验证消息进行更新。

我们来看项目ch04/jsf-crud-ajax-validation,它是页面视图jsf-validation/createContactDetail的完整摘录。 xhtml:

<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" label="Title" id="title" value="#{contactDetailControllerJV.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" />
        <f:validateRequired/>
        <f:ajax event="blur" render="titleError"/>
      </h:selectOneMenu>
      <h:message id="titleError" for="title" styleClass="alert validation-error"/>
    </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" label="First name" value="#{contactDetailControllerJV.contactDetail.firstName}" id="firstName" placeholder="First name">
          <f:validateRequired/>
          <f:validateLength maximum="64" />
          <f:ajax event="blur" render="firstNameError"/>
      </h:inputText>
      <h:message id="firstNameError" for="firstName" styleClass="alert validation-error"/>
    </div>
  </div>
  <div class="form-group">
    <h:outputLabel for="middleName" class="col-sm-3 control-label">
        Middle name</h:outputLabel>
      ...
  </div>
  <div class="form-group">
    <h:outputLabel for="lastName" class="col-sm-3 control-label">
        Last name</h:outputLabel>
    <div class="col-sm-9">
      <h:inputText class="form-control" value="#{contactDetailControllerJV.contactDetail.lastName}" label="Last name" id="lastName" placeholder="Last name">
          <f:validateRequired/>
          <f:validateLength maximum="64" />
          <f:ajax event="blur" render="lastNameError"/>
      </h:inputText>
      <h:message id="lastNameError" for="lastName" styleClass="alert validation-error"/>
    </div>
  </div>
  ...
</h:form>

此页面视图举例说明了在 JSF 中向页面添加 AJAX 验证非常容易。 <f:ajax> Core JSF 标签嵌入在对应的 HTML JSF 标签中,如您所见 用于名字和 姓氏字段。非 AJAX 和 AJAX 页面的联系详细信息的另一个区别是添加了标识符,例如 firstNameErrorlastNameError <h:message> 标签。我们需要添加 HTML 标识符元素,以允许 JavaScript 按 ID 从浏览器的 中查找 HTML 元素文档对象模型 (DOM)。

除了中间名和简报 HTML 复选框字段之外,该页面的所有属性都添加了 AJAX 验证。 AJAX 验证也适用于自定义验证器和转换器。

以下屏幕截图说明了单个属性 AJAX 验证:

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

演示每个输入字段的单个验证的屏幕截图

Validating groups of input fields

到目前为止,我们已经看到了对单个输入字段实例的 JSF AJAX 验证。 <f:ajax> 标签也可以在一组组件上进行验证。我们可以将标签包含在一个或多个 JSF 输入字段周围,然后 <f:ajax> 标签成为 UI 组件的父级。这会导致 JSF 将 AJAX 验证应用于多个组件。

让我们使用以下页面视图将 组验证添加到联系人详细信息表单中的 Date-of-Birth 字段中:

<h:inputHidden id="aggregateDobHidden" label="hiddenField1" value="true">
  <f:validator validatorId="dateOfBirthValidator" />
  <f:attribute name="dob_dotm" value="#{dob_dotm}" />
  <f:attribute name="dob_moty" value="#{dob_moty}" />
  <f:attribute name="dob_year" value="#{dob_year}" />
</h:inputHidden>

<f:ajax event="blur" render="dobDayError dobMonthError dobYearError">
  <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="#{contactDetailControllerJV.dobDay}" binding="#{dob_dotm}" label="Registration Day">
          ...                        
        </h:selectOneMenu>
      </div>
    </div>
    <div class="col-sm-3">
      <label class="control-label" for="dobMonth">Month</label>
      <div class="controls">
        <h:selectOneMenu id="dobMonth" value="#{contactDetailControllerJV.dobMonth}" binding="#{dob_moty}" label="Registration Month">
          ...                        
        </h:selectOneMenu>
      </div>
    </div>
    <div class="col-sm-3">
      <label class="control-label" for="dobYear">Year</label>
      <div class="controls">
          ...
      </div>
    </div>
    <div class="col-sm-12">
      <h:message id="dobDayError" for="dobDay" styleClass="alert validation-error"/>
    </div>
    <div class="col-sm-12">
      <h:message id="dobMonthError" for="dobMonth" styleClass="alert validation-error"/>
    </div>
    <div class="col-sm-12">
      <h:message id="dobYearError" for="dobYear" styleClass="alert validation-error"/>
    </div>
    <div class="col-sm-12">
      <h:messages for="aggregateDobHidden" styleClass="alert validation-error"/>
    </div>
  </div>
</f:ajax>

如您所见,我们 用包含 <f:ajax> 标记包围 DOB 输入字段.事件属性仍设置为 blur。 render 属性设置为特定验证消息的 HTML 元素 ID 列表,即 dobDayErrordobMonthErrordobYearError

aggregationDobHidden HTML 隐藏元素与非 AJAX 示例中的相同,以说明验证不会干扰自定义验证。

回顾一下,使用 <f:ajaxTag>,并将其嵌入到任何 JSF 组件中。要验证一组多个组件,请用 <f:ajaxTag> 将组件括起来。

以下屏幕截图描述了 DOB 字段周围的多组件 AJAX 验证。一年中的月份组件最后是浏览器的焦点,因此,相应的验证消息描述了 onblur DOM JavaScript 事件。类似地,在这组字段之间使用制表符会一一生成错误消息。

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

出生日期输入字段的组验证屏幕截图

AJAX custom tag in depth

了解可应用于此 Core JSF 自定义标记的属性很有用。下表描述了 <f:ajax> 的属性。

属性

描述

延迟

指定向服务器发送多个 AJAX 请求之间的延迟(毫秒)。请求由 JSF 实现排队。将该值设置为 none 将禁用该功能。

已禁用

指定一个布尔值来指示标签状态。如果设置为 true,则不会呈现 AJAX 行为。默认值为假。

事件

定义一个 字符串枚举,表示AJAX 操作的事件类型。默认情况下,JSF 确定组件的事件名称。

执行

枚举 以空格分隔的名称集合,这些名称代表在服务器上执行的组件。该值可以是字符串或值表达式。默认值为@this,表示AJAX行为的父组件。

立即

声明一个 布尔值,指示是否在 JSF 生命周期的早期处理输入值。

监听器

表示在广播事件期间将被调用的监听方法的名称,即AjaxBehaviorEvent

onerror

指定 将接受错误的 JavaScript 函数的名称。

onevent

指定 将处理 UI 事件的 JavaScript 函数的名称。

渲染

枚举当 AJAX 行为完成时将在客户端 上呈现的 UI 组件集合。该值可以是由空格分隔的组件标识符集合,也可以是值表达式。默认值为 @none,表示不渲染任何组件。

从前面的表格中,您会注意到 execute 和 render 属性可能指示其他有意义的值。 execute 属性规定了要在服务器上执行的组件。当 AJAX 行为完成时,render 属性确定受影响的 UI 组件。下表列出了属性值:

价值

描述

@all

指定 所有组件都在视图中执行或呈现。

@form

指定仅执行或呈现作为表单子项的组件。

@none

指定不执行或呈现任何组件。

@this

指定仅执行或呈现触发 AJAX 请求的当前组件。

组件标识符列表

枚举 作为 AJAX 请求显式执行或呈现的 UI 组件的标识符。

表达语言

指定最终返回字符串集合的值表达式,该集合指示作为 AJAX 请求-响应执行或呈现的 UI 组件。

A partial JSF lifecycle


JSF 生命周期实际上适用于所有 Faces 请求和响应,包括那些来自支持 AJAX 的组件的请求和响应。在幕后,JSF 为 AJAX 请求和响应实例化一个特殊对象 javax.faces.context.PartialViewContext,并将其输入处理生命周期。此上下文对象包含允许 JSF 在服务器端更新组件模型的信息。基于 部分上下文,JSF 决定是否完成所选UI 组件的部分处理和/或UI 组件的部分呈现。部分处理对应于生命周期的 Apply-Requests-Values、Process-Validations 和 Update-Model-Values 阶段。部分渲染是指渲染-响应阶段。

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

部分请求 – AJAX 提交的响应生命周期

上图概括了我们对 JSF 生命周期中 AJAX 请求 和响应的部分上下文的理解。

Handling views


在本章中,我们主要研究了使用 JSF 对用户输入的验证。我们忽略了一些关于导航的杂项概念。现在让我们谈谈处理视图和导航。

Invoking controller methods

几种方法可以从带有参数的 页面视图调用控制器。对于数字电子商务应用程序中的许多情况,开发人员需要检索特定的数据记录,触发服务器端动作,或者在后端从客户端保存某个状态。

Parameterized method invocations

JSF 允许 开发人员使用表达式语言将参数传递给页面视图中的方法。 Chapter 3, 构建 JSF 表单 称为方法表达式调用,它是在 JSF 2.0 中引入的。

以下是页面视图 /jsf-miscellany/examplar-methods.xhtml 的摘录:

<h:form id="methodExampler" styleClass="form-horizontal" p:role="form">
  ...
  <div class="form-group">
    <div class="col-sm-9">
    <p>
        Invoke JSF controller with 3 literal arguments
    </p>
    <p class="monospace">
        \#{examplarController.methodThreeArgs(
          'Obiwan','Ben','Kenobi')}
    </p>
    <h:commandButton styleClass="btn btn-primary" action="#{examplarController.methodThreeArgs( 'Obiwan','Ben','Kenobi')}" value="Invoke" />
    </div>
  </div>
  ...
 
</h:form> 

上述代码描述了 <h:commandButton> 标记和一个动作值表达式,即 #{examplarController.methodThreeArgs('Obiwan', '本','克诺比')}。这是一个带有三个文字字符串参数的方法调用。

参数也可以是对其他 JSF 范围实例的引用。以下是另一个调用,只有两个参数显示了这一点:

  <h:commandButton styleClass="btn btn-primary" action="#{examplarController.methodTwoArgs( examplarController.city, examplarController.country)}" value="Invoke" />

参数是从控制器 bean 属性动态设置的。现在让我们看一下控制器 ExamplarController

@Named("examplarController") @ViewScoped
public class ExamplarController {
  private String city = "London";
  private String country="United Kingdom";

  public String methodOneArg( String alpha ) {
    Flash flash = FacesContext.getCurrentInstance().
      getExternalContext().getFlash();
    flash.put("result",
      String.format("executed methodOneArg(\"%s\")",
        alpha ));
    return "examplar-methods-complete?redirect=true";
  }

  public String methodTwoArgs(
    String alpha, String beta ) {
    Flash flash = FacesContext.getCurrentInstance().
      getExternalContext().getFlash();
    flash.put("result",
      String.format("executed methodTwoArgs(\"%s\", \"%s\")",
      alpha, beta ));
    return "examplar-methods-complete?redirect=true";
  }

  public String methodThreeArgs(
    String alpha, String beta, String gamma ) {
    Flash flash = FacesContext.getCurrentInstance().
      getExternalContext().getFlash();
    flash.put("result",
      String.format("executed methodThreeArgs(\"%s\", \"%s\", \"%s\")",
      alpha, beta, gamma ));
    return "examplar-methods-complete?redirect=true";
  }

  ...
  // Getters and setters omitted
}

有三种方法,分别是,称为methodOneArg ()methodTwoArgs()methodThreeArgs()。这些名称对于可以传递的参数数量是不言自明的;每个人都在 JSF Flash 范围内保存一个输出结果,然后再转到下一个页面视图 /jsf-miscellany/examplar-methods-complete.xhtml

以下是最终状态 Facelet 视图的提取 示例方法完成.xhtml:

<ui:composition template="/basic_layout.xhtml">
  <ui:define name="title">
    <title> JSF Method Invocation Example </title>
  </ui:define>

  <ui:define name="mainContent">
    <h1> Method Invocations Complete</h1>
    <h:messages globalOnly="true" styleClass="alert alert-danger" />
    <div class="jumbotron">
      <h1> Complete </h1>
      <p>
        Terrific! You completed the action.
        The result message was <b>#{flash['result']}</b>.
      </p>
    </div>
    ... 
  </ui:define> <!--name="mainContent" -->
</ui:composition>

Passing parameters to the controller

在创建 JSF 2.0 规范之前,可以使用 <f 将参数发送到支持 bean 控制器: <h:commandLink>, <h:commandButton> 正文中的 param> 标记, 或 <h:link> 标签。尽管该技术已被 JSF 2.0 中的方法调用表达式所取代,但它仍然是一种向控制器发送越界通信的有用技术。

以下代码显示了配方,我们在 <h:commandButton> 中嵌入了两个 <f:param> 元素自定义标签:

<div class="form-group">
  <div class="col-sm-9">
    <p>
        Invoke JSF controller method with parameters
    </p>
    <p class="monospace">
      \#{examplarController.methodPassingAttribute()
      <br/>
          name="callToActionText" value="FindNearestDealer"
      <br/>
      name="customerType" value="Motorbikes"
    </p>
    <h:commandButton styleClass="btn btn-primary" action="#{examplarController.methodPassingParameters()}" value="Invoke" >
      <f:param name="callToActionText" value="FindNearestDealer"/>
      <f:param name="customerType" value="Motorbikes"/>
    </h:commandButton>
  </div>
</div>

此页面视图上的创作调用控制器的无参数方法,methodPassingParameters()。 JSF 通过 Faces 请求将两个参数传递给目标方法,键名为 callToActionTextcustomerType

让我们看看处理这个调用的控制器方法:

  public String methodPassingParameters() {
    Map<String,String> params = FacesContext.getCurrentInstance()
        .getExternalContext().getRequestParameterMap();
    String ctaText = params.get("callToActionText");
    String custType = params.get("customerType");
    Flash flash = FacesContext.getCurrentInstance().
        getExternalContext().getFlash();
    flash.put("result",
      String.format("executed methodPassingParameters() " +
        "ctaText=\"%s\", custType=%s", ctaText, custType ));
    return "examplar-methods-complete?redirect=true";
  }

方法 methodPassingParameters() 中,我们从 中检索参数FacesContext 实例通过使用嵌套调用 getRequestParameterMap()。然后可以直接从 Map 类型的地图集合中访问参数。有趣的是,参数只能是字符串,并且这种技术可以与 JSF 2.0 及更高版本中的方法参数调用结合使用。

以下屏幕截图显示了演示本节中描述的方法调用技术的页面:

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

方法调用 JSF 示例的屏幕截图

Invoking an action event listener

处理视图的最后一种技术调用控制器中的动作侦听器。任何接受 参数的实例 方法>javax.faces.event.ActionEvent 可以是一个动作事件监听器。动作侦听器与页面标记中的 UI 组件相关联。 JSF 在调用动作之前调用动作侦听器,因此这种技术有助于挂接业务逻辑并为动作调用设置数据。

以下是实施此技术的方法调用页面的摘录。我们将在这段代码中省去 Bootstrap CSS 标记:

<h:commandButton styleClass="btn btn-primary" action="#{examplarController.performAction}" actionListener="#{examplarController.attributeListener}" value="Invoke">
  <f:attribute name="contactName" value="Roy Keane" />
</h:commandButton>

<h:commandButton> 标签有一个额外的 actionListener 属性设置为引用动作监听器方法的表达式,attributeListener()。该标签还将 嵌入<f:attribute>定义一个传递的属性。 action 属性引用方法 performAction()

让我们检查我们的 ExamplarController 支持 bean 以查看代码:

  private String contactName;

  public void attributeListener(ActionEvent event){
    contactName = (String) event.getComponent()
      .getAttributes().get("contactName");
  }

  public String performAction() {
    Flash flash = FacesContext.getCurrentInstance()
      .getExternalContext().getFlash();
    flash.put("result",
      String.format("executed performAction()
        contactName=\"%s\" ", contactName ));
    return "examplar-methods-complete?redirect=true";
  }

在提交命令按钮时,JSF 首先使用 ActionEvent 实例调用方法 attributeListener()。我们可以找到负责调用的组件并检索存储在其上的属性。在这种情况下,我们检索键入为 contactName 的属性的值。该值存储在控制器的实例变量中。 (如果我们的支持 bean 的范围设置为 @RequestScope@ViewScope,因为随着时间的推移,实例变量将在多个请求之间共享!)

在动作侦听器返回后,JSF 最终会调用动作方法 performAction()。实例变量 contactName 可用,并且具有来自 页面的当前值。该方法继续 到下一个页面视图。

Redirection pages

如果您一直按照本章中的示例进行操作,那么您一定注意到页面浏览量带有查询参数 redirect=true(或 faces-redirect=true,根据官方 JSF 规范)。这是对 JSF 的指令,用于将 HTTP 响应发送回 Web 客户端以重定向到 URL。为什么需要这个后缀?它允许用户为页面视图添加书签,因为 JSF 框架通过仅呈现输出有效地向用户隐藏当前页面视图。主要问题是内部页面转发,这使得使用数字应用程序的客户难以记住或收藏他们的位置。如果客户有一个深度嵌套的信息架构站点,那么提供页面重定向的能力是关键。第二个问题是,如果您的 Web 应用程序以线性方式执行流程,则 Web 浏览器的 URL 会更新,但始终显示流程中的前一页。

重定向在控制器方法 examplar-methods-complete?redirect=true 中起作用,这会导致 JSF 向浏览器发送 HTTP 响应重定向。 Web 浏览器将重定向解释为 URL 的另一个 HTTP GET 请求,例如 http://localhost:8080/jsf-crud-ajax-validation-1.0-SNAPSHOT/jsf-miscellany/examplar-方法.xhtml。重定向的结果是每个页面导航或操作至少发生两个请求-响应事件。如果您还记得,@ViewScoped@RequestScoped bean 的范围仅在短时间内可用。当 JSF 处理来自重定向指令的 HTTP GET 时,原来的 bean 已经消失了。这就是示例使用 Flow 范围的原因;该范围保证来自控制器业务逻辑的数据在显示下一个页面视图之前仍然存在。

指定页面重定向的另一种方法是通过 faces-config.xml 用于特定的导航案例。我们可以定义一个案例如下:

<navigation-rule>
  <from-view-id>epayment.xhtml</from-view-id>
  <navigation-case>
    <from-outcome>payment-delivery</from-outcome>
    <to-view-id>payment-deliver.xhtml</to-view-id>
    <redirect />
  </navigation-case>
  <navigation-case>
    <from-outcome>payment-creditcard</from-outcome>
    <to-view-id>payment-credit.xhtml</to-view-id>
    <redirect />
  </navigation-case>
</navigation-rule>

这种配置风格在设置第三方 JSF 包时很有用。当然,它也 为库编写者提供了灵活性,并且不会污染Java 管理的bean 重定向字符串。我想这是一个以马换课程的情况,因此取决于项目的目的。

最后,开发者可以通过提交链接和按钮直接设置重定向到页面视图。以下代码展示了这种技术:

<h:commandButton style="btn bth-primary" action="epayments.xhtml?faces-redirect=true" value="Proceed to Paymemt" />

Debugging the JSF content

也许我应该早点介绍 JSF 的这个特性,因为学习使用 JSF 进行开发可能会让初学者感到困惑。如果您包含 <ui:debug/> 模板视图之一内的自定义标记元素。实际上,框架的 Facelet 视图渲染器输出负责此功能。

通过将 单个 <ui:debug> 嵌入 < ;ui:insert> 标记使 JSF 将一个特殊的 UI 组件添加到 UI 层次结构树中。此调试组件捕获 UI 层次结构的 Facelet 视图信息和当前状态,包括应用程序中的任何作用域变量。信息是在渲染时捕获的。如果用户按下键 Ctrl + Shift + D,JSF 会打开一个单独的浏览器窗口,显示可调试信息,这在困难的情况下非常有用。应用程序的主模板是添加 <ui:debug> 标签的最佳位置。

<ui:debug> 标签接受以下属性:

姓名

类型

描述

热键

字符串

定义 导致可调试窗口打开的热键的单个字符。默认值为 d

渲染

ValueExpression

指定 是否呈现调试组件。它必须是计算结果为 truefalse 的值表达式或字符串文字。

以下屏幕截图显示了示例方法调用:

读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX

单击 加号 (+) 会展开内容,以便开发者可以动态查看更多信息。

Summary


本章重点介绍 JSF 验证的不同形式,因为用户知道数据是否输入正确非常重要。我们检查了两种形式的验证方法:客户端和服务器端。我们查看了 FacesMessage 实例并学习了如何创建它们。之后,我们开始介绍服务器端的验证,特别是 Java EE 7 中的 Bean Validation 框架。然后,我们将开发人员的旅程延伸到 JSF 验证。我们学习了如何创建自定义验证器和转换器。我们还学习了如何使用 AJAX 执行即时模式验证并了解部分上下文生命周期。最后,我们花了很多时间处理视图并将信息从页面视图传递到控制器。在此过程中,我们处理了 JSF 流范围和页面重定向。

在下一章中,我们将把注意力转向会话范围,并开始将有用的流程流应用程序放在一起。在这一点上,我们为蓬勃发展的数字 JSF 应用程序添加了技巧和复杂性。我会让你看到那里。

Exercises


  1. <h:outputLink><h:commandButton> 元素之间的根本区别是什么?如何使用 CSS 框架(如 Bootstrap)适当地设置控件元素的样式?

  2. 在上一章中,有一些练习围绕着开发一个用于向当地的爱好者读书俱乐部注册新人的 Web 应用程序。您是否碰巧将内容写在单独的页面中而没有重复使用?

  3. 将 UI 模板合成应用到您的爱好读书俱乐部项目。将此版本称为第二版并保存第一个版本供您参考。使用模板合成标签 <ui:define><ui:composition><ui:insert> 仅在此阶段。

  4. 添加 <ui:debug> 自定义标签来掌握模板页面。这个特殊标签对开发者有什么作用?

  5. 一个恼怒的业务利益相关者来到您的办公室,并告诉您他们在使用欺骗数据方面遇到的问题。似乎网上有些顽皮的人在伪造数据录入,这给个案工作者造成了更大的负担。作为 JSF 的顾问,请解释如何使用验证来保护后端数据库中的数据。只有服务器端验证有效吗?只有客户端验证有效吗?

  6. 参考前面的 Hobby Book Club 应用程序,现在让我们向您创建的 JSF 表单元素添加验证。

  7. 将 Bean Validation 添加到 registrant 类(Registrant.java — 您可能在自己的项目中对此类进行了不同的命名)。您的用户会对验证输出感到满意吗?

  8. 当您只向应用程序添加服务器端验证时会发生什么?

  9. Bean Validation 和 JSF 验证有什么区别?

  10. Bean Validation 和 JSF 验证有什么相似之处?

  11. 根据用户,Bean Validation 和 JSF 验证的错误消息有多合适?

  12. 从创建页面开始。根据注册人的姓名进行验证。您可以直接在页面视图上使用 <f:validateRequired><f:validateLength> 进行验证。将适当的 <h:messages> 添加到页面视图。

  13. 一些注册人使用 Facebook、Twitter 和 Instagram 等社交网络。向 Registrant POJO 添加一些属性。添加 URL 验证器以验证社交网络属性是否正确。使用正则表达式验证器来验证 Twitter 帐户语法,或者,也许,编写您自己的自定义验证器。

  14. 下载本书的源代码示例并运行第 4 章的示例代码。研究如何从服务器端进行验证。

  15. 鉴于您已经开发了带有服务器端验证的项目,您必须将 Hobby Book Club Web 应用程序提升一个档次。使用 AJAX 为控件元素添加客户端验证。您需要将适当的 <f:ajax> 元素添加到 JSF 表单控制元素中。不要忘记每个控件都需要一个区域来呈现特定的错误消息;因此,您不会在页面上非常接近地添加相应的 <h:message> 元素。

  16. 下载 Chrome Developer Web Tools 或类似的网页检查开发工具,并检查 JSF 应用程序的 HTML 内容。对于各种 HTML 元素(尤其是表单)的命名,您有什么观察和注意事项?

  17. 稍作休息,将现代 CSS 样式添加到 Hobby Book Club 应用程序中。请同事或朋友评估您的应用程序的用户体验并收集反馈。根据反馈采取行动;改变周围的内容。

  18. 向您的 CRUD 应用程序添加取消操作;您需要什么来确保 JSF 不验证输入?