Unit 14 - 套用 Boostrap 的 Server-Side validation CSS class 至表單欄位
Unit 14
技術現況與問題
User Stroy
進入表單畫面在未提交表單前, 具有資料檢查的輸入欄位沒有任何的顏色或訊息顯示驗證結果。使用者提交表單後, 驗證通過的欄位以 Bootstrap 的 is-valid
CSS 樣式顯示, 驗證失敗的以 is-invalid
CSS 樣式顯示。
技術現況
Bootstrp 提供兩個 Server-Side validation 的樣式: is-valid
及 is-invalid
。在 UIInput
component 的 styleClass
套用這些樣式。
在 JSF 中可透過 EL 取得 UIInput Component 的驗證狀態。UIInput.isValid():Boolean
傳回元件的驗證結果。Fales 表示驗證失敗, True 表示成功或尚未進行驗證。
問題
UIInput.isValid():Boolean
的預設值為 True, 有可能表單尚未提交進行驗證。直接使用此預設值來設定 UIInput
的樣式會造成第一次進行表單時(尚未提交進行驗證前), 輸入欄位會套用 is-valid
class, 向使用者表示這些欄位已經驗證通過。前述行為不符合使用者的操作經驗。
解決方式
- 在 JSF CDI Bean 中新增一個 flag 欄位, 記錄表單是否曾經被提交, 預設值為 False。
- 在 JSF 的 Post Validate Event 中更新 flag 欄位
- 在
UIInput
的styleClass
屬性, 同時使用 flag 欄位及元件的驗證狀態判斷是否要套用is-valid
或者is-invalid
CSS 樣式
flag 欄位 | UIInput.isValid() |
運算結果為 | 套用樣式 |
---|---|---|---|
False | True | False | 不套用 |
True | True | True | is-valid |
False | False | 不會發生此種情況 | |
True | False | True | is-invalid |
前述套用況中, flag 欄位為 False 時, UIInput.isValid()
的值不可能為 False。因為其預設值為 True, 只有提交表單後 UIInput.isValid()
的值才有可能變成 False。
實作
JSF 頁面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<h:body>
<f:event listener="#{bookEntryBean.processEvent}" type="postValidate" />
<div class="container">
<h:form>
<div class="form-group">
<h:outputLabel for="isbn">ISBN</h:outputLabel>
<h:inputText id="isbn"
ps:placeholder="長度 13 碼"
validatorMessage="長度不足 13 碼"
value="#{bookEntryBean.book.isbn}"
binding="#{isbnComp}"
styleClass="form-control
#{(isbnComp.valid and bookEntryBean.isFormSubmitted) ? 'is-valid' : ''}
#{!isbnComp.valid? 'is-invalid': ''}">
<f:validateLength minimum="13" maximum="13" />
</h:inputText>
<h:message for="isbn" errorClass="errorMsg"/>
</div>
</h:form>
</div>
</h:body>
<h:inputText>
的 binding
屬性
將 <h:inputText>
對應到的 UIInput
component 暴露到 EL Scope, 以便使用 EL 語法存取此元件。在 EL Scope 中, 元件的名稱為 isbnComp
<h:inputText>
的 styleClass
屬性
使用 EL 語法依照 “是否已提交表單” 及 “元件是否驗證通過” 套用 in-valid
或者 is-invalid
CSS 樣式。 bookEntryBean.isFormSubmitted
為 JSF CDI Bean 的特性, 記錄是否已提交表單。 isbnComp.valid
為 UIInput
元件的特性, 相當於呼叫 isbnComp.isValid()
。
JSF CDI Bean
JSF CDI Bean BookEntryBean
實作 SystemEventListen
介面的 processEvent()
。若發生 PostValidateEvent
則標記表單已曾提交:
1
2
3
4
5
6
@Override
public void processEvent(SystemEvent se) throws AbortProcessingException {
if (se instanceof PostValidateEvent){
this.isFormSubmitted = true;
}
}
完整程式碼如下:
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
55
56
57
58
59
60
package net.hychen39.jsfCDIBeans;
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PostValidateEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import net.hychen39.entities.Book;
/**
*
* @author hychen39@gm.cyut.edu.tw
*/
@Named(value = "bookEntryBean")
@SessionScoped
public class BookEntryBean implements Serializable, SystemEventListener {
private Book book;
private boolean isFormSubmitted;
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
public boolean isIsFormSubmitted() {
return isFormSubmitted;
}
public void setIsFormSubmitted(boolean isFormSubmitted) {
this.isFormSubmitted = isFormSubmitted;
}
public BookEntryBean() {
}
@PostConstruct
public void init(){
book = new Book();
this.isFormSubmitted = false;
}
@Override
public void processEvent(SystemEvent se) throws AbortProcessingException {
if (se instanceof PostValidateEvent){
this.isFormSubmitted = true;
}
}
@Override
public boolean isListenerForSource(Object o) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}