安装与调用
引用下面CSS:
<link rel="stylesheet" href="https://unpkg.com/lu2/theme/edge/css/common/ui/Tips.css">
JS为:
<script type="module" src="https://unpkg.com/lu2/theme/edge/js/common/ui/Validate.js"></script>
或者import
导入:
<script> import Validate from 'https://unpkg.com/lu2/theme/edge/js/common/ui/Validate.js'; </script>
如果是npm安装调用,则:
import 'lu2/theme/edge/css/common/ui/Validate.css';
'Validate from'并非必须
import Validate from 'lu2/theme/edge/js/common/ui/Validate.js';
如果 JS import语法报错,试试在业务代码中动态引入。
import('lu2/theme/edge/js/common/ui/Validate.js');
基础功能演示
基于HTML5规范的表单验证交互组件。依赖ErrorTip组件。
- 内置type类型包括:
email
,tel
,url
,zipcode
,number
,date
,hour
,minute
,time
,date-range
以及month-range
。在高版本IE浏览器的向下兼容模式下,会自动将一些type直接text化,这在原生浏览器下是没有问题的。真实用户场景不会存在此问题,如果大家实在拗不过测试同学,可以在type
类型后面加个空格,例如type='email '
. 这些类型输入框默认内置正则表达式。 - 自定义正则表达式,使用原生HTML5属性
pattern
. 例如:pattern="^\d{16}$"
. - 范围超出,如数值输入,range数值范围选择,以及日期和时间的起止限制,使用原生
min
,max
,step
属性。 - 内容长度限制,使用原生
maxlength
, 以及minlength
。本组件会自动选择超出部分的文字。 - 内置计数和计数验证功能,只要使用UI组件通用的HTML和类名结构(可参考文本框和文本域静态UI组件相关文档)。中英文计数规则使用
lang
属性控制。没有lang
属性,表示中英文1:1
;lang="en"
表示后面的数值是字符数,1个汉字等于2个英文字符,lang="zh"
表示后面的数值是汉字数,2个字母等于1个汉字。 - 支持指定提示信息和自定义验证方法,具体参见这里。
测试兼演示
下面表单采用组件默认验证交互,即点击“提交”按钮后开启即时验证,所有有误内容标红,但是提示tips只会出现在第1个或当前active控件上。
// 表单验证 var elePwd = document.querySelector('#inputPwd'); var elePwdAgain = document.querySelector('#inputPwdAgain'); // 文件上传按钮元素 var eleBtnFile = document.querySelector('label[for="file"]'); // 隐藏input框 var eleHiddenFile = document.querySelector('#inputImgUrl'); // 如果选择文件 file.addEventListener('change', function () { eleHiddenFile.value = 'xxx.png'; // 触发change事件执行 eleHiddenFile.dispatchEvent(new CustomEvent('change')); }); // 验证绑定 new Validate('#validateForm', function () { new Dialog().alert('全部验证通过'); }, { // 自定义验证提示和方法 validate: [{ id: 'inputBank', report: { // 改变内置的提示信息为自定义 patternMismatch: '银行账户只能是16位数值' } }, { id: 'inputPwd', method: function (ele) { var valueAgain = elePwdAgain.value; if (valueAgain && ele.value != valueAgain) { return '前后密码不一致'; } // 再次输入密码样式变化,后一个参数必需 this.styleError(elePwdAgain, !!elePwdAgain.value); } }, { id: 'inputPwdAgain', method: function (ele) { if (ele.value != elePwd.value) { return '前后密码不一致'; } // 输入密码样式变化,后一个参数必需 // 否则会和上面验证来回执行导致死循环 this.styleError(elePwd, !!elePwd.value); } }, { id: 'inputImgUrl', method: function (ele) { // ele指存放身份证图片地址的hidden类型的input框 // 隐藏元素无法定位,转移提示元素给按钮 ele.dataTarget = eleBtnFile; if (ele.value == '') { return '尚未上传证件照'; } } }] });
证件照验证相关HTML代码:
<label for="file" class="ui-button">上传照片</label>
<input type="hidden" id="inputImgUrl">
<!-- 上传文件的表单 -->
<form><input type="file" id="file" accept="image/*"></form>
注意,上面hidden
类型的<input>
元素自定义的method
方法实现功能是为空验证,该功能也可以通过添加required
属性实现,如下:
<input type="hidden" id="inputImgUrl" required>
然后使用report
参数改变提示内容。例如:
// required设置时候的处理 { id: 'inputImgUrl', report: { // 指定值缺失时候的提示文字 valueMissing: '尚未上传头像' } }
语法和参数
语法
new Validate(element, callback, options);
参数
其中:
- element
- 必须。Element|String。可以是DOM元素,也可以是DOM元素的选择器。通常指
<form>
元素。 - callback
- 可选。Function。全部验证通过之后的回调方法。其中上下文
this
指当前实例对象,支持一个参数,表示element
参数对应的元素。 - options
- 可选。Object。可选参数,具体见下表:
参数名称 | 支持类型 | 默认值 | 释义 |
---|---|---|---|
validate | Array Object |
[] | 自定义提示和验证方法。为对象数组集。如[{}, {}] 。如果仅有一个数组项,也可以直接使用该数组项作为参数。
本参数要点较多,本文档有专门深入介绍,详见这里。 |
multiple | Boolean | true | 提交时候是全部错误高亮,还是仅仅第一个。 |
immediate | Boolean | true | 是否在表单提交后开启即时验证。 |
label | Boolean | true | 是否使用关联<label> 元素中裸露的文字作为提示关键字,会自动过滤冒号,此参数非常实用。 |
事件
参与表单验证的所有控件元素均支持原生的valid
和invalid
事件,分别在验证通过和验证不通过的时候触发(本文档页面对底部案例有使用示意)。
另外,当所有表单控件元素都验证通过的时候,会触发表单元素自定义的valid
事件,例如:
form.addEventListener('valid', function () {
// 表单验证通过
});
validate可选参数深入
validate参数实际使用多这样:
new Validate('formId', { validate: [{ id: 'inputId', report: { /* 设置错误类型对应提示 */ }, method: function () { /* 额外自定义验证方法,返回customError提示文字 */ } }] });
如果validate参数值的数组项只有一个,可以直接使用该数组项作为参数,示意:
new Validate('formId', { validate: { id: 'inputId', report: {}, method: function () {} } });
数组对象深入
数组项为纯对象,支持下面几个参数:
- id
- 必需。String。目标验证输入框或选择框元素的
id
属性值。 - report
- 可选。Object。自定义出错的提示文字。支持以下提示字段:
-
{ badInput: '提示文字...' customError: '...' patternMismatch: '...' rangeOverflow: '...' rangeUnderflow: '...' stepMismatch: '...' tooLong: '...' tooShort: '...' typeMismatch: '...' valueMissing: '...' }
例如
valueMissing
指值缺失。所有这些提示字段源自浏览器原生验证的ValidityState对象。每个字段对应的含义可以参见这篇文章:https://www.zhangxinxu.com/wordpress/?p=8895所有提示字段值还支持Function类型,用在需要动态返回提示内容的场景。
- method
- 可选。Function。自定义验证方法。
其中上下文this指当前实例对象,支持一个参数,为当前验证元素。
返回错误提示内容字符串,或
{ customError: '...' }
对象。其他返回值(包括
undefined
)都认为是验证通过。
自定义提示案例
下面有一个普普通通的输入框和一个普普通通的按钮:
<input class="ui-input" required> <button class="ui-button" data-type="primary">点我</button>
点击按钮,会触发验证提示,这里直接使用原生语法示意,如下:
button.addEventListener('click', function () { input.reportValidity(); });
如果输入框里面没有值,此时出现的提示文案是内置的“请填写此字段”。
如果我们想要改变出现的提示文案,可以有下面两种方法:
- 借助Validate实例方法,我们可以随便找个输入框的祖先元素当作表单元素(实际开发外面需要有
<form>
元素,这里示意省略了)。new Validate(input.parentElement, { validate: { id: 'input', report: { valueMissing: '主人,这里还没有输入内容,喵~' } } }); button.addEventListener('click', function () { input.reportValidity(); });
效果如下:
- 使用原生的API语法
Element.setCustomValidity()
,如下:input.setCustomValidity({ valueMissing: '主人,请输入内容,喵呜~' }); button.addEventListener('click', function () { input.reportValidity(); });
效果如下:
然而,实际开发,其实使用更多的是借助<label>
元素重置宽泛的提示。详见这里。
自定义验证方法案例
演示method
参数以及隐藏元素的提示功能实现,此参数的基本用法其实在一开始的“测试与演示”案例那里有演示,这里再细化下。
首先演示下基本功能:
下面有一个普普通通的输入框和一个普普通通的按钮:
<input class="ui-input"> <button class="ui-button" data-type="primary">点我</button>
此时,我们观察
<input>
输入框对应的HTML代码,可以看到没有设置required
属性,默认状态下,认为是合法的。如果我们希望此时内容为空也提示错误,而HTML又没法设置,怎么办呢?可以使用method
参数,JS代码如下:new Validate(input.parentElement, { validate: { id: 'input', method: function (element) { if (element.value.trim() == '') { return '这个是必填的哟~'; } } } }); button.addEventListener('click', function () { input.reportValidity(); });
在本例中,为了演示,所以外面没有
<form>
元素,在这种场景下,想要自定义验证方法,还有更简单的实现。所有的自定义提示和方法都是使用Element.customValidity
实现的,因此,上面代码我们还可以简化成这样:input.customValidity = { method: function (element) { if (element.value.trim() == '') { return '这个是必填的哟~'; } } }; button.addEventListener('click', function () { input.reportValidity(); });
- 然后演示下隐藏元素的提示,有些表单控件是隐藏在页面中的,例如
'hidden'
类型输入框,或者一些很丑的拿不出台面的元素。Validate组件对LuLu UI内置的表单元素已经做了提示元素转移,但是并不能覆盖所有的场景,此时,需要开发人员针对具体的场景进行调整。有两种做法,一是通过
data-target
属性关联,还有一种是在DOM对象上使用dataTarget属性关联。例如一个更换头像功能,点击按钮图像上会出现提示:
HTML代码如下:
<img id="img" src="default.png" alt="默认头像"> <input id="input" type="hidden" data-target="img" required> <button id="button" class="ui-button" data-type="primary">确认更换</button>
使用了data-target将提示定位元素从隐藏的输入框转移为<img>图像。此时就可以安心执行
reportValidity()
方法了。input.setCustomValidity({ valueMissing: '尚未上传更换的头像' }); button.addEventListener('click', function () { input.reportValidity(); });
如果HTML设置不方便,也可以使用DOM对象进行关联:
<img id="img" src="default.png" alt="默认头像"> <input id="input" type="hidden" required>
input.dataTarget = img;
// 其余代码同上...
扩展调用方法
为了更好地适应多种使用场景,本组件还支持下面两种调用方法:
- form.validate()语法;
- is-validate自定义属性;
form.validate()语法
form.validate()
语法仅支持<form>
元素,使用示意:
<form id="f0"> <input type="search" class="ui-input" required> <button type="submit" class="ui-button" data-type="primary">搜索</button> </form>
f0.validate(); f0.addEventListener('valid', function () { new Dialog().alert('验证通过'); });
is-validate自定义属性
给任意的元素设置is-validate
属性,就会自动进行表单验证初始化的设置。
例如:
<form id="f1" is-validate> <input type="search" class="ui-input" required> <button type="submit" class="ui-button" data-type="primary">搜索</button> </form>
f1.addEventListener('valid', function () { new Dialog().alert('同样验证通过'); });
当使用is-validate
属性语法的时候,支持connected
和DOMContentLoaded
生命周期事件。
document.validate对象
document.validate
对象和Validate组件的核心,或者说Validate组件是基于document.validate
对象封装而来。包括对DOM元素重置的
表单验证浏览器内置的3个方法checkValidity()
,reportValidity()
,setCustomValidity()
和1个属性validity
的重置也是基于document.validate
对象实现。
因此,了解document.validate
对象有助于实现更定制化的需求。
属性
- reg, name
reg
的值是内置表单元素类型对应的正则表达式对象合集。默认值是(点击展开):name
值是内置表单元素类型对应的中文名称对象合集。默认值是(点击展开):-
我们可以使用这两个属性扩展自定义的验证类型,例如,扩展一个身份证输入框。
document.validate.reg.idcard = '^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|[Xx])$'; document.validate.name.idcard = '身份证';
此时,我们就获得了
type="idcard"
类型的输入框。点击下面的“验证”按钮,看看提示信息是不是包含了“身份证”关键字:代码如下:
<input id="input" type="idcard" class="ui-input" required> <button id="button" class="ui-button" data-type="primary">验证</button>
- focusable
- 出错提示出现的时候,输入框的值是否框选。
true
表示框选,0
和false
表示忽略。此属性有成熟的内置逻辑,不建议自己尝试使用。
方法
- selectRange(element, start, end)
- 设置element从start到end位置文字选中。
- getTel(tel)
- 返回准确的连续11位号码,过滤短横线或者+86这些不需要的字符。
- getType(element)
- 返回元素准确的类型。
- getLength
- 返回元素准确的字符长度。默认1个字符算1个长度,如果输入框设置
lang="zh"
属性,则1个汉字算1个长度,2个字母等于1个汉字;如果输入框设置lang="en"
属性,则1个英文字符算1个长度,1个汉字等于2个英文字符。正因为有这样的规则,所以才有此方法。
- getReportText(element)
- 返回元素对应的错误提示信息,如果元素验证通过,没有错误,则返回值是空字符串。
- getTarget(element)
- 返回准确的ErrorTip提示的目标元素。
- getMissingState(element)
- 返回元素值是否缺失的验证状态。返回值:
{ // 如果值缺失,下面的false应该是true valueMissing: false }
- getMismatchState(element, regex, params)
- 返回元素的值和
type
类型对应正则,或者和pattern
正则的验证状态。没有错误的返回值是:{ patternMismatch: false, typeMismatch: false }
regex
和params
两个参数表示指定正则表达式,和i
,g
这类正则参数。本组件并未用到这两个参数。- getRangeState(element)
- 返回元素的值是否超出
max
,min
以及step
属性限定的范围。验证无误的返回值是:{ badInput: false, rangeOverflow: false, rangeUnderflow: false, stepMismatch: false }
- getLengthState(element)
- 返回元素的字符数量是否超出
maxlength
和minlength
的范围限制。验证无误的返回值是:{ tooLong: false, tooShort: false }
- getValidity(element)
- 返回元素完整的验证状态结果。就是上面4个方法返回值的合集。另外,会多一个
valid
属性,如果为true
,表示验证通过,如果为false
,则表示验证不通过。规则和规范标准一致。 - isMissing(element)
- 返回布尔值。是否值缺失。
- isMismatch(element)
- 返回布尔值。是否值不匹配。
- isOut(element)
- 返回布尔值。是否值范围超出。
- isOverflow(element)
- 返回布尔值。是否值的字符长度范围超出。
- checkValidity(element)
- 返回布尔值。是否值验证不通过。可以看成是上面4个验证方法的集合。
- setCustomValidity(element, content)
- 设置自定义错误的提示文字。也就是customError错误类型的提示内容。其中,
content
必需。 - reportValidity(element, content)
- 在准确的位置显示出错提示,并红色高亮元素。其中,
content
参数可选。本组件内部并未用到该参数。本方法返回布尔值,表示验证是否成功的结果。 - styleError(element, valid)
- 根据元素的验证状态,显示或关闭出错样式。其中
valid
是可选参数,布尔值。如果指定valid
的值,则不会再次执行元素的合法性判断,直接使用这个参数值进行样式控制。 - errorTip(element, content)
- 在准确的位置显示出错提示。
原生属性和方法
本组件对原生的表单验证方法进行了重置,以便更好地和LuLu UI体系结合。分别如下:
属性
- Element.validity
- 返回当前表单控件元素的验证状态对象。例如:
badInput: false customError: false patternMismatch: false rangeOverflow: false rangeUnderflow: false stepMismatch: false tooLong: false tooShort: false typeMismatch: false valueMissing: false // 验证通过与否 valid: true
- Element.validationMessage
- 返回当前表单控件元素的验证提示信息,如果是空字符串,则表示验证无误
- Element.willValidate
- 当前表单控件元素是否可以参与验证,此属性本组件并未重定义,完全原生,因此,返回值和本组件行为可能会有不一致的地方。例如hidden类型的输入框返回值是false,但是本组件对于hidden类型的输入框也进行了验证。
方法
- Element.checkValidity()
- 返回当前表单控件元素或者表单元素的验证结果。返回值是布尔值。
true
表示验证通过。 - Element.reportValidity(content)
- 显示当前表单控件元素的错误提示,如果没有错误,则不出现提示。会使用LuLu UI内置的UI。其中,
content
参数可选。 - Element.setCustomValidity(content)
- 设置当前表单控件元素自定义的提示内容。其中,
content
参数必需。可以是字符串,表示所有的错误类型都使用这个提示内容;也可以是纯对象,指定具体错误类型的提示内容。例如:input.setCustomValidity({ valueMissing: '主人,请输入内容,喵呜~' });
原生属性和方法虽然使用方便,但是实际开发往往需要即时验证,实时更新输入框的验证状态。此时,还是需要用到Validate实例方法。也就是下面要介绍内容。
实例对象属性和方法
实例对象下面两种语法都能获取,如下:
var myValidate = new Validate(eleForm);
或者是:
new Validate(eleForm); var myValidate = eleForm.data.validate;
myValidate暴露了如下属性和方法:
{ // 元素 element: { form: null }, // 自定义提示和方法的数据 data: [], // 是否阻止表单验证, // 如果为true,表单提交行为不会被阻止 stopValidate: false, // 一些布尔标志量 params: { // 是否一次验证多个 multiple: true, // 是否即时验证 immediate: true, // 是否提示关键字取自label标签 label: true }, // 即时验证细节处理 immediate: function () {}, // 返回布尔值,当前表单元素是否验证通过 // 此方法内部有出错提示显示的逻辑处理 checkValidity: function () {}, // 显示出错提示,同Element.reportValidity(content) reportValidity: function (element, content) {} }
另外,eleForm元素上有一个名为isImmediated
的标志量,表示表单元素是否已经开启了即时校验验证。
更多应用案例
活用label参数优化提示
借助<label>
元素可以让我们的提示信息更加友好。
例如,点击下面的提交按钮预览效果:
HTML如下:
<form id="commentForm"> <label for="comment"><span>请填写</span>评语:</label>(不超过50字,已输入<label for="comment" class="ui-textarea-count">0</label>字) <div class="ui-textarea" style="max-width:300px;"> <textarea id="comment" maxlength="50" rows="3" required></textarea> </div> <button type="submit" class="ui-button" disabled data-type="primary">提交</button> </form>
JS就是简单初始化:
// label提示和计数高级用法示意
new Validate('#commentForm', function () {
new Dialog().alert('评论成功', {
type: 'success'
});
});
关于提示优化
在这个例子中,值缺失的提示不是“请填写此字段”,而是“评论不能为空”,显然更友好,那这个友好的提示是从哪里来的呢,JS代码中并没有看到端倪。
实际上是在HTML中设置的,先看关键代码部分:
<label for="comment"><span>请填写</span>评语:</label>
Validate组件会通过与表单控件元素关联的<label>
元素寻找提示关键字。关键字提取规则有4个小点:
- 只选取裸露的文字。例如虽然上面
<label>
元素完整文本是“请填写此字段”,但是由于“请填写”外面有<span>
标签,于是被忽略(请填写外面的span是故意添加的); - 会过滤冒号和数字,于是提示关键字不是“评语:”,而是“评语”;
- 多个关联
<label>
元素取长度最长的文本作为关键字,因此后面计数的<label>
元素会被忽略; - 提示关键字小于2个字符被忽略。
根据近20多个大小项目实践,上面规则没有出现提示异常。
关于计数
Validate组件默认计数交互是配合.ui-input-x > input.ui-input
和.ui-textarea-x > textarea.ui-textarea
的HTML结构实现的,计数内容自动创建,并定位在右下角。
但是,可能有产品不喜欢这样的交互,希望位置在其他地方,本组件也是支持的。两点:
<label>
元素的for
属性值和输入框元素的id
值一致;- 根据输入框类型不同,
<label>
元素的类名需要包含ui-input-count
或者ui-textarea-count
。
此时就会有自动计数和超过范围时候的颜色高亮提示效果。
如果<label>
元素中还有其他内容,则计数字符需要用一个span
标签包起来。
<label for="comment" class="ui-textarea-count"><span>0</span>/200</label>
非表单元素的验证
建议所有的验证和提交走原生的<form>
元素。
如果遇到特殊场景,无法使用<form>
元素,不要担心,Validate.js也是支持的。
参考下面的例子,一个<input>
框在普通的<div>
中有个普通的<button>
标签按钮。
<div id="notForm"> <span class="ui-input ui-search-input"> <input type="search" required> <span class="ui-icon-search">搜索</span> </span> <button class="ui-button" data-type="primary">搜索</button> </div>
JS代码如下:
// 非表单元素验证
var notForm = document.querySelector('#notForm');
var notFormValidate = new Validate(notForm, null);
button.addEventListener('click', function () {
if (notFormValidate.checkValidity()) {
new Dialog().alert('无表单验证通过');
}
});
区别在于需要自己绑定点击事件以及回车事件(原生表单内置的,这里回车没支持),然后执行全局验证方法myValidate.checkValidity()
,如果返回是true
,则表示整个表单验证通过。
一开始就开启失焦验证
这种交互和浏览器内置的交互行为是相悖的,我个人是极力反对的,但是架不住一些难搞的产品经理,希望一开始就算用户没提交,也要开始验证。可以执行一次myValidate.immediate()
方法。
如果产品经理希望一开始就验证,但不希望这么重(上面即时验证开启后,focus聚焦也会提示),则需要自己添加失焦事件,可以参考下面案例。
// 一开始就失焦验证 var eleBlurForm = document.querySelector('#blurForm'); var blurValidate = new Validate(eleBlurForm, function () { new Dialog().alert('验证通过'); }); // 添加失焦验证事件处理 eleBlurForm.querySelectorAll('input, select, textarea').forEach(function (eleInput) { eleInput.addEventListener('blur', function () { // 需要还没有开启即时验证 if (!eleBlurForm.isImmediated) { // 提示时候内容不选中 document.validate.focusable = 0; // 提示 blurValidate.reportValidity(eleInput); } }); });
验证成功后走原生提交
这个使用实例对象的stopValidate
属性实现:
new Validate('#submitForm', function (validate) { validate.stopValidate = true; this.submit(); });
JS单独触发某个输入框的验证
关键部分源码如下:
验证码:<input id="code" pattern="\d{4}" required> <button id="button" type="button" class="ui-button" data-type="danger">单独验证验证码</button>
var aloneValidate = new Validate('#validForm'); var eleCode = document.querySelector('#code'); button.addEventListener('click', function () { aloneValidate.reportValidity(eleCode); });
除了使用实例对象上的reportValidity()
方法,也可以使用控件元素上原生的reportValidity()
方法,例如:
button.addEventListener('click', function () { document.querySelector('#code').reportValidity(); });
使用文字进行错误提示
有时候,设计师设计的出错提示是内嵌在页面中的一段文字,而不是浮层提示,本组件也是可以支持的,但是,虽说支持,其实是有能力实现这样的效果,组件本文并未内置这样的交互支持,因为这样的设计是和 LuLu UI 的理念不相符的。
实现原理:
- 隐藏ErrorTip效果;
- 通过输入框的valid/invalid事件控制提示文字的显隐;
比方说下面这个例子:
代码示意如下:
/* 隐藏红色出错提示 */
.ui-tips-error {
display: none;
}
<form id="validateForm"> <p> 邮箱:<input type="email" id="email" is="ui-input" name="email" required> <output id="result" style="color:red;"></output> </p> <button type="submit" class="ui-button" data-type="primary">提交</button> </form>
new Validate('#validateForm');
// 自定义成功和出错提示处理
email.addEventListener('valid', function () {
result.innerHTML = '';
});
email.addEventListener('invalid', function () {
result.innerHTML = this.validationMessage;
});
本页贡献者:
zhangxinxu