前言
写这篇文档主要是为了渣土系统这样的KO(即Knockout)老项目,这里边使用的非常多。mouseValidate这个库确实没怎么听过,以至于我想找一下官方文档都搜不到,只能由我自己简单的定义一下了。mouseValidate是一个基于jquery的表单验证库,目前公司项目中除了KO老项目中普遍使用,其它系统基本没有用到,不过这并不妨碍我们学习它的设计思想,以及根据我们自己的需求对它进行定制修改。
基本使用
- 第一步引进来,例如:
define(["jquery", "durandal/app", "durandal/composition", "knockout", "mouseValidate"], function ($, app, composition, ko, mouseValidate) { function viewModal() {
} return viewModal; });
-
第二步初始化验证
实际上就是定义一些规则,非空啊,手机号码啊,身份证号码之类的。这里只摘录核心代码片段,具体实例可以到渣土系统中找,例子非常多。另外,注意这个initValidate方法一般页面加载完毕就先调用一遍,这样被添加规则的表单字段就能做出响应了。
//添加规则 model.initValidate = function () { mouseValidate.clearValidate(); mouseValidate.initValidate($(_dom).find("#businessLicenseNumber"), {"isEmpty": "请输入工商营业执照编号"}); mouseValidate.initValidate($(_dom).find("#registerMoney"), {"isEmpty": "请输入注册资金","isNumber":"请输入正确数值"}); mouseValidate.initValidate($(_dom).find("#legalRepresentative"), {"isEmpty": "请输入企业法人"}); mouseValidate.initValidate($(_dom).find("#legalRepresentativeContact"), {"isEmpty": "请输入企业法人联系方式","isAllPhone":""}); mouseValidate.initValidate($(_dom).find("#legalRepresentativeNumber"), {"isEmpty": "请输入企业法人身份证号码"}); };
//提交表单时校验
model.submit = function(){
//注意returnFlag并不是boolean类型,而是"true"或"false"
var returnFlag = MouseValidate.returnValidate();
if (returnFlag === "false") {
commonUtil.showTip(_dom, "error", "完成提示与必填项!", "company-tip");
return false;
}
};
-
效果展示
调用MouseValidate.returnValidate(),校验的是整个表单。校验不通过的表单项显示红色边框。
单击某个表单项让它获得焦点,然后再失去焦点,会单独对这个表单项进行校验。校验不通过,会显示相应提示信息。
缺陷
-
不支持自定义校验规则,比如:校验车牌号。
-
disabled或readonly的表单项,不能触发校验,这是因为这两种情况下,没办法走如下流程:获取焦点——手动输入或选择——失去焦点的操作方式,所以不能触发校验。为了便于大家理解,这里截取一个这样的场景。下面3个字段,都是通过点击按钮,在弹框界面选择某项,然后带回来赋值给右边的input框。因为是选择方式,不支持手动输入,故这几个input框全部都是readonly状态。
下面选取具体的值后,发现Input框仍然是红色边框
优化与扩展
-
本次优化与扩展的目的
针对上面2个缺陷,本次改造mouseValidate库希望达到2个目的。
-
支持自定义校验规则,这一点非常重要。
-
readonly、disabled 字段值发生变化后,触发校验规则。
-
-
mouseValidate库源码概览
其实结构非常简单,原来的代码主要由如下几部分构成:2个容器、初始化规则收集函数、表单提交校验函数、清除规则函数、一堆内置校验规则,本次扩展新增了一个customKeys和customFlag,以及增加了一个供外部注册规则的 registerValidate 方法,它们专门用于处理自定义校验规则。这些内容里比较重要的就是initValidate和returnValidate。
-
initValidate
initValidate方法中,我并不打算详细解读focus、blur等方法,有兴趣的自己去研读。我主要讲一下自己加的一段处理readonly和disabled的逻辑。因为readonly(或disabled)表单项可以通过选择方式或其它非手动输入方式得到值,所以这里只能监听一下它的change事件,其实说穿了很简单,就是当它的值发生变化时,简单校验一下,如果为空就给它来个红色边框,反之就正常边框。
//readonly和disabled的元素增加一个change事件,同时还需在代码中还需要手动触发一下change事件,
// 这是因为readonly和disabled会导致默认的change失效,$(_dom).find("#fenceNames").trigger("change");
if ($(node).attr("readonly") === "readonly" || $(node).attr("disabled") === "disabled") {
$(node).change(function (evt) {
var value = $(node).val();
if (value || value === 0) {
$(node).css("border", "1px solid #B7C7CD");
} else {
$(node).css("border", "1px solid red");
}
});
}
但是,这里有一个非常重要的细节,readonly或disabled后,change事件需要手动显示触发**$(_dom).find("xxx").trigger("change")**。这个地方我一直没有想到更好的办法,只能在当前表单项拿到值后,手动触发一下了。就像下面这样:
function initEvents() { app.off('app:construction:choose:fences'); app.on('app:construction:choose:fences').then(function (data) { self._selectedFences = data; if (self._selectedFences && self._selectedFences.length) { var fenceNames = commonUtil.getMappingFields(self._selectedFences, "fenceName", ","); self.fenceNames(fenceNames);
//触发一下change事件配合mouseValidate库 $(_dom).find("#fenceNames").trigger("change"); } });
}
-
returnValidate
returnValidate方法用于提交表单时,统一校验所有添加了规则的表单项。使用方法类似于:
//提交表单时校验 model.submit = function(){ //注意returnFlag并不是boolean类型,而是"true"或"false" var returnFlag = MouseValidate.returnValidate(); if (returnFlag === "false") { commonUtil.showTip(_dom, "error", "完成提示与必填项!", "company-tip"); return false; } };
那么它里边具体是怎么做的呢?
- 校验isEmpty规则,将所以不通过的表单节点,放入到flag容器中;
- 校验自定义规则,将所有不通过的表单节点,放入到flag容器中(这个是我新加的逻辑);
- 统一处理flag容器中的节点,依次添加红色边框,以表示哪些没有通过校验。
下面主要讲一下我自己添加的逻辑。这里要结合前面的inValidate方法来看。故这里重新再贴一遍截图。我们首先要搞清楚customFlag里到底存储的是什么。customFlag.set(node, key) 中的node很好理解,key实际上就是 isVehicleNum、isTaskNum 这样的规则函数!所以customFlag里实际上是类似这样的结构:[ {node: isVehicleNum}, {node: isTaskNum} ] 这样的键值对。
了解了上面这些,然后如果对eval变态函数有些了解,下面这段逻辑就基本能看明白了。eval的神奇在这里表现得淋漓尽致了,能够将字符串拼接成的函数调用执行起来,并且拿到调用后的结果!当然前提是你这个method首先必须得存在,所以下面接着必须讲一下 registerValidate方法的作用了。
//处理自定义的校验
customFlag.forEach(function (method, node) {
var value = $(node).val();
var res = eval(method+"('"+value+"')");
if (res.success) {
if (flag.get(node) === "false") {
flag.set(node, "true");
}
} else {
flag.set(node, "false");
}
});
-
registerValidate
这个方法比较简单,这里就不多说了,主要目的就是向customKeys数组中添加元素(自定义规则函数名称)。同时,添加进去的规则,务必要有对应的校验函数。后面会举一个添加车牌号校验规则的实例,这个例子也是实际开发中遇到的需求,比较有代表性。
使用自定义校验
-
注册自定义规则,并给表单项添加该规则
model.initValidate = function () { //注册自定义规则isVehicleNum mouseValidate.registerValidate("isVehicleNum"); mouseValidate.clearValidate(); //给表单项添加规则,这里包含2个规则,isEmpty、isVehicleNum,多个规则是可以一起使用的 mouseValidate.initValidate($(model._constrain).find("#vehicleNum"), {"isEmpty": "请输入车牌号", "isVehicleNum": "请输入正确的车牌号码"}); }
-
在mouseValidate文件中定义规则函数
注意规则校验函数的返回值结构,务必保持一致!
/* * 车牌号验证(这里只简单校验一下前缀和长度) * @param {string} */ function isVehicleNum(val) { //校验前缀 var prefix = eUrban.global.LICENSE_PLATE_NUMBER || ""; if (prefix && val.indexOf(prefix) !== 0) { return {success: false, message: '车牌号前缀不正确!'}; } //校验长度,车牌号码长度目前是7、8位数 if (val.length < 7 || val.length > 8) { return {success: false, message: '车牌号长度不正确!'}; } return {success: true}; }
-
校验效果
后续
增加文本长度校验规则
核心代码片段
/** * 校验文本长度 * @param val * @returns {{success: boolean, message: string}|{success: boolean}} */ function checkLen(val) { if (!window._checkNode) { return {success: true}; } var min = $(window._checkNode).attr("minlen"); var max = $(window._checkNode).attr("maxLen"); min = min && !isNaN(min) ? Number(min) : 0; max = max && !isNaN(max) ? Number(max) : 0; var len = val.length; if (len < min) { return {success: false, message: '文本长度不得小于' + min}; } if (len > max) { return {success: false, message: '文本长度不得大于' + max}; } return {success: true}; }
//returnValidate、blur等代码中增加window._checkNode赋值。
customFlag.forEach(function (method, node) {
window._checkNode = node; //增加它,用于在校验函数中获取边界
var value = $(node).val();
var res = eval(method+"('"+value+"')");
if (res.success) {
if (flag.get(node) === "false") {
flag.set(node, "true");
}
} else {
flag.set(node, "false");
}
});