干货!一步步实现自己的表单验证器

表单验证的需求简直太常见了。“所有用户的输入都是不可信的”这个思想指导我们在设计表单的时候,一定要进行用户输入的验证。对于用户体验而言,越早的反馈则越佳,所以表单验证的工作应该尽可能地在前端就进行(当然,前端对于后端而言也是输入端,所以后端仍然需要进行检验)。简单的表单验证完全可以给input绑定几个change事件来进行。但表单一复杂,或者相似验证规则一多,这种编码的方式就很难管理事件了。这时候,我们通常需要使用一些库来帮助我们处理表单验证的工作。前端表单验证的库太多了,随便一搜:表单验证-百度搜索

那……为什么还要自己实现一个呢?因为去年工作中遇到了比较复杂的验证逻辑,选一个别人的库一是要学习api,二是维护起来困难,要符合自己的页面风格也不是那么轻松;再加上表单验证器并不复杂,于是就打算自己写一个。所以这个表单验证器并不算那么地通用,不过我觉得传播思路和方法更重要,要想修修改改变成一个通用的库也很容易,只是没那么重要罢了(其实是懒……)。

依赖

jquery + bootstrap。jquery看来是缺不了,bootstrap没那么重要,对代码稍做修改就可以取消依赖。

能做什么

我期望这个表单验证器直接在html代码中指定好需要采用什么样的规则,不需要用javascript进行多余的配置;允许暂时跳过验证;允许扩展验证规则;提供验证难过、验证失败、警告三种验证结果;允许手动触发验证等等。

一个典型的bootstrap风格的表单项应该长这个样子:

 重点看第5行,对于一个普通的input组件,我加了两个属性, data-validate-disable 有值表示表单验证时,会跳过这个input组件; data-validate 则存放如果要验证的话,将采用什么样的验证规则。这个例子中表示要验证这个input组件是否为空、是否为手机号码这两个规则。注意,在request 的后面我还加了一点内容,这是我想传递给验证规则的参数,比如这里我期望告诉验证器,如果input值只有空格,也认为是有值的。相似的,我们可以定义一个规则 min 来处理最少几个字符,并在html中把这个设定值传递进来,就像这样: data-validate="min:10" 。我也期望一个验证规则允许多个参数传递,参数与参数之间用逗号隔开。

看起来很有意思,那就开始动手吧。

先写个jquery插件吧

 给jquery对象添加一个 validation 方法,用法很简单: $(Selector).validation() 。支持链式调用。Selector比较自由,如果是 :input 元素,则直接给这些元素初始化验证器;否则找出它们所有的 :input 子元素,给这些子元素初始化验证器。什么意思呢?就以上面的html片断为例,你可以任性地使用如下任意一种方式初始化验证器:

  • $('[name=mobile]').validation(); // 选择:input 
  • $('.form-group').validation(); // 选择普通节点,对所有:input子元素生效 
  • $('body').validation(); // 对页面中所有的:input元素生效 

再说说所谓的初始化,其实就是绑定事件。那么要绑定什么事件呢?主观上,对于一个输入框,当我们输入好了,焦点离开时,应该就进行输入内容的验证,并给出验证结果。如果验证失败,会有提示信息,但这些提示信息应该在重新获得焦点时清除,否则当用户在修正输入的时候还一直提示上次的错误信息,会让用户无所适从。那么针对以上的情况,我们需要在失去焦点(blur)和获得焦点(focus)时分别绑定验证的方法(validate)和清除错误信息的方法(clear)。另外针对checkbox、radiobox、select等控件,最好也给select事件绑定验证方法。

为什么不使用更通用change事件来绑定验证方法呢?原因有二:

  1. 我觉得有些场景需要用javascript载入默认值或历史输入。这些内容应该延后验证(不是不验证),不然刚打开页面就是表单验证错误让人很难受。所以change事件不合适。
  2. 由于获得焦点会清空错误消息,如果没有修改内容,直接失去焦点,这时不会触发change事件,导致不会再验证,表现为错误信息丢失。

那下面就来具体看看validateclear两个方法。

清除错误消息及验证状态设置

 这一段一起说,因为这部分内容是dom操作相关的,与bootstrap强相关,如果你不用bootstrap,那就尽情地替换掉吧(估计改几个类名就行了)。这几个函数的用途从名字上就可以看出。clear上面已经提到过,清空验证信息;另外三个分别处理验证通过、验证失败和警告时的信息展示。简单是简单,但这里有两个问题需要额外考虑一下。

第一个是验证信息优先级的问题:

一个输入项可能同时有多个验证规则要匹配,可能有些验证规则通过,有些失败,有些警告。在具体处理时,我觉得比较合理的流程是:如果出现验证失败,则立刻停止后续验证,直接提示错误信息;如果警告,则提示警告信息,并继续后续规则验证;如果验证成功,理论上不需要做任何处理,直接进行下一步验证,但谨慎起见,还是做一下清空错误信息的工作,但要注意不可以清空掉警告信息(错误信息不用管,因为一产生错误信息,验证也就停止了,不会再触发验证成功了)。

另一个问题是同一个表单单元中有多个输入控件的问题,就像这样:

QQ20150415121912

几个控件共用了一个错误信息展示文本,我觉得如果某一个控件输入内容有误,必须再次修改这个控件内容才能清空错误信息,修改别的控件时,这个错误信息应该保留。所以在清除错误信息时,我们额外需要知道这个错误信息是由哪个控件引起的。在上面的示例中,我们在处理验证错误和警告时,把引起问题的控件的name记录到表单单元的data-for属性中了。到需要清空的时候再比对一下data-for和当前控件的name是不是一致,不一致就不清空错误信息。

处理验证流程

我们给输入控件的blurselect事件绑定了validate方法,那么这个validate方法如何实现呢?

 先看这段代码的前一部分,我定义了三个$.Callbacks对象,分别用以处理不同验证结果的响应。从效果上,不用$.Callbacks对象,直接传递函数引用也是可行的,我是想把dom操作和验证逻辑分离开,用类似事件触发的异步形式来处理验证逻辑。

再看第17行,它规定了两种情形不执行验证,一种是控件被禁用,另一种是控件中存在data-validate-disable属性。这个不用多解释了。

重点看20~30行,这段是关键。第21行中,我们把data-validate中的内容按空白(空格、tab、换行)切割到一个数组conditions中,conditions中的每一项都是一条需要验证的规则。那么很自然地,遍历这个数组。再看第26行,引入了一个新的方法pickStrategy,很明显它是对这条规则进行验证。先别管它的实现,看看它接收的参数。还记得前面我们说要允许用 rule:param1,param2 的形式给验证规则传入参数吗?pickStrategy拿到的第一个参数就是 [rule, param1, param2] ,通过condition.spit(/[:,]/g) 解析出。第二个参数是当前处理的控件的jquery对象,后面三个分别是验证成功、失败、警告三种情形的回调对象。如果验证失败,pickStrategy应该返回 false ,同时中止验证,否则验证下一条规则。

策略模式

下面就要来说pickStrategy方法了,不过先要补充一点背景知识——策略模式。策略模式是一种设计模式。

设计模式是搞软件工程的人常常挂在嘴边的词汇,表示对设计的复用。当然前端开发在工程化的进程上每家公司情况各异,我估计绝大多数公司的前端开发并不考虑工程上的问题,只考虑完成需求。因而对于没有OO编程开发背景的前端开发而言,设计模式可能是陌生的,甚至程序设计(别紧张,没有在说程序编写)本身就是陌生的。由于工程化的忽略和javascript语言本身的优点(很多模式没必要实现)和缺点(很多模式无法实现),前端开发中很少提设计模式。那么前端开发者怎么理解设计模式呢?设计模式就是一系统问题(场景)的通用解决思路。比如有人觉得jquery的链式调用很好用,能很大程度降低工作量,于是在别的地方也用函数 return this; 的方式构造支持链式调用的函数,这就可以认为是一个模式(谈不上设计模式)。

那策略模式是为了解决什么问题,或应对什么场景的呢?如果干一件事、完成一个任务可以有不同的策略,不同的算法来完成,这些策略、算法应该具有相同的输入和输出,但可以使用不同的资源(即不care中间实现差异)。具体要采用什么策略和方法则在程序运行时依据条件选择。举个例子?

QQ20150415155218

 压缩文件,你可以选择不同的算法,但它们拥有相同的输入和输出。

那么我们做表单验证跟策略模式有什么关系呢?我们完全可以把每个验证规则看作是一个策略,我们支持的所有的策略放在一起,取个高大上的名字叫“策略池”。当我们想验证一个规则的时候,只需要去策略池中取出这个策略跑一下,不需要在一个很大的函数里面跑一堆if…else,或者switch…case。另外如果有新的规则加进来,只要把它塞到策略池中,无需更改已有的代码,实现验证规则和验证流程解耦

先实现一个策略池吧:

 太棒了,一句代码就完事了,爱死javascript了!

再回到pickStrategy方法,看看怎么从这个策略池中拿到我们需要的验证规则:

 先说这个token,还记得它是什么吗?如果要验证的规则是“min:5”,那么token就是 ['min','5'] 这个数组。第2行取出这个数组的第一项 'min' ,再从策略池中取出这个验证规则。如果没有这个规则,则跳过这次验证,认为此次通过;如果有这个规则,则执行这个规则(见第5行),需要注意一下第5行的token已经发生变化了,除去规则名称,只剩下参数了,延续前面的例子,这里token应该是 ['5'] 。

现在我们的策略池是空的,那可不行,先试着写一个验证“min”规则的方法吧:

 这个方法内,this指向当前验证的控件的jquery对象,第1个参数是额外带给验证规则的参数;第2个参数是验证成功的回调,使用时需要配合 return true; (见9、10行);第3个参数是验证失败的回调,使用是需要配合 return false; (见13、14行);第4个参数是警告的回调,需要配合 returntrue; ,这个验证中没用到。

起始的几个验证方法也可以直接写在策略池中,我写了几个,供大家参考(代码比较长,展开需谨慎。不想展开?你可能要错过身份证验证、邮箱验证、url验证等一堆干货了!):

添加验证规则

说好的支持自定义验证规则呢?放心,不会少的,而且超简单:

 加个静态方法搞定。

手动发起验证

有些场景下需要手动check一下表单内容才放心吧:

 原理很简单,对:input子元素触发一次blur事件。

还有一些是我没有做的

都不是很复杂,因为我的业务场景不关注这些,所以就懒过去了。

  1. 没做通用的ajax验证
    现在对于ajax的验证,可以这样做:

     表单提交前,除了检查有没有.has-error的元素找到填写出错项,还要检查有没有.wait元素,如果有的话都不能立刻提交。
    如果要有一个通用的ajax验证机制,可以自行扩展$.validation方法
  2. 没做验证失败时表单提交阻断
    正如上一条中说的,表单提交前,需要检查.has-error和.wait元素,可以自行扩展$.fn.validation方法,添加form的submit事件绑定。
  3. 没有为handle验证状态的dom操作提供接口
    不想做,bootstrap挺好的。
  4. 没有测试用例
    不打算给这些代码添加逻辑了,不想写测试用例。

    下载

    validation.js

      转载自:http://www.zhouhua.info/2015/04/15/validation/

    • 0
      点赞
    • 1
      收藏
      觉得还不错? 一键收藏
    • 1
      评论
    评论 1
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值