Hello World
Spiga

jQuery Validation插件remote验证方式的Bug

2009-12-04 14:29 by 老赵, 8957 visits

jQuery插件很多,其中一个重要的插件便是jQuery Validation,它的作用是对表单进行验证,还上了jQuery官网。不过奇怪的是,最近用下来感觉有些古怪,因为好像有些死板,已有功能的应变能力还不强,甚至还有个奇怪的Bug。任何项目有Bug其实也正常,但这个Bug其实是一个文档上已经记载了,却没有实现的功能,这就有些说不过去了。这个问题便出在remote验证方式上,还好修改起来非常容易,在此记录一下,也方便以后的参考。

在表单验证时,有时候会需要发一个AJAX请求去服务器上进行判断,例如在用户注册时检查用户名是否存在。jQuery Validation插件提供了一种remote方式来实现这一点。例如我可以这样验证表单:

<form id="regForm">
    <input type="text" name="userName" />
</form>

<script language="javascript">
    $('#regForm').validate({
        'rules': {
            'userName': {
                'required': true,
                'remote': '/account/verify'
            }});
</script>

这样,jQuery Validation便会请求“/account/verify?userName=jeffz”这样的URL来获取true/false。可惜的是,我们在使用ASP.NET MVC时,往往会将input的name写为特定的形式,目的是利用DefaultModelBinder的强大绑定功能。例如:

<form id="regForm">
    <input type="text" id="userName" name="user.Name" />
</form>

与此同时,我们用来进行验证的Action方法,它的参数名可能也有所不同:

public ActionResult Verify(string name) { ... }

根据文档描述,此时我们应该这样写:

$('#regForm').validate({
    'rules': {
        'user.Name': {
            'remote': {
                url: '/account/verify',
                data: {
                    name: function() { return $("#userName").val(); }
                }}}}});

可是,从实际效果来看,jQuery还是在请求“/account/verify?user.Name=jeffz”,百思不得其解。确认在三之后只得求助于jquery.validation.js源码,看后差点晕过去:

remote: function(value, element, param) {
    if ( this.optional(element) )
        return "dependency-mismatch";
    
    ...
    
    param = typeof param == "string" && {url:param} || param; 
    
    if ( previous.old !== value ) {
        previous.old = value;
        var validator = this;
        this.startRequest(element);
        var data = {}; 
        data[element.name] = value; // data还是以element.name为准?
        $.ajax($.extend(true, {
            url: param,
            mode: "abort",
            port: "validate" + element.name,
            dataType: "json",
            data: data,
            success: function(response) {
                ...

我很奇怪,不知道为什么会这样做,这样根本没有起到指定参数名的作用。那么,改吧:

remote: function(value, element, param) {
    if (this.optional(element))
        return "dependency-mismatch";

    ...

    param = typeof  param == "string" && {url:param} || param;

    if (previous.old !== value) {
        previous.old = value;
        var validator = this;
        this.startRequest(element);
        var data = {};
        data[element.name] = value;
        $.ajax($.extend(true, {
            // url: param,
            url: param.url,
            mode: "abort",
            port: "validate" + element.name,
            dataType: "json",
            // data: data,
            data: param.data || data,
            success: function(response) {
                ...

修改两处即可,问题就此解决。只可惜,jquery.validate.min.js类似的文件只能自己进行压缩了。

居然会出现这样的问题,实在令人费解。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

49 条回复

  1. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-04 14:37:00

    前几天刚试过,这个玩意儿的兼容性很差,所设计的逻辑似乎完全不考虑动态页面的技术。遂放弃。。。。

    param = typeof param == "string" && {url:param} || param;

    应该是写错了,

    param = typeof param == "string" ? {url:param} : param;

    否则如果一定要用&&和||的话,也应该这样写:

    typeof param == "string" && param = {url:param};

  2. 老赵
    admin
    链接

    老赵 2009-12-04 14:42:00

    @Ivony...
    啥叫动态页面技术?兼容性还好吧,各浏览器都能用。

  3. 飞翔的柴禾
    *.*.*.*
    链接

    飞翔的柴禾 2009-12-04 14:59:00

    param = typeof param == "string" && {url:param} || param;
    相当于
    if (typeof param == "string") {
    param = {url:param};
    }

  4. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-04 15:03:00

    Jeffrey Zhao:
    @Ivony...
    啥叫动态页面技术?兼容性还好吧,各浏览器都能用。



    就是ASP.NET、PHP、JSP之类的技术哈。

    其实我所不能理解的是他古怪的设计逻辑。


    按照常人的一般理解,一个验证插件,其工作逻辑应该是,在某个时机,检查需要验证的输入控件,确保他们都符合某种规则,否则提示错误信息。

    那么这里面需要定制的:
    1、时机
    2、确定需要验证的输入控件
    3、规则


    而这个作者的脑子里想的显然和我不一样,他认为验证插件工作逻辑是这样的,提供一份规则描述清单,然后把他附加到某个表单,然后我就工作了。

    这样带来的问题是很明显的,与ASP.NET的ServerForm兼容问题十分严重。

  5. 老赵
    admin
    链接

    老赵 2009-12-04 15:05:00

    @飞翔的柴禾
    看来等价于?:啊,那么他这么写是吃饱了撑的么……

  6. 飞翔的柴禾
    *.*.*.*
    链接

    飞翔的柴禾 2009-12-04 15:12:00

    Jeffrey Zhao:
    @飞翔的柴禾
    看来等价于?:啊,那么他这么写是吃饱了撑的么……



    某些不支持:?的语言中比较常见的trick写法吧,呵呵,利用表达式求值顺序和表达式的值

  7. 老赵
    admin
    链接

    老赵 2009-12-04 15:13:00

    @Ivony...
    我倒觉得这个思路还好吧,其实和ASP.NET的“标记”一种思路……
    使用时输出<%= this.txtName.UniqueID %>应该就好了吧。

  8. 老赵
    admin
    链接

    老赵 2009-12-04 15:14:00

    @飞翔的柴禾
    哪些语言呀?这也要利用&&和||不仅仅是布尔运算的特性吧……

  9. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-04 15:21:00

    Jeffrey Zhao:
    @飞翔的柴禾
    看来等价于?:啊,那么他这么写是吃饱了撑的么……




    看起来是不会等价的,应该是写错了。


    用&&来代替if还是蛮常见的,但&&和||代替if else就很罕见了,因为有个前提(下面说)。

    其实这是逻辑运算符短路运算造成的:

    对于A && B || C这样的表达式而言,&&的优先级高于||
    所以,如果A为假,则B不用计算(因为false && anything === false),直接将false代入A && B,形成false || C,计算C表达式的值。

    但如果A为真,则还需要计算表达式B,确定A && B的值是否为真。但一般这个计算结果都会是真,则true || C === true,不计算表达式C。

    所以要达成if else的效果,有一个前提是B必须是恒真表达式。

    &&和||只能达成if else的效果,不能达成?:的效果。

  10. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-04 15:32:00

    Jeffrey Zhao:
    @Ivony...
    我倒觉得这个思路还好吧,其实和ASP.NET的“标记”一种思路……
    使用时输出<%= this.txtName.UniqueID %>应该就好了吧。




    问题是都可以解决,但这个思路是有问题的。

    这样造成了验证规则与输入控件的紧耦合,增删输入控件都必须同步调整验证规则,这是很那啥的。。。

    当然这个插件也提供了直接在输入控件上描述验证规则的方法,但在我看来,作者的思路本来就错了。

    作为jQuery的插件,jQuery的核心就是选择器,那为什么不好好利用呢?

    换言之验证规则为什么不描述成选择器+函数而要自作聪明的去match name属性呢?



    当然这种设计思路有他的道理。先决条件是数据结构静态,即一个表单提交的name是静态不变的。如果一个表单所需要提交的信息总是确定的,只是外观经常调整,这种设计思想无可厚非。但我遇到的现实恰恰是,这个表单所提交的信息不确定。

    现在动态页面的技术越来越多,大家都会更倾向于设计一种可以自行验证自己的输入控件而不是设计一个自行验证的表单。Validation的设计思想决定了使用这个框架很难去做一个可移植性强的带验证的输入控件。而这种设计思想更容易做出可移植性强的带验证功能的表单。

  11. 飞翔的柴禾
    *.*.*.*
    链接

    飞翔的柴禾 2009-12-04 15:38:00

    @Ivony
    在javascript中&&和||的运算结果并不一定是布尔值
    true && anything == anything
    false || anything == anything
    只是除0, -0, null, “”, false, undefined, NaN之外的值作为布尔值运算时都为true

    @老赵
    比如在lua中就经常有这样的写法:
    obj = obj or {}
    当obj == nil时给obj赋个默认值

  12. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-12-04 15:56:00

    Ivony...:
    但如果A为真,则还需要计算表达式B,确定A && B的值是否为真。但一般这个计算结果都会是真,则true || C === true,不计算表达式C。

    所以要达成if else的效果,有一个前提是B必须是恒真表达式。

    &&和||只能达成if else的效果,不能达成?:的效果。


    老赵指出的那行代码里,B就是恒真的……关键是当B不是null、undefined、NaN、0、''、false之类的假值时,它就是“真值”;{ url: param }是一个对象,自然总是真值。如果typeof param == 'string'为true,剩下的就是B || C,也就是取B的值;反之,前面是false,那么false || C就是C的值。这是很典型的trick

    老赵是用啥浏览器来测试的来着?

    Rhino 1.7 release 2 2009 03 22
    js> var param = 'foo'
    js> param = typeof param == 'string' && { url: param } || param
    [object Object]
    js> param.url
    foo
    js> param = typeof param == 'string' && { url: param } || param
    [object Object]
    js> param.url
    foo


    V8 version 2.0.0
    > var param = 'foo'
    > param = typeof param == 'string' && { url: param } || param
    [object Object]
    > param.url
    foo
    > param = typeof param == 'string' && { url: param } || param
    [object Object]
    > param.url
    foo


    Ruby里给变量赋默认值的惯用法就是:
    foo ||= bar


    wk,不停提交都提交不上去……

  13. Will Meng
    *.*.*.*
    链接

    Will Meng 2009-12-04 15:59:00

    老赵今天的思维跨度真大
    从clr一下跑到juqery上面了。。。

  14. 老赵
    admin
    链接

    老赵 2009-12-04 15:59:00

    @RednaxelaFX
    我用chrome。话说foo ||= bar这种之类的其实我也经常用,只是连用的就那个啥了……
    // 从昨晚开始博客园发评论就疯了

  15. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-12-04 16:04:00

    @Jeffrey Zhao
    Chrome用的就是V8,那应该不会有问题才对……但你修改了之后问题确实消失了是么?调试一下吧orz

  16. 老赵
    admin
    链接

    老赵 2009-12-04 16:04:00

    这个trick又读了一遍,发现还是挺容易理解的,好吧好吧,真浮躁亚……

  17. 老赵
    admin
    链接

    老赵 2009-12-04 16:12:00

    @RednaxelaFX
    这行代码里它没有问题,是我一开始没理解,自作主张换了个容易理解的作法,呵呵。

  18. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-12-04 16:17:00

    @Jeffrey Zhao
    但把代码写成这样的人实在是装13……写foo ||= bar是惯用法是因为它比foo = bar unless foo要短,但这帖里讨论的这行JavaScript代码拿来比较,

    if (typeof param == 'string') param = { url: param }

    param = typeof param == "string" && {url:param} || param

    明明写if更短些……要让我写的话这里肯定还是用if版了 =_=||||

  19. 老赵
    admin
    链接

    老赵 2009-12-04 16:40:00

    @RednaxelaFX
    话说,昨天那个profiler没法跟踪框架内部的方法,说不定是缺少pdb和source的原因……我试了ants profiler也是一个结果。

  20. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-12-04 16:51:00

    @Jeffrey Zhao
    Just My Code << 有没有类似这种选项?有的话应该不要勾上

  21. 老赵
    admin
    链接

    老赵 2009-12-04 17:17:00

    @RednaxelaFX
    找到的,也注意了,没效果……

  22. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-04 17:57:00

    飞翔的柴禾:
    @Ivony
    在javascript中&&和||的运算结果并不一定是布尔值
    true && anything == anything
    false || anything == anything
    只是除0, -0, null, “”, false, undefined, NaN之外的值作为布尔值运算时都为true

    @老赵
    比如在lua中就经常有这样的写法:
    obj = obj or {}
    当obj == nil时给obj赋个默认值




    受教了。。。

  23. DCBI
    *.*.*.*
    链接

    DCBI 2009-12-04 19:19:00

    这两天也下了一个jquery验证插件:formValidator3.5【】,用的时候 ,只是更改了js的引入位置,就老是不出效果:
    我写的时候位置:

           
            </link>
            <script src="formValidator.js" type="text/javascript" charset="UTF-8">
            </script>
    <script src="jquery_last.js" type="text/javascript">
            </script>
            <script src="formValidatorRegex.js" type="text/javascript" charset="UTF-8">
            </script>
     <link type="text/css" rel="stylesheet" href="validator.css">
    

    而本来正确的引入位置:
    <script src="jquery_last.js" type="text/javascript">
            </script>        
            <script src="formValidator.js" type="text/javascript" charset="UTF-8">
            </script>
            <script src="formValidatorRegex.js" type="text/javascript" charset="UTF-8">
            </script>
    <link type="text/css" rel="stylesheet" href="validator.css"></link>
    

    就是由于位置的错误,使得里面的jquery插件函数之间出现冲突,不能正确显示效果,调了半天,最后还是从头来,在aptana中写一个简单的调试通过才知道是这个错误

  24. 司徒正美
    *.*.*.*
    链接

    司徒正美 2009-12-05 09:35:00

    所说jQuery的垃圾插件很多,许多人以前没有接触过javascript,然后学了三个月jQuery就开始写插件了……

  25. andy.wu
    *.*.*.*
    链接

    andy.wu 2009-12-05 09:45:00

    司徒正美:所说jQuery的垃圾插件很多,许多人以前没有接触过javascript,然后学了三个月jQuery就开始写插件了……



    此言极是,我当年就是如此,不过我可没胆量公开,哈哈。

  26. _龙猫
    *.*.*.*
    链接

    _龙猫 2009-12-05 15:24:00

    至今我对这个插件的参数传入设定耿耿于怀,一点都没有面向对象。

  27. 老赵
    admin
    链接

    老赵 2009-12-05 15:36:00

    @_龙猫
    为啥非要面向对象啊,其实我觉得jQuery基本上就不是走那条路子的,它就想快速几行代码解决问题。

  28. 老赵
    admin
    链接

    老赵 2009-12-05 15:37:00

    司徒正美:所说jQuery的垃圾插件很多,许多人以前没有接触过javascript,然后学了三个月jQuery就开始写插件了……


    不过既然都上官网了,还有文档……总之让我大跌眼镜。

  29. chenleinet
    *.*.*.*
    链接

    chenleinet 2009-12-05 21:01:00

    'remote': {
    url: '/account/verify',
    data: {
    name: function() { return $("#userName").val(); }
    },能否告知,这里remote是如何使用的,本来想搜的,但不知道搜啥

  30. 老赵
    admin
    链接

    老赵 2009-12-05 21:28:00

    @chenleinet
    怎么会不知道搜啥,提取关键字啊

  31. 文超
    *.*.*.*
    链接

    文超 2009-12-06 01:47:00

    谁能给个更好用的验证插件呢?
    最好是……既然可以像ASP.NET那样有服务器端的支持,又能单独使用(纯JS的那种)。
    用法最好很简单,不用记的,查查手册就能用的那样子。

    对于 jQuery.Validation 这个还得写个 json 等等,键盘敲得多有点累,因为表单大的话就痛苦了。
    其实我就想使用方法简单点,就是找不到这样的插件。我的意思是不一定是正正经经写个 json 传给 validate(),能懒一点就好了。

    哎呀好期待有这么个东西。谁帮忙快点开发个出来用用吧。

  32. 阿牛
    *.*.*.*
    链接

    阿牛 2009-12-06 15:32:00

    我印象提交表单就是用Name呀, 脚本访问才用 ID

  33. 老赵
    admin
    链接

    老赵 2009-12-06 16:00:00

    @文超
    我搞过,写过文章,自己写一下也就1小时。

  34. _龙猫
    *.*.*.*
    链接

    _龙猫 2009-12-07 08:45:00

    @Jeffrey Zhao
    我指的不是jQuery本身,而是指这个插件的规则参数,硬生生要扒一个Message出来,真是气死人了

  35. 老赵
    admin
    链接

    老赵 2009-12-07 10:21:00

    @_龙猫
    message是错误信息,本就应该扒出来啊,你看.NET的资源信息也是独立的。如果你不独立扒出来的话,多语言就非常难搞了。

  36. _龙猫
    *.*.*.*
    链接

    _龙猫 2009-12-07 10:49:00

    @Jeffrey Zhao
    我始终认为规则还是写成:
    rules:{
    titleIn: {
    required: {val:true,msg:"请填写标题"}
    },
    countIn: {
    required:{val:true,msg:"请填写数量"}
    number:{val:true,msg:"请填写数字"}
    range: {val:[1, 30],msg:"数量范围在1-30之间"}
    }
    }
    这样为好.
    这个跟“多语言”有虾米关系?

  37. 老赵
    admin
    链接

    老赵 2009-12-07 10:52:00

    @_龙猫
    嗯,现在如果要求您把所有文字换成英文,那么你会怎么做呢?
    提取成独立对象的好处,就是可以通过引入一个资源文件来做到本地化,而那个资源文件里可能只需要一个json对象而已。

  38. _龙猫
    *.*.*.*
    链接

    _龙猫 2009-12-07 11:06:00

    @Jeffrey Zhao
    哈,我把“多语言”理解成了多种编程语言了

    如果是考虑到多国语言,我也会写成:

    rules:{
    titleIn: {
    required: {val:true,msg:valid-Msg.title.required}
    },
    countIn: {
    required:{val:true,msg:valid-Msg.count.required}
    number:{val:true,msg:valid-Msg.count.number}
    range: {val:[1, 30],msg:valid-Msg.count.range}
    }
    }
    至于valid-Msg,当然是写成可配置成不同语言的,这样就解决你说的问题了。

  39. _龙猫
    *.*.*.*
    链接

    _龙猫 2009-12-07 11:07:00

    也许您会觉得我在钻牛角尖,但是我主要是要说明个人的观点:参数传递结构化一些会带来很大的可读性

  40. 老赵
    admin
    链接

    老赵 2009-12-07 11:09:00

    @_龙猫
    但这就要求资源文件在这段定义之前引入,否则不行了。
    其实其它还好,但是文字资源我还是很支持独立抽出来的。

  41. _龙猫
    *.*.*.*
    链接

    _龙猫 2009-12-07 11:19:00

    @Jeffrey Zhao
    抽出来是更合理的,我只是在抱怨它抽出来的方式很暴力而已。 :D

  42. chenleinet
    *.*.*.*
    链接

    chenleinet 2009-12-11 22:49:00

    'remote': {
    url: '/account/verify',
    data: {
    name: function() { return $("#userName").val(); }
    }}}}});
    我还是没弄明白,这个‘remote’是如何与那个remote:function()匹配的,以及remote加单引号的用意,能不能给些提示,那个搜索的关键字,我也不知道是啥

  43. 老赵
    admin
    链接

    老赵 2009-12-12 01:04:00

    @chenleinet
    去看文档啊,我也是看文档的。

  44. 长天之云
    222.240.182.*
    链接

    长天之云 2010-08-11 15:47:31

    var data = {};
    data[element.name] = value;
    $.ajax($.extend(true, {
        data: data,
    }, param));
    

    从上面精简的三行来看,插件作者的本意就只是发送带有具体name属性表单域的名和值,而不是为了ASP.NET的什么DefaultModelBinder。

    实际上这里看起来最重要的是“$.extend(true,{},{})”方法,url被覆盖,remote.data被合并,这样的结果就是自定义的remote.data做为附加的参数被发送到服务器了。

    而如果改成“data: param.data || data”,原始表单域的名和值就被丢弃了,改动之后更像一个BUG。

  45. jianshao810
    183.26.117.*
    链接

    jianshao810 2010-09-04 11:47:58

    还是不能用,取到的值永远是第一次加载的值。

    <script type="text/javascript">
        $("#SubmitForm").validate({
            rules: {
                email: { required: true, remote: {
                    url: '/CheckEmail.aspx',
                    type: "post",
                    data: { email: $("#email").val() }
    
                }
                }
            },
            success: function(label) {
                label.addClass("valid2").text("通过");
            }
        });
    </script>
    

    这里email每次获取都是以开始的那个值.

  46. 老赵
    admin
    链接

    老赵 2010-09-05 18:03:12

    @jianshao810

    您的评论格式实在太差,不好意思我删除了……

  47. skysbird
    125.39.66.*
    链接

    skysbird 2012-01-29 15:29:07

    其实你这里说的bug,并不是bug,他的文档已经规定返回json格式了,你只要按照他说的返回json格式的字串即可。

  48. skysbird
    125.39.66.*
    链接

    skysbird 2012-01-29 15:30:21

    楼主,我发错地方了,不好意思

  49. 七星
    223.223.198.*
    链接

    七星 2014-04-30 13:53:32

    楼主,你自定义的参数是空,这是在页面加载时 data: {

    name: function() { return $("#userName").val(); }

    $("userName").val()为空

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我