65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使我们的应用程序适配 jQuery Validation 插件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (6投票s)

2011年6月11日

CPOL

6分钟阅读

viewsIcon

28157

downloadIcon

257

本文旨在让读者了解 jQuery Validation 插件的内部工作原理,以及我们如何从应用程序中扩展验证规则。

引言

我一直在研究 jQuery validation 插件代码(Jquery.validate.js),发现它非常有趣且有用。我想,如果我在这里分享我的发现,对于那些希望了解 jQuery validation 框架工作原理的人来说,将会很有益。本文包含了 jQuery validation 插件工作原理的内部细节,并解释了我们如何扩展默认规则以提供我们自己的验证方法。

背景

我有点好奇 jQuery 是如何通过在 class 属性上应用简单的类规则,如 required emailrequired url 等,神奇地验证输入控件的。于是我花了好几个小时来理解Jqquery.validate.js 是如何由专家编写的。

使用代码

为了研究 jQuery validation 的工作原理,我创建了一个简单的 HTML 页面,并在上面添加了一些控件。我还应用了一些(现有的)规则来观察验证效果。下面的代码显示了validation.html 的代码。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
                    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <script src="jquery-1.4.1.js"></script>
  <script type="text/javascript" src="jquery.validate.js"></script>
<style type="text/css">
* { font-family: Verdana; font-size: 96%; }
label { width: 10em; float: left; }
label.error { float: none; color: red; padding-left: .5em; vertical-align: top; }
p { clear: both; }
.submit { margin-left: 12em; }
em { font-weight: bold; padding-right: 1em; vertical-align: top; }
.class1 {color:red}
</style>
  <script>
      $.extend($.validator, { cool: function () { } });
      $(document).ready(function () {
          $("#commentForm").validate();
          $.validator.addMethod("PinCode", function () { }, 
                                "PIN code is not in valid format");
      });
  </script>
  
</head>
<body>
  
<form class="cmxform" id="commentForm" method="get" action="">
<fieldset>
   <legend>A simple comment form with submit validation and default messages</legend>
   <p>
     <label for="cname">Name</label>
     <em>*</em><input id="cname" name="name" 
          size="25" class="class1 required"  minlength="2" />
   </p>
   <p>
     <label for="cemail">E-Mail</label>
     <em>*</em><input id="cemail" name="email" 
          size="25"  class="required email" />
   </p>
   <p>
     <label for="curl">URL</label>
     <em>  </em><input id="curl" name="url" 
         size="25"  class="required url" value="" />
   </p>
   <p>
     <label for="ccomment">Your comment</label>
     <em>*</em><textarea id="ccomment" name="comment" 
         cols="22"  class="required"></textarea>
   </p>
   <p>
     <input class="submit" type="submit" value="Submit"/>
   </p>
 </fieldset>
 </form>
</body>
</html>

工作原理

在上面的 HTML 文件中,应用的类有 requiredrequired emailrequired url 等。对输入元素的这些最小的更改应该足以使它们成为 jQuery validation 插件框架的一部分。

jQuery validation 框架的核心是 jQuery validator ($.validator),它包含了处理元素验证工作所需的所有默认设置。jQuery validator 带有默认设置,当我们调用表单上的 validate() 方法时,validate() 方法会将 options 作为输入参数,并返回 validator。我们可以随时发送自己的 options,它们将扩展 jQuery 的默认验证设置。下面是 validator 最初自带的默认设置。

以下是 validtor 的默认设置

messages: {},
groups: {},
rules: {},
errorClass: "error",
validClass: "valid",
errorElement: "label",
focusInvalid: true,
errorContainer: $( [] ),
errorLabelContainer: $( [] ),
onsubmit: true,
ignore: [],
ignoreTitle: false,
onfocusin: function(element) {
    this.lastActive = element;
        
    // hide error label and remove error class on focus if enabled
    if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
        this.settings.unhighlight && this.settings.unhighlight.call( 
              this, element, this.settings.errorClass, this.settings.validClass );
        this.errorsFor(element).hide();
    }
},
onfocusout: function(element) {
    if ( !this.checkable(element) && 
            (element.name in this.submitted || !this.optional(element)) ) {
        this.element(element);
    }
},
onkeyup: function(element) {
    if ( element.name in this.submitted || element == this.lastElement ) {
        this.element(element);
    }
},
onclick: function(element) {
    // click on selects, radiobuttons and checkboxes
    if ( element.name in this.submitted )
        this.element(element);
    // or option elements, check parent select in that case
    else if (element.parentNode.name in this.submitted)
        this.element(element.parentNode)
},
highlight: function( element, errorClass, validClass ) {
    $(element).addClass(errorClass).removeClass(validClass);
},
unhighlight: function( element, errorClass, validClass ) {
    $(element).removeClass(errorClass).addClass(validClass);
}

我们可以在本地页面上覆盖任何这些设置。例如,以 onfocusout 事件为例;我想执行与框架提供的逻辑不同的逻辑。在我们的本地页面中,我们可以做一些如下的事情。

$.validator.defaults.onfocusout = function(element)
{
    //your custom code,
},

label 将用于显示错误消息;'error' 有自己的 class,我们可以在我们的应用程序中覆盖这个 class 并提供我们自己的自定义样式,这与我们在上面的示例中所做的有些类似。

label { width: 10em; float: left; }
label.error { float: none; color: red; padding-left: .5em; vertical-align: top; }

规则如何应用以及验证如何进行

jQuery validator 有一个 rules 对象 ($.validator.settings.rules)。jQuery 中的一个rule 将一个元素与一个验证方法相关联,而一个method 定义了如何验证元素的验证过程。框架规则分为四个级别:

  • 类级别
  • 属性级别
  • 元规则
  • 静态规则

例如,这里是一些框架中定义的 classRuleSettings

classRuleSettings: {
    required: {required: true},
    email: {email: true},
    url: {url: true},
    date: {date: true},
    dateISO: {dateISO: true},
    dateDE: {dateDE: true},
    number: {number: true},
    numberDE: {numberDE: true},
    digits: {digits: true},
    creditcard: {creditcard: true}
},

元素的规则准备

为了获取应用于元素的规则,jQuery 首先获取 class 属性的值;例如,考虑我们的示例电子邮件元素。

  1. 它的 class 值为 "required email"。
  2. 它使用分隔符 ' ' 来分割 class 属性返回的值;在本例中,我们得到两个值:requiredemail
  3. 对于每个值(requiredemail),它会从 classRuleSettings 中获取映射,并为该元素准备验证规则。这里的验证规则是 {required:true,email:true}。对于规则中的每个值,如 requiredemail 等,框架在 $.validator.Methods 中定义了方法。一系列方法描述了如何根据分配给元素的规则来验证元素。
  4. 以下是一些框架验证器方法:MinLengthMaxLengthrangelengthminmaxrangeurldatedateISOnumberdigitscreditcard 等。

  5. 在创建了类级别的规则之后,它会遍历输入元素的其他属性(如 minlengthmaxlength)等,并准备像上面 {required:true, minlength=2} 这样的规则对象,并将两者合并。对于元规则和静态规则,其工作方式类似。
  6. 一旦规则准备就绪,它就会遍历验证方法($.validator.Methods)列表,并根据规则关联调用相应的方法。在上面的例子中,将调用三个验证方法:requiredemailminlength<input name="cname" id="cname" class="required email" minlength=2 />)。下面是验证框架中方法的代码样子。
  7. $.validator.Methods{
    
      required: function(value, element, param) {
            // check if dependency is met
            if ( !this.depend(param, element) )
                return "dependency-mismatch";
            switch( element.nodeName.toLowerCase() ) {
            case 'select':
                // could be an array for select-multiple or a string,
                // both are fine this way
                var val = $(element).val();
                return val && val.length > 0;
            case 'input':
                if ( this.checkable(element) )
                    return this.getLength(value, element) > 0;
            default:
                return $.trim(value).length > 0;
            }
        },
      email: function(value, element) {
            // contributed by Scott Gonzalez:
            //   http://projects.scottsplayground.com/email_address_validation/
            return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-
                   \/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+
                   (\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\u
                   FDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+
                   )?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|
                   [\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x
                   7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x
                   0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-
                   \uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-
                   \uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\u
                   FFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+((
                   [a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\u
                   D7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\u
                   F900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\u
                   FDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
        },
    }

如果框架提供的验证方法不够用怎么办?

如何添加我们自己的验证机制/方法?嗯,框架提供了一个名为 $.validator.addMethod(name, method, Message) 的方法,它将一个新的验证方法添加到 Methods 列表中。其中,name 是验证方法的名称,method 是验证过程,Message 是验证失败的消息。在上面的示例中,我添加了一个 pin code 验证方法,它不是框架的一部分。为简单起见,该方法始终返回 false 以触发验证,然后将显示验证消息。

框架为每种验证失败都有默认的消息设置;例如,当 required 验证失败时,它会显示“This field is required”(此字段为必填项),如下面的示例所示。

messages: {
    required: "This field is required.",
    remote: "Please fix this field.",
    email: "Please enter a valid email address.",
    url: "Please enter a valid URL.",
    date: "Please enter a valid date.",
    dateISO: "Please enter a valid date (ISO).",
    number: "Please enter a valid number.",
    digits: "Please enter only digits.",
    creditcard: "Please enter a valid credit card number.",
    equalTo: "Please enter the same value again.",
    accept: "Please enter a value with a valid extension.",
    maxlength: $.validator.format("Please enter no more than {0} characters."),
    minlength: $.validator.format("Please enter at least {0} characters."),
    rangelength: $.validator.format(
                   "Please enter a value between {0} and {1} characters long."),
    range: $.validator.format("Please enter a value between {0} and {1}."),
    max: $.validator.format("Please enter a value less than or equal to {0}."),
    min: $.validator.format("Please enter a value greater than or equal to {0}.")
},

如果我们想显示我们自己的自定义消息,那么我们可以通过以下方式在我们的本地 Web 应用程序中覆盖每条消息。

$.validator.messages.required ="My Custom message";

当我们点击 Submit 按钮时,下面的代码将被触发。

this.submit( function( event ) {
    if ( validator.settings.debug )
        // prevent form submit to be able to see console output
        event.preventDefault();
        
    function handle() {
        if ( validator.settings.submitHandler ) {
            if (validator.submitButton) {
                // insert a hidden input as a replacement
                // for the missing submit button
                var hidden = $("<input type="hidden" />").attr(
                  "name", validator.submitButton.name).val(
                  validator.submitButton.value).appendTo(validator.currentForm);
            }
            validator.settings.submitHandler.call( validator, validator.currentForm );
            if (validator.submitButton) {
                // and clean up afterwards;
                // thanks to no-block-scope, hidden can be referenced
                hidden.remove();
            }
            return false;
        }
        return true;
    }
                    
    // prevent submit for invalid forms or custom submit handlers
    if ( validator.cancelSubmit ) {
        validator.cancelSubmit = false;
        return handle();
    }
    if ( validator.form() ) {
        if ( validator.pendingRequest ) {
            validator.formSubmitted = true;
            return false;
        }
        return handle();
    } else {
        validator.focusInvalid();
        return false;
    }
}

在上面的代码中,validator.form() 会逐个元素进行表单验证并累积错误列表,最后 $.validator.ShowErrors() 方法会在页面上显示错误。

我们如何进行条件验证?

请记住,在调用框架 validate()(它返回 validator)时,我们可以将 options 作为输入参数传递。这里我编写了一个依赖函数处理程序来执行条件必填检查。这里我正在对 URL 应用一个条件验证,即仅当 email 的值为空时,URL 才为必填(当然,这种依赖关系没有意义,但这只是为了演示 options 输入参数)。

$("#commentForm").validate(
{
  rules:
  {
      url:
      {
          required: function () {
              return $("#cemail").val().length === 0;
          }
    }
  }
}
);

在上面的代码中,只有当 email 为空时 URL 才为必填;在内部,jQuery 使用 extend() 将我们自己提供的自定义规则与 validator 的默认设置进行扩展。下面展示了代码设置如何与我们作为输入参数发送的默认选项进行扩展。

$.validator = function( options, form ) {
    this.settings = $.extend( {}, $.validator.defaults, options );
    this.currentForm = form;
    this.init();
};

关注点

  1. jQuery validation 框架隐藏了许多复杂的逻辑,并为用户提供了以简单方式指定规则的灵活性。
  2. 它是高度可扩展的,我们可以覆盖现有的规则或添加新的规则/验证方法。
  3. 我们可以让不同的团队编写自己的验证方法并将它们集成到现有框架中,这样我们就可以分离关注点。
  4. 错误消息可以根据页面进行本地化自定义。

免责声明

本文使用的示例不适用于直接生产部署。目的是让读者了解 jQuery 的工作原理以及我们如何在本地 Web 应用程序中使用它。个人有责任在实现 Model Binding 时遵循必要的最佳实践。

© . All rights reserved.