注册全局指令(表单验证)

1). 导出验证指令对象

在 src/directives 下新建 validator.js 文件,复制贴入以下代码:

src/directives/validator.js

  1 function validate(el, modifiers, bindingValue) {
  2   bindingValue = bindingValue && typeof bindingValue === 'object' ? bindingValue : {}
  3   const value = typeof el.value === 'string' ? el.value.trim() : ''
  4   const { title = '该项', error } = bindingValue
  5   let defaultError = ''
  6 
  7   if (modifiers.required && value === '') {
  8     defaultError = `${title}不能为空`
  9   } else if (bindingValue.target) {
 10     const target = document.querySelector(bindingValue.target)
 11     const targetValue = target ? target.value : null
 12 
 13     if (targetValue !== value) {
 14       defaultError = `输入的${title}不匹配`
 15     }
 16   } else if (bindingValue.regex) {
 17     try {
 18       if (!bindingValue.regex.test(value)) {
 19         defaultError = `${title}格式不正确`
 20       }
 21     } catch (e) {}
 22   }
 23 
 24   if (defaultError) {
 25     if (error === undefined) {
 26       showError(el, defaultError)
 27     } else {
 28       showError(el, error)
 29     }
 30   } else {
 31     showError(el)
 32   }
 33 }
 34 
 35 function showError(el, error) {
 36   const parentElement = el.parentElement
 37   const errorElement = getErrorElement(el)
 38 
 39   if (error === undefined) {
 40     errorElement.style.display = 'none'
 41     parentElement.classList.remove('has-error')
 42   } else {
 43     errorElement.textContent = error
 44     errorElement.style.display = 'block'
 45     parentElement.classList.add('has-error')
 46   }
 47 }
 48 
 49 function getErrorElement(el) {
 50   const parentElement = el.parentElement
 51   let errorElement = parentElement.querySelector('.help-block')
 52 
 53   if (!errorElement) {
 54     const tpl = `<span class="help-block"></span>`
 55     const fragment = document.createRange().createContextualFragment(tpl)
 56 
 57     parentElement.appendChild(fragment)
 58     errorElement = parentElement.querySelector('.help-block')
 59   }
 60 
 61   return errorElement
 62 }
 63 export default {
 64   bind(el, binding, vnode) {
 65     // 使用解构赋值声明 value = binding.value,  arg = binding.arg,  modifiers = binding.modifiers 
 66     const { value, arg, modifiers } = binding
 67     // 如果没传对应的事件名称参数,就默认使用 change 事件
 68     const eventType = ['change', 'blur', 'input'].indexOf(arg) !== -1 ? arg : 'change'
 69     // 默认处理器,当用户开始输入时,移除错误提示
 70     const defaultHandler = () => {
 71       showError(el)
 72     }
 73     // 验证处理器,当用户触发对应的事件时,验证用户输入的信息
 74     const handler = () => {
 75       validate(el, modifiers, value)
 76     }
 77 
 78     // 在 el 元素上的添加 input 事件监听
 79     el.addEventListener('input', defaultHandler, false)
 80     // 在 el 元素上的添加用户指定的事件监听
 81     el.addEventListener(eventType, handler, false)
 82 
 83     // 移除 el 元素上事件监听和数据绑定的方法
 84     el.destroy = () => {
 85       el.removeEventListener('input', defaultHandler, false)
 86       el.removeEventListener(eventType, handler, false)
 87       el.destroy = null
 88     }
 89   },
 90   inserted(el, binding, vnode) {//被绑定插入父节点的时候调用,保证了父节点的存在
 91     const { value, modifiers } = binding
 92     // 指定当前一系列验证项的父级,我们这里指定为含 data-validator-form 的元素
 93     const form = el.closest('[data-validator-form]')
 94     // 指定一个按钮来检查所有验证项,我们这里指定为含 type=submit 的元素
 95     const submitBtn = form ? form.querySelector('[type=submit]') : null
 96 
 97     if (submitBtn) {
 98       // 提交处理器
 99       const submitHandler = () => {
100         // 验证所有项
101         validate(el, modifiers, value)
102 
103         // 获取错误信息
104         const errors = form.querySelectorAll('.has-error')
105 
106         if (!errors.length) {
107           // 没有错误信息时,在按钮上添加一个 canSubmit 属性,并指定为 true
108           submitBtn.canSubmit = true
109         } else {
110           // 有错误信息时,在按钮上添加一个 canSubmit 属性,并指定为 false
111           submitBtn.canSubmit = false
112         }
113       }
114 
115       // 在按钮上的添加 click 事件监听
116       submitBtn.addEventListener('click', submitHandler, false)
117 
118       // 移除按钮上事件监听和数据绑定的方法
119       el.destroySubmitBtn = () => {
120         submitBtn.removeEventListener('click', submitHandler, false)
121         el.destroySubmitBtn = null
122       }
123     }
124   },
125   unbind(el) {
126     // 移除事件监听和数据绑定
127     el.destroy()
128     if (el.destroySubmitBtn) el.destroySubmitBtn()
129   }
130 }

2). 注册全局验证指令

在 src/directives 下新建 index.js 文件,复制贴入以下代码:

src/directives/index.js

import Vue from 'vue'
import validator from './validator'

Vue.directive('validator', validator)

注册全局指令需要使用 Vue.directive,第一个参数 'validator' 是指令名称,第二个参数 validator 是指令对象或者指令函数,我们这里是指令对象。全局注册的好处是,可以在实例内部的所有组件中使用,而不用在每个组件内部单独引用和注册。

 

3). 引入全局验证指令

打开 src/main.js 文件,引入 ./directives

src/main.js

1 import Vue from 'vue'
2 import App from './App'
3 import router from './router'
4 import './directives'

4). 使用表单验证指令

打开 src/views/auth/Register.vue,查找 <div class="panel-body"> 元素,复制以下代码将其替换:

src/views/auth/Register.vue

 1 <div class="panel-body" data-validator-form>
 2   <div class="form-group">
 3     <label class="control-label">用户名</label>
 4     <input v-validator:input.required="{ regex: /^[a-zA-Z]+w*s?w*$/, error: '用户名要求以字母开头的单词字符' }" type="text" class="form-control" placeholder="请填写用户名">
 5   </div>
 6   <div class="form-group">
 7     <label class="control-label">密码</label>
 8     <input >
 9   </div>
10   <div class="form-group">
11     <label class="control-label">确认密码</label>
12     <input v-validator.required="{ target: '#password' }" type="password" class="form-control" placeholder="请填写确认密码">
13   </div>
14   <div class="form-group">
15     <label class="control-label">图片验证码</label>
16     <input v-validator.required="{ title: '图片验证码' }" type="text" class="form-control" placeholder="请填写验证码">
17   </div>
18   <div class="thumbnail" title="点击图片重新获取验证码">
19     <div class="captcha"></div>
20   </div>
21   <button type="submit" class="btn btn-lg btn-success btn-block">
22     <i class="fa fa-btn fa-sign-in"></i> 注册
23   </button>
24 </div>

我们先在外层元素上,添加 data-validator-form 属性,使其成为所有验证项的父级:

1 <div class="panel-body" data-validator-form>

自定义指令知识:

  • binding.name:指令名,不包括 v- 前缀,这里是 'validator' ;
  • binding.value:指令的绑定值,这里是 { regex: /^[a-zA-Z]+w*s?w*$/, error: '用户名要求以字母开头的单词字符' } ;
  • binding.arg:传给指令的参数,这里是 'input' ;
  • binding.modifiers:一个包含修饰符的对象,这里是 { required: true } ;