Unit 08 - 驗證表單資料

4 minute read

目標

  • 瞭解前端及後端表單資料驗證的差別
  • 瞭解 JSF 如何在伺服器端執行表單資料的驗證
  • 使用 JSF 提供的資料驗證 facelet
  • 顯示驗證錯誤訊息

知識:

前端與後端資料驗證

前端資料驗證指在提交表單至伺服器前, 使用 JS 驗證表單資料是否正確. 若發生驗證錯誤, 會停止提交資料到伺服器, 並在頁面上顯示錯誤訊息。

前端資料驗證在提交表單至伺服器後, 在伺服器端進行表單資料的驗證. 若發生錯誤, 伺服器會回應(response) 錯誤訊息給瀏覽器, 瀏覽器在將訊息 render 在頁面上.

JSF 的表單資料驗證過程

表單資料提交的伺服器後, 我們將提交的資料稱之為 Submitted Value. 這些 Submitted Value 的型別為字串型別, JSF 需要先使用轉換器(Converter) 轉換成特定型別後, 才能做資料驗證。

驗證完畢後的被儲存到元件(Component)中, 並視新/舊新不同, 排入ValueChangeEvent至事件佇列中。

轉換過程如上圖所示, 過程如下:

  1. 檢查元件中的 Submitted Value 是否為 Null 值. 如為 Null 值, 結束驗證程序. 若否, 繼續下個步驟。
  2. 使用轉換器轉換 Submitted Value 的型別至特定的型別.
  3. 若轉換失敗, 拋出 ConverterException 例外, 跳至步驟 6. 若成功, 下一步進行進行資料驗證.
  4. 驗證轉換後的資料。若失敗, 拋出 ValidatorException 例外, 跳至步驟 7. 若成功, 下一步比較新舊值是否有不同.
  5. (新舊資料比較) 比較元件上的新舊值是否不同. 若是, 排入一個 ValueChangeEvent 值事件佇列(Event Queue), 跳至步驟 9 結束驗證程序. 若否, 直接跳至步驟 9。
  6. (轉換失敗) 產生轉換錯誤訊息, 跳至步驟 8.
  7. (驗證失敗) 產生嚴重錯誤訊息, 跳至步驟 8.
  8. (將元件設為 invalid) 將元件的 valid 特性設為 false, 表示元件上的 Submitted Value 不是有效值。跳至步驟 10, 結束驗證程序。
  9. 結束驗證程序

Validation Facelets

標準驗證器 JSF 提供以下的標準驗證器:

數值範圍:

  • f:validateDoubleRange: 驗證 java.lang.Double 型態資料介於某個範圍.
  • f:validateLongRange: 驗證 java.lang.Long 型態資料介於某個範圍.
  • f:validateLength: 驗證 java.lang.String 型態資料的長度介於某個範圍

必要輸入:

  • f:validateRequired: 驗證必要輸入欄位是否為空值. 作用和設定 facelet tag 的 required 屬性相同。

正規表示式:

  • f:validateRegex: 使用正規表示式(Regular Expression)對資料進行驗證。

可以使用驗證器(Validator)的 Facelet tag

只要是具備輸入能力的 Faclet Tag 都可以使用驗證器, 檢查輸入資料是否合乎要求。這些 Facelet Tag 包含:

  • 文字輸入:h:inputXXX 類型的 Tag, 如: h:inputText, h:inputSecret, h:inputTextarea
  • 是非選擇: h:selectBooleanCheckbox
  • 單選: h:selectOne 類型的 Tag, 如: h:selectOneListbox, h:selectOneRadio, h:selectOneMenu
  • 多選: h:inputMany 類型的 Tag, 如: h:selectManyCheckbox, h:selectManyListbox, h:selectManyMenu

使用方式

有數種方式讓我們在具備輸入能力 Faclet Tag 可以使用驗證器。最常使用的方式為在輸入類型 Facelet Tag 內夾入驗證標籤。

1
2
3
4
<h:inputText id="zipcode" value="#{user.zipCode}">
    <f:validateLength maximum="5" minimum="5" />
    <f:validateLongRange minimum="90000" maximum="99999" />
</h:inputText>

Code Source: P180 in [1]

上述程式碼驗證輸入的郵遞區號

  • 字串的長度需要等於 5
  • 轉換成長整數之後, 數值範圍需要介於 9000 到 99999 之間。

還有其他方式:

  • 標籤方式
    • 將輸入標籤加入在驗證標籤之內, 要求在內的輸入標籤進行相同的資料驗證規則。
 <h:form>
    <f:validateLongRange maximum="100">
        <h:inputText /> 
        <h:inputText />
    </f:validateLongRange>
    <h:messages  />
    <h:commandButton value="Submit" />
</h:form>
  • 指定 validator-Id
    • 使用 f:validator 標籤, 配合驗證器的 validator-Id, 將驗證器指定到輸入標籤。
    • 在元件中使用 validatorId 屬性, 指定特定的驗證器的 validator-Id

停用驗證器

f:validateXXX 類型的驗證標籤內提供 disabled 屬性[4], 讓我們停用特定的驗證器。

1
2
3
4
<h:inputText id="zipcode" value="#{user.zipCode}">
    <f:validateLength maximum="5" minimum="5" />
    <f:validateLongRange minimum="90000" maximum="99999" disabled="true" />
</h:inputText>

繞過頁面內的所有驗證器

某些情況下, 我們需要繞過頁面中的驗證器, 執行其他的動作。例如, 按下頁面中的”取消”按鈕, 轉跳到另一個頁面。此取消動作並不需要驗證表單中的資料, 便可直接轉跳頁面。

Facelet Tag 中有一個 immediate 屬性, 可以讓 JSF 提前處理此標籤, 如此便可以繞過其他標籤內的驗證器了.

有以下程式碼:

1
2
3
4
5
6
<h:inputText id="zipcode" value="#{user.zipCode}">
    <f:validateLength maximum="5" minimum="5" />
    <f:validateLongRange minimum="90000" maximum="99999" disabled="true" />
</h:inputText>
<h:commandButton value="Submit" action="/processOrder">
<h:commandButton value="Cancel" action="/cancelOrder" immediate="true">

在此例中共有 3 個 Facelet Tags, 其中 h:inputText 內有 2 個驗證器。在 Cancel 按鈕中設定了 immediate 屬性。所以 JSF 引擎在處理此表單內的元件時, 會優先處理第 2 個 h:commandButton, 並執行頁面轉跳, 讓我們繞過 h:inputText 內的驗證器。

如果不使用此方法, 按下 Cancel 按鈕時, 會同時處理 h:inputText, JSF 引擎會執行驗證器。若驗證失敗, 會停留在原來的頁面, 無法轉跳到其它頁面。

顯示錯誤訊息

h:message h:messages

Tags from PrimeFaces [6]: p:message p:messages p:growl

實作練習

User Story

使用者填寫以下表單欄位,

  1. Amount 欄位, 允許範圍為 10 - 10,000. 顯示數字到小數點第 2 位。
  2. Credit Card 欄位, 最小長度為 13
  3. Expiration date (Month/Year) 欄位, 必要欄位; 輸入格式為 MM/yyyy
  4. 完成後, 按下 “Process” 按鈕, 轉跳頁面顯示先前的輸入
  5. 或者, 按下 “Cancel” 按鈕, 回到首頁

實作步驟

Step 建立負責 UI 互動的 CDI Bean. 建立套件 CDIBeans, 並在套件內建立一個 SessonScoped CDI bean. 這個 Bean 有三個 properties: Amount, Card, 及 Expiratin Date.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package CDIBeans;
  
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
import java.io.Serializable;
import java.util.Date;
  
/**
 *
 * @author hychen39@gm.cyut.edu.tw
 */
@Named(value = "processCard")
@SessionScoped
public class ProcessCard implements Serializable {
    private double amount;
    private String card;
    private Date expiredDate;
  
    public double getAmount() {
        return amount;
    }
  
    public void setAmount(double amount) {
        this.amount = amount;
    }
  
    public String getCard() {
        return card;
    }
  
    public void setCard(String card) {
        this.card = card;
    }
  
    public Date getExpiredDate() {
        return expiredDate;
    }
  
    public void setExpiredDate(Date expiredDate) {
        this.expiredDate = expiredDate;
    }
  
  
    /**
     * Creates a new instance of ProcessCard
     */
    public ProcessCard() {
    }
}

Step 建立首頁 index.xhtml。首頁內有一個連結, 轉跳到信用卡輸入頁面。

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h1>JSF Unit08 驗證表單資料</h1>
  
        <h:link outcome="/fillCreditCardForm"> 填寫信用卡 </h:link>
  
    </h:body>
</html>

Step 建立信用卡輸入頁面 fillCreditCardForm.xhtml。 注意在 h:InputText 中使用的轉換器(Converter)及驗證器(Validator)。另外, 顯示錯誤訊息時所使用的 h:message

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>Unit08: Fill form</title>
    </h:head>
    <h:body>
        <h1>填寫信用卡</h1>
        <h:form>
            <h:panelGrid columns="3">
                <h:outputText value="Amount"/>
                <h:inputText id="amount" label="Amount"
                             value="#{processCard.amount}"
                             required="true">
                    <f:convertNumber minFractionDigits="2" />
                    <f:validateDoubleRange minimum="10" maximum="10000"/>
                </h:inputText>
                <h:message for="amount" styleClass="errorMessage"/>
  
                 <h:outputText value="Card"/>
                <h:inputText id="card" label="Card"
                             value="#{processCard.card}"
                             required="true"
                             requiredMessage="長度至少 13 碼">
                    <f:validateLength minimum="13"/>
                </h:inputText>
                <h:message for="card" styleClass="errorMessage"/>
  
                <h:outputText value="Expired Date (Month/Year)"/>
                <h:inputText id="date" label="Expired Date"
                             value="#{processCard.expiredDate}"
                             required="true">
                    <f:convertDateTime pattern="MM/yyyy" />
                </h:inputText>
                <h:message for="date" styleClass="errorMessage"/>
            </h:panelGrid>
            <h:commandButton value="Process" action="/result" />
            <h:commandButton value="Cancel" action="/index" immediate="true" />
        </h:form>
    </h:body>
</html>

Step 建立顯示輸入結果的頁面 result.html 注意在 h:outputText 中使用的轉換器, 將不同的資料型態轉換成需要的字串格式。

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>Result</title>
    </h:head>
    <h:body>
        <h:panelGrid columns="2">
            Amount: <h:outputText value="#{processCard.amount}">
                <f:convertNumber minFractionDigits="2"/>
            </h:outputText>
            Card: <h:outputText value="#{processCard.card}"/>
            Expired Date: <h:outputText value="#{processCard.expiredDate}">
                <f:convertDateTime pattern="MM/yyyy"/>
            </h:outputText>
        </h:panelGrid>
    </h:body>
</html>

複習問題

  1. Converter 的作用為何? 處理日期的轉換器是哪一個? 要如何使用?
  2. Validator 驗證器的作用為何?
  3. 舉例屬於兩個標準驗證器的 Facelet, 並說明如何使用.
  4. h:messagefor 屬性有什麼用途呢?
  5. h:messageh:messages 有什麼不同?
  6. 某些 Facelet 標籤上會有 immediate 屬性, 例如 h:commandButton。此 immediate 設為 true 有什麼作用呢?

技術挑戰

請嘗試使用以下的 PrimeFaces 的 Facelets [6] 來顯示錯誤訊息: p:message p:messages p:growl

參考資料

[1] Burns, E. and Schalk, C., JavaServer Faces 2.0: the complete reference. McGraw Hill, 2010.

[2] Overview (JSF 2.2 View Declaration Language: Facelets Variant) , accessed on 2018/10/29

[3] JSF Validators, Faces Core (JSF 2.2 View Declaration Language: Facelets Variant)

[4] validateDoubleRange (JSF 2.2 View Declaration Language: Facelets Variant)

[5] inputText (JSF 2.2 View Declaration Language: Facelets Variant)

[6] Messages, PrimeFaces Showcase

Last update: 2019-12-29 11:53:26

Updated: