[Vue] 跟著 Vue 闖蕩前端世界 - 12 使用 vee-validate 進行多語系表單資料驗證

表單驗證是每個網站必備的工作項目,本文使用 vee-validate 實作表單驗證,並搭配 vue-i18n 語系檔來打造友善的多語系錯誤提示環境。

前言


前端檢核基本上套用 vee-validate 就搞定了,本文除了介紹 vee-validate 驗證機制外,還會以筆者近期的開發經驗分享如何套用檢核語系檔,並整合系統提供給 vue-i18n 語系檔至錯誤訊息中來顯示欄位名稱。

vee-validate v2.0.3

 

基本設定


開始前先了解一下我們對檢核的需求有哪些,後續會針對各項目進行說明。

  1. 觸發顯示錯誤提示的時機
  2. 多國語系 (中英欄位名稱帶入錯誤提示中)
  3. 覆寫特定檢核的錯誤提示文字
  4. 自行定義檢核邏輯

 

首先當然是安裝套件

npm install vee-validate --save

 

接著以最小配置來進行設定 (setupVeeValidate.js),可以在這個階段我們可以決定的項目如下:

  • 初始語系 (locale)
  • 觸發檢核的時機 (events)
  • 預設各語系檢核失敗提示的文字 (dictionary)
/* setupVeeValidate.js */

import Vue from 'vue'
import VeeValidate from 'vee-validate'
import localeService from 'services/localeService'

// support lang
import tw from 'vee-validate/dist/locale/zh_TW'
import en from 'vee-validate/dist/locale/en'

// global config
const config = {
  // 初始語系 (tw / en)
  locale: localeService.getCurrentLang(),
  // 觸發檢核的時機(輸入or離開輸入框)
  events: 'input|blur',
  // 預設各語系檢核失敗提示的文字 (需要對應 locale 設定)
  dictionary: { tw, en }
}

Vue.use(VeeValidate, config)

 

依照筆者的習慣會將語系相關邏輯收至 localeService 服務中,避免類似邏輯散落四處,此服務主要負責「封裝預設語系判斷邏輯」、「取得當下語系」及「切換語系」的工作,以下就參考參考即可。

/* localeService.js */

import { Validator } from 'vee-validate'
import i18n from 'setup/setupLocale'

/**
 * @description 切換網站語系
 */
const switchLang = (newLang) => {
  const isChange = getCurrentLang() !== newLang
  if (isChange && (newLang === 'en' || newLang === 'tw')) {
    i18n.locale = newLang
    Validator.localize(newLang)
    window.localStorage.setItem('locale', newLang)
  }
}

/**
 * @description 取得預設(當下)語系
 */
const getCurrentLang = () => {
  const locale = window.localStorage.getItem('locale') || 'tw'
  window.localStorage.setItem('locale', locale)
  return locale
}

export default {
  switchLang,
  getCurrentLang
}

 

 

套用檢核


這個套件本身就已經有提供一些常用檢核邏輯,像是必填資訊、數字檢查等邏輯可以直接套用,因此我們可以先套用這些預設檢核來測試一下功能是否正常運作;而套用的方式只需要在 input 元素中加入 v-validate directive 來設定所需的 validator 名稱及參數即可,以下是一個簡單的範例。

  1. 定義 name 作為檢核識別碼,以 name 作為 key 值來存放檢核的錯誤資訊。
  2. 定義 validators 來為 v-model 資料做檢核,檢核順序從左至右以 "|" 符號區隔,並可傳入參數。 

 

測試一下,當在輸入資料或移開輸入框 (input / blur) 時會觸發「必填」及「最少輸入文字長度」的檢核,而預設的錯誤訊息會將欄位 name 套入其中做顯示。

若無多國語系的需求可以簡單透過 data-vv-as 屬性定義這個欄位的顯示名稱 ( ex. data-vv-as="使用者名稱" ) ,這樣錯誤訊息中的 userId 就會被置換成 "使用者名稱",以此獲得完整的錯誤提示訊息。

 

送出資料前需檢查每個欄位是否皆通過檢核,此時可使用 this.$validator.validateAll() 方法來檢查每個欄位,只有在全部通過的時候才會將資料送出,示意代碼如下。

/* SetUserIdForm/index.vue */

export default {
  name: 'SetUserIdForm',
  methods: {
    submit: async function () {
      const isValid = await this.$validator.validateAll()
      if (isValid) {
        // call api to submit data
      }
    }
  }
}

 

 

套入欄位名稱語系


在一個具有多國語系的網站中,輸入欄位前方一定會有欄位名稱來說明此輸入框的作用,而這個欄位名稱會對應到語系檔中的 key 來做正確語系文字的呈現,因此我們的目標就是要讓 vee-validate 套上這個語系檔,當欄位名稱對應到語系檔時可以自動套入語系檔中的文字至錯誤訊息中。實作方式請參考以下資訊。

 

目前網站提供給 vue-i18n 的語系檔如下

/* tw/lang.js */
export default {
  __userId: '使用者名稱',
  __password: '固定密碼',
}
/* en/lang.js */
export default {
  __userId: 'User Name',
  __password: 'Password',
}

 

依 vee-validate 所定義的結構建立 veeValidateDic.js 檔案,將這些中英語系檔作為 attributes 資料傳入。

/* tw/veeValidateDic.js*/

import attributes from '/tw/lang'

export default {
  // 對應到輸入元素的name名稱 <input name="__userId"> or <input data-vv-name="__userId">
  // 在上方 error message 的 field 就會自動取代成"中文"語系檔中 key 為 __userId 的文字
  attributes
}
/* en/veeValidateDic.js*/

import attributes from '/en/lang'

export default {
  // 對應到輸入元素的name名稱 <input name="__userId"> or <input data-vv-name="__userId">
  // 在上方 error message 的 field 就會自動取代成"英文"語系檔中 key 為 __userId 的文字
  attributes
}

 

開啟 setupVeeValidate.js 設定檔將上述檔案引入 vee-validate 全域 config 中

 

最後只要將 name 改為語系檔中表示這個欄位名稱的 key 值即可。

 

從結果中發現 input name 設定的 __userId 值已經可以 mapping 到中文語系檔中的「使用者名稱」文字,並且順利套入到錯誤提示中了。

 

 

覆寫錯誤提示文字


當預設檢核所提供的錯誤提示訊息不符合需求時,我們可以考慮覆寫它;例如以下情境,客戶覺得應該把「不能小於」調整為「長度須超過」比較符合期待,此時可以透過以下方式進行調整。

 

開啟先前所定義的 veeValidateDic.js 設定檔,新增 messages 物件來針對特定 validator 覆寫其錯誤訊息,可以將欄位顯示名稱 (field) 及 validator 參數 (args) 套入訊息中,開發人員可以依照需求靈活調整訊息內容。

/* tw/veeValidateDic.js*/

import attributes from '/tw/lang'

export default {
  messages: {
    // 覆寫原有 validator 錯誤訊息
    min: (field, args) => `${field}長度需超過${args[0]}字元!`
  }

  // ... 略 ...
}

 

錯誤訊息就會依照設定顯示

 

必填檢核的訊息調整技巧

筆者在開發過程中發現必填 (required) 的檢核錯誤訊息並不適合全部情境,因為可能會套到 input / select / check 等型態的輸入欄位中,若在核取方塊下方顯示「請輸入 XXX」還是滿奇怪的。

本來考慮建立一個新的檢核邏輯來處理,但發現進入自定義檢核的先決條件就是必須存在資料 (有值才會進入檢核邏輯),因此繞回原點自行傳入參數來決定 required 提示訊息 (請輸入、請勾選、請選取....) 囉!

/* tw/veeValidateDic.js*/

export default {
  messages: {
    // 第二個參數 args 為驗証方法後面設定的參數, 為陣列(可用逗號區隔多筆參數)
    // 如驗證訊息需"請選擇XXXX", 可加入參數 v-validate="'required:choose'"
    // 如驗證訊息需"請選取XXXX", 可加入參數 v-validate="'required:select'"
    required: (field, args) => {
      if (args && args.length === 1) {
        switch (args[0].toString()) {
          case 'select':
            return `請選取${field}`
          case 'choose':
            return `請選擇${field}`
          case 'check':
            return `請勾選${field}`
          default:
            return `請輸入${field}`
        }
      } else {
        return `請輸入${field}`
      }
    }
  }
}

 

 

自定檢核邏輯


每個表單或多或少都有一些特殊的邏輯需要被檢核,並且有重複被使用的機會,因此筆者會將檢核類的邏輯統一都收在 validators 資料夾中,無論是否搭配 vee-validator 做欄位檢核,亦或者在程式中都可以重複取用相同的檢核邏輯;而筆者會在這個資料夾中透過 index.js 來動態 export 資料夾中的所有 validator 出來,這樣當新增新檢核邏輯時就不需要異動太多地方。

/* Dynamic Exporter:
 * Dynamically export all files (except self) in current folder
 */
const req = require.context('.', false, /\.js$/)

req.keys().forEach((key) => {
  const name = key.replace(/^\.\/(.*)\.js/, '$1')

  if (name !== 'index') {
    module.exports[name] = req(key).default
  }
})

 

載入自定檢核邏輯

再來將所有於 validator 資料夾下的檢核邏輯都透過 Validator.extend() 方法擴充到 vee-validate 中,而 validator 名字就是檔名,接著就可以專心針對每個特定的檢核來撰寫邏輯了。

 

撰寫自定檢核邏輯

首先,試想一個設定用戶名稱的情境,用戶名稱需由【英文】+【數字】組合而成,因此我們可以在 validator 資料夾中建立一個 requireAlphaNum.js 檔案來檢查此邏輯;另外,又希望不要跟特定資料 【身分證字號】相同,因此再建立一個可以接受參數的 noSameAsCustId.js 來做檢核。

/* validator/requireAlphaNum.js */

export default (value) => {
  // 需包含英數字
  return /^(?:[0-9]+[a-z]|[a-z]+[0-9])[a-z0-9]*$/i.test(value)
}
/* validator/noSameAsCustId */

export default (value, args) => {
  // 不可與身份證字號相同
  let custId = args[0]
  if (custId) {
    return value.toUpperCase().indexOf(custId.toUpperCase()) === -1
  }
  return true
}
可以接收欄位值 (value) 及傳入 validator 的參數 (args) 作為檢核判斷依據

 

設定錯誤提示文字

訂出新的 validator 當然也要設定對應的錯誤提示文字,而這部分如同覆寫預設檢核邏輯錯誤提示文字,只要在先前所定義的 veeValidateDic.js 設定檔中,於 messages 物件中新增自訂 validator 錯誤訊息格式即可。

/* tw/veeValidateDic.js*/

export default {
  messages: {
    // ... 略 ...
    requireAlphaNum: (field) => `${field}需包含英文與數字`,
    noSameAsCustId: (field) => `${field}不可包含身分證號`
  },
  // ... 略 ...
}

 

套上自定檢核邏輯

最後將新增的兩個自訂 validator requireAlphaNum 及 noSameAsCustId 加入 v-validate 屬性中即可,注意noSameAsCustId 需要傳入 custId 參數作為比較是否與身分證號相同之用。

 

測試一下這兩個檢核邏輯無誤就可以收工了

 

 

例外的檢核失敗訊息


原則上相同 validator 只會存在一種檢核失敗提示文字,但有時候相同的檢核邏輯套用在不同輸入框會需要呈現不一樣的提示訊息,這種情境筆者稱之例外的檢核失敗提示訊息;在 vee-validator 中提供一個方式可以針對特定 input name 指定特定 validator 來顯示特定 message 文字。

 

可於 veeValidateDic.js 設定檔中,新增 custom 物件來針對特定 validator 調整錯誤訊息格式。

  1. 檢核欄位所設定的名稱 (input name)
  2. 該欄位所需調整的 validator 名稱及調整後的檢核失敗提示文字

 

在符合上述條件 input name 為 __userId 時,當 required 檢核失敗就會依照 custom 設定做顯示;其他不符合條件的檢核,就會依照預設或通用性 message 設定來做顯示。

 

 

合併欄位檢核


有時候資料會分散在數個輸入欄位中 (ex. 信用卡有效年月),可能年份是一個輸入框,月份又是一個輸入框,最終須依據年份與月份檢核是否已過期,因此針對這類檢核的方式如下。

  1. 檢核月份 month 
  2. 檢核年分 year 
  3. 檢核年分加月份,通常會定義一個 computed property 來合併資料,如本例 fullCardValidDate 資料,接著在 v-validate 中設定 fullCardValidDate 來針對其內容值做 creditCardValidDate 自訂邏輯檢核

由於 v-validate 會自動查看是否有設定 v-modal 資訊並對該值做檢核,因此不必設定為 <input v-modal="boo" v-validate:boo="`required`" /> 形式;但當檢核目標為特定 computed property 時,就必須明確指定需檢核哪一個計算屬性。

 

 

參考代碼


以下為各設定檔的示意範例,有興趣的朋友可以試著玩看看就會有比較深刻的印象。

 

初始套件設定檔

/* setupVeeValidate.js */

import Vue from 'vue'
import VeeValidate, {Validator} from 'vee-validate'
import localeService from 'services/localeService'

// support lang
// https://github.com/baianat/vee-validate/tree/master/locale
import tw from 'vee-validate/dist/locale/zh_TW'
import en from 'vee-validate/dist/locale/en'

// 自定義的語系文字設定檔
import twVeeValidateDic from '../i18n/tw/veeValidateDic' 
import enVeeValidateDic from '../i18n/en/veeValidateDic' 

// 擴充自定義 validator 至 vee-validate 中
import validators from '../utils/validators'
import each from 'lodash/each'
each(validators, (validator, key) => {
  Validator.extend(key, validator)
})

// global config
const config = {
  // 初始語系 (tw / en)
  locale: localeService.getCurrentLang(),
  // 觸發檢核的時機 (輸入or離開輸入框)
  events: 'input|blur',
  // 預設各語系檢核失敗提示文字 (需要對應 locale 設定)
  dictionary: { tw, en }
}

Vue.use(VeeValidate, config)

// 將自定義的語系文字設定檔 合併至 預設檢核邏輯的語系文字
Validator.localize({tw: twVeeValidateDic, en: enVeeValidateDic})

 

檢核文字設定檔

/* tw/veeValidateDic.js */

import attributes from './lang'

export default {
  custom: {
    // 當有相同 validator 要呈現不同文字時,可以在這邊做例外的設定
    __userId: {
      required: field => `務必填寫${field}(此資訊將作為登入帳號使用)`
    }
  },
  messages: {
    // 覆寫原有 validator 錯誤訊息
    required: (field) => `請輸入${field}`,
    min: (field, args) => `${field}長度需超過${args[0]}字元!`,
    // 設定自訂 validator 錯誤訊息
    requireAlphaNum: (field) => `${field}需包含英文與數字`,
    noSameAsCustId: (field) => `${field}不可包含身分證號`
  },
  // 對應到輸入元素的name名稱 <input name="__userId"> or <input data-vv-name="__userId">
  // 在上方 error message 的 field 就會自動取代成"中文"語系檔中 key 為 __userId 的文字
  attributes
}

 

 

參考資訊


VeeValidate 官方網站

Custom Validator - Not Running on Blank Value

 

 


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !