现在的位置: 首页 > easyui > Form > validatebox > 正文
浅谈validatebox组件校验的实现方案
2012年08月09日 validatebox ⁄ 共 3797字 评论数 7 ⁄ 被围观 15,270 views+
文章目录
[隐藏]

Easyui框架里的validatebox组件在表单相关的一系列组件中占有极其重要的位置,大多表单类组件都直接或者间接的依赖它,所以弄清楚validatabox是如何对用户输入做校验是很有必要的。注意,本篇文章只讨论如何实现校验,而不讨论具体的校验规则。

原理分析:

说到校验,就必须说道事件的监听,做过自动完成同学应该都知道,我们往往选择监听兼容性较好的keyup事件来实现。不过validatebox却不是通过keyup,而是通过监听focus来实现校验的。

这样问题就来了,当用户将焦点放到输入框A上的时候会触发一次绑定focus事件,后面鼠标不移开,继续输入的时候并不会触发focus了,validatebox是如何实现不停监听的呢,熟悉js的朋友可能立刻想到了setTimeout,对就是它。

不过仅仅靠setTimeout还是不够的,还必须要循环或者递归地setTimeout才行,我们看看1.2.4版本的实现源码,或许就能豁然开朗了:

function bindEvents(target){
    var box = $(target);
    var validatebox = $.data(target, 'validatebox');
    validatebox.validating = false;
    box.unbind('.validatebox').bind('focus.validatebox', function(){
        validatebox.validating = true;
        (function(){
            if (validatebox.validating) {
                validate(target);
                setTimeout(arguments.callee, 200);
            }
        })();
    }).bind('blur.validatebox', function(){
        validatebox.validating = false;
        hideTip(target);
    }).bind('mouseenter.validatebox', function(){
        if (box.hasClass('validatebox-invalid')) {
            showTip(target);
        }
    }).bind('mouseleave.validatebox', function(){
        hideTip(target);
    });
};

其中,最关键的代码是这一段:

(function(){
    if (validatebox.validating) {
        validate(target);
        setTimeout(arguments.callee, 200);
    }
})();

大费周折地定义并立即运行匿名函数的目的是什么?知道arguments.callee的朋友,一眼就能看出来了,是为了不停地递归,arguments.callee就是调用运行中函数自身的意思,是一种实现递归的很便捷的方案。

只要满足validatebox.validating条件,就会一直递归调用,而validatebox.validating只有在blur的时候才被设置为false,到这里各位应该能理解validatebox为什么能够实时校验用户的输入内容了。

涉及问题:

form组件的validate方法内部的focus常引发的问题:

老早以前,我写过一篇《My97DatePicker与easyui共用问题》,该文对故障原因作了粗略分析;同时,如果将我上篇文章中扩展combo组件的disableTextbox方法用到表单中,并且最后用form组件的validate方法校验整个form时,便会报错。

为什么form组件的validate方法如此容易引发问题?关键点还是在focus上,先看1.2.4的源码:

function validate(target){
    if ($.fn.validatebox) {
        var box = $('.validatebox-text', target);
        if (box.length) {
            box.validatebox('validate');
            box.trigger('blur');
            var valid = $('.validatebox-invalid:first', target).focus();
            return valid.length == 0;
        }
    }
    return true;
};

代码首先查找form中带validatebox-text样式的元素,找到后直接调用validatebox组件的validate方法对每一个找到的元素做一次校验,然后主动触发blur事件,最后将焦点房到第一个验证不通过的元素上。

考虑得周全一点的话,如果我查找的box当中,有的元素是disabled掉的呢?disabled的元素上没法focus的,而在1.2.5、1.2.6、1.3版本中,该方法做了稍微改动,这个改动在作者的更新日志中并未提及,拿1.3的压缩码来看:

function _39f(_3a3){
    if ($.fn.validatebox) {
        var box = $(".validatebox-text", _3a3);
        if (box.length) {
            box.validatebox("validate");
            box.trigger("focus");
            box.trigger("blur");
            var _3a4 = $(".validatebox-invalid:first", _3a3).focus();
            return _3a4.length == 0;
        }
    }
    return true;
};

多了行box.trigger("focus");主动触发当前表单内的所有符合条件的输入框使其顺序focus,这行代码可谓杀伤力巨大,真是“宁可枉杀一千,不可漏掉”一个,本来强行将焦点定位到最后一个元素上就有跟disabled元素冲突的风险,这下倒好,这种风险一下子扩散到所有需要校验的元素上了。

既然原因我们已经分析出来了,那么就得想办法解决这个问题,不改源码,那就只能复写form组件的validate方法,首先box.trigger("focus");这句杀伤力巨大的代码需要去掉,再者,我们得想办法解决第一个校验不通过元素就是disabled的情况。

本文给出一个不算太完美的覆写方法:

$.extend($.fn.form.methods, {
    validate: function(jq){
        function showTip(target){
            var box = $(target);
            var msg = $.data(target, "validatebox").message;
            var tip = $.data(target, "validatebox").tip;
            if (!tip) {
                tip = $("<div class=\"validatebox-tip\">" + "<span class=\"validatebox-tip-content\">" + "</span>" + "<span class=\"validatebox-tip-pointer\">" + "</span>" + "</div>").appendTo("body");
                $.data(target, "validatebox").tip = tip;
            }
            tip.find(".validatebox-tip-content").html(msg);
            tip.css({
                display: "block",
                left: box.offset().left + box.outerWidth(),
                top: box.offset().top
            });
            $('.validatebox-tip').bind('mouseover', function(){
                var tip = $.data(target, "validatebox").tip;
                if (tip) {
                    tip.remove();
                    $.data(target, "validatebox").tip = null;
                }
            });
        };
        function validate(target){
            if ($.fn.validatebox) {
                var box = $(".validatebox-text", target);
                if (box.length) {
                    box.validatebox("validate");
                    box.trigger("blur");
                    var valid = $(".validatebox-invalid:first", target);
					if(valid.prop('disabled')){
						showTip(valid[0]);
					}else{
						valid.focus();
					}
                    return valid.length == 0;
                }
            }
            return true;
        };
        return validate(jq[0]);
    }
});

细心的同学应该还是可以通过演示页面找出不足之处的,如果大家有什么好的方法,欢迎留言指教,或者一起讨论。

演示地址:

http://www.easyui.info/easyui/demo/form/061.html

目前有 7 条留言 其中:访客:5 条, 博主:2 条

  1. 活力橙 : 2012年08月10日09:00:50  -49楼 @回复 回复

    果断来顶!好文不解释。


  2. 管理员
    世纪之光 : 2012年08月10日13:04:40  -48楼 @回复 回复

    @活力橙
    丫的,光顶没用啊,有收获不?

  3. www.94haha.com : 2012年08月28日12:27:42  -47楼 @回复 回复

    很多东西都挺实用呢

  4. 就是哈哈 : 2012年08月29日07:31:57  -46楼 @回复 回复

    有对我有用的,转到QQ空间了,嘿嘿


  5. 管理员
    世纪之光 : 2012年08月29日09:02:10  -45楼 @回复 回复

    @就是哈哈
    很高兴这篇文章能帮到你,随便转吧,注明来源就行了。

  6. 幸福的小酒 : 2012年09月02日14:37:39  -44楼 @回复 回复

    博客比我的强多了,学习下

  7. Linode : 2012年11月09日03:08:40  -43楼 @回复 回复

    讲的很详细的,收下了。。。

给我留言

留言无头像?


×