適用於多層 Component 架構

VeeValidate 雖然在 input event 就會執行驗證,但實務上可能會需要在 Submit 按下時,重新對所有欄位進行驗證,尤其當 component 又包含其他 component 時,該如何對所有 component 進行驗證呢 ?

Version


Vue 2.5.17
VeeValidate 2.1.2

Scenario


validate000

  1. 兩個 email 欄位,只有第一個欄位輸入正確,第二個欄位尚未輸入,因此其 input event 未被觸發,所以 VeeValidate 尚未執行驗證,按下 Submit 之後,才會對所有欄位進行驗證,並且 alert() 顯示錯誤訊息

validate001

  1. 啟動 VeeValidate 驗證後,顯示錯誤訊息,並且將 focus 顯示在第二個 email 欄位

VeeValidate


demo.vue

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<template>
<div>
<div>
<input v-model="customEmail1"
v-validate="'required|custom-email'"
name="customEmailAddress1"
type="text">

</div>
<div>
<input v-model="customEmail2"
v-validate="'required|custom-email'"
name="customEmailAddress2"
type="text">

</div>
<div>
{{ errors.first('customEmailAddress1') }}
</div>
<div>
{{ errors.first('customEmailAddress2') }}
</div>
<div>
<button @click="onSubmit">Submit</button>
</div>
</div>
</template>

<script>
import validator from '../validators/email';

/** VeeValidate */
validator();

/**--------------------------------------------------------------
| data
*--------------------------------------------------------------*/

const data = function() {
return {
customEmail1: '',
customEmail2: '',
};
};

/**--------------------------------------------------------------
| function
*--------------------------------------------------------------*/

/** 產生 showError(),目的在僅執行一次,所以使用 closure 包住 count */
const showError = function() {
if (showError.once) return;

alert(this.errors.items[0].msg);
const name = this.errors.items[0].field;
this.$root.$el.querySelector(`[name=${name}]`).focus();

showError.once = true;
};

/** 執行所有 component 的 validate() */
const validateAll = (component, showFunc) => {
const validator = component.$validator;
validator && validator.validate().then(result => result || showFunc());
component.$children.forEach(component => validateAll(component, showFunc));
};

/**--------------------------------------------------------------
| methods
*--------------------------------------------------------------*/

/** Submit Handler */
const onSubmit = function() {
validateAll(this.$root, showError.bind(this));
};

const methods = {
onSubmit,
};

export default {
name: 'demo',
data,
methods,
};
</script>

68 行

1
2
3
const onSubmit = function() {
validateAll(this.$root, showError.bind(this));
};

Submit 按下後,執行 onSubmit()

showError() 負責 alert() 顯示 第一個 錯誤訊息,為什麼要使用 bind() 並傳入 this 呢 ? 稍後會解釋。

執行 validateAll() 對所有欄位進行驗證,第一個參數傳入 root component,第二個參數 showError()

47 行

1
2
3
4
5
6
7
8
9
const showError = function() {
if (showError.once) return;

alert(this.errors.items[0].msg);
const name = this.errors.items[0].field;
this.$root.$el.querySelector(`[name=${name}]`).focus();

showError.once = true;
};

showError() 除了在顯示錯誤訊息外,並且只能執行一次。

利用 ECMAScript 可動態建立 property 特性,動態建立 once 作為 Boolean tag,true 表示已經執行過一次,false 表示從未執行。

once property 尚未建立時為 undefined,視為 Falsy Value,繼續執行,最後再將 once 設定為 true,將來不再執行。

1
alert(this.errors.items[0].msg)

VeeValidate 會將 errors object 放在 this 下,也就是 Vue Instance 下,也由於 Vue 會對 this 改變,因此特別使用 function(),將來要使用 bind() 傳入 Vue 的 instance this

所有的 VeeValidate 的錯誤訊息 object 都放在 this.errors.items array 下,其中的 msg property 就是錯誤訊息,而 itemrs[0] 第一個錯誤訊息。

1
const name = this.errors.items[0].field;

由於 VeeValidate 必須使用 HTML 的 name,因此可使用 field property 抓到欄位的 name

1
this.$root.$el.querySelector(`[name=${name}]`).focus();

利用 this.$root.$elquerySelect() 抓到該欄位,然後呼叫 focus()

58 行

1
2
3
4
5
const validateAll = (component, showFunc) => {
const validator = component.$validator;
validator && validator.validate().then(result => result || showFunc());
component.$children.forEach(component => validateAll(component, showFunc));
};

由於 component 可能包含其他 component,因此第一個參數先傳進 root component,由 root component 向下搜尋其他 component。

VeeValidate 有個特性,若該 component 使用 VeeValidate 的話,會在該 component 動態 加上 $validator property,也就是 $validator 並不是所有的 component 都有,因此要先判斷 component 是否有 $validator property。

若沒有 $validator property,ECMAScript 為 undefined,視為 Falsy Value,必須為 true 才可繼續執行,所以使用 && logic operator。

值得注意的是:validate() 回傳的是 promise,也就是 VeeValidate 是以 asynchronous 方式執行所有驗證,必須在非同步驗證完之後,才能判斷驗證是否正確。

result 為 Boolean,只有當 false 時才會顯示錯誤訊息,因此使用 || logic operator。

由於 component 還有包含其他 component,因此要使用 Recursive 方式呼叫 validateAll(),才能將所有 component 進行驗證。

Conclusion

  • 若要使 function 只執行一次,可使用 Closure 搭配 counter
  • 只有使用 VeeValidate 的 component 才會動態擁有 $validator property,因此使用 && logic operator 判斷是否有 $validator property
  • 因為只有 resultfalse 才要顯示錯誤訊息,因此使用 || logic operator 判斷 false 才執行
  • 因為 component 還會包含其他 component,因此要使用 Recursive,才能對所有 component 進行驗證

Sample Code

完整的範例可以在我的 GitHub 上找到

Reference

VeeValidate, Validation Events

2018-11-21