JQuery 验证-要求在一个组中至少填写一个字段

我正在使用优秀的 jQuery验证插件来验证一些表单。在一个表单上,我需要确保用户至少填写一组字段中的一个。我想我已经有了一个很好的解决方案,并想与大家分享。请提出你能想到的任何改进建议。

找不到内置的方法来做到这一点,我搜索并找到了 瑞贝卡 · 墨菲的定制验证方法,这是非常有帮助的。

我从三个方面改进了它:

  1. 让您传入字段组的选择器
  2. 让您指定要通过验证必须填充该组中的多少个
  3. 在组中的所有输入中,只要其中一个输入通过验证,就显示它们 验证。(参见最后对 Nick Craver的喊叫。)

你可以说 “必须填充至少与选择器 Y 匹配的 X 个输入”

使用这样的标记的最终结果是:

<input class="productinfo" name="partnumber">
<input class="productinfo" name="description">

是这样一组规则:

// Both these inputs input will validate if
// at least 1 input with class 'productinfo' is filled
partnumber: {
require_from_group: [1,".productinfo"]
}
description: {
require_from_group: [1,".productinfo"]
}

第3项假设在成功验证后向错误消息添加了 .checked类。你可以这样做,如下,就像这里展示的一样

success: function(label) {
label.html(" ").addClass("checked");
}

与上面链接的演示一样,我使用 CSS 为每个 span.error提供一个 X 图像作为背景,除非它具有类 .checked,在这种情况下,它会得到一个复选标记图像。

以下是我目前的代码:

jQuery.validator.addMethod("require_from_group", function(value, element, options) {
var numberRequired = options[0];
var selector = options[1];
//Look for our selector within the parent form
var validOrNot = $(selector, element.form).filter(function() {
// Each field is kept if it has a value
return $(this).val();
// Set to true if there are enough, else to false
}).length >= numberRequired;


// The elegent part - this element needs to check the others that match the
// selector, but we don't want to set off a feedback loop where each element
// has to check each other element. It would be like:
// Element 1: "I might be valid if you're valid. Are you?"
// Element 2: "Let's see. I might be valid if YOU'RE valid. Are you?"
// Element 1: "Let's see. I might be valid if YOU'RE valid. Are you?"
// ...etc, until we get a "too much recursion" error.
//
// So instead we
//  1) Flag all matching elements as 'currently being validated'
//  using jQuery's .data()
//  2) Re-run validation on each of them. Since the others are now
//     flagged as being in the process, they will skip this section,
//     and therefore won't turn around and validate everything else
//  3) Once that's done, we remove the 'currently being validated' flag
//     from all the elements
if(!$(element).data('being_validated')) {
var fields = $(selector, element.form);
fields.data('being_validated', true);
// .valid() means "validate using all applicable rules" (which
// includes this one)
fields.valid();
fields.data('being_validated', false);
}
return validOrNot;
// {0} below is the 0th item in the options field
}, jQuery.format("Please fill out at least {0} of these fields."));

万岁!

喊出来

现在对于这个大喊——最初,我的代码只是盲目地隐藏了其他匹配字段上的错误消息,而不是重新验证它们,这意味着如果存在其他问题(比如“只允许输入数字,而且你输入了字母”) ,它会被隐藏,直到用户试图提交。这是因为我不知道如何避免上面评论中提到的反馈循环。我知道一定有办法,所以 我问了一个问题Nick Craver启发了我。谢谢,尼克!

问题解决

这原本是一个“让我分享这一点,看看是否有人可以提出改进”的类型的问题。虽然我仍然欢迎反馈,但我认为在这一点上已经相当完整了。(它可以更短,但我希望它易于阅读,不一定要简洁。)好好享受吧!

更新-现在是 jQuery 验证的一部分

这是2012年4月3日的 正式添加到 jQuery 验证

74101 次浏览

Starting a variable name with $ is required in PHP, but pretty weird (IMHO) in Javascript. Also, I believe you refer to it as "$module" twice and "module" once, right? It seems that this code shouldn't work.

Also, I'm not sure if it's normal jQuery plugin syntax, but I might add comments above your addMethod call, explaining what you accomplish. Even with your text description above, it's hard to follow the code, because I'm not familiar with what fieldset, :filled, value, element, or selector refer to. Perhaps most of this is obvious to someone familiar with the Validate plugin, so use judgment about what is the right amount of explanation.

Perhaps you could break out a few vars to self-document the code; like,

var atLeastOneFilled = module.find(...).length > 0;
if (atLeastOneFilled) {
var stillMarkedWithErrors = module.find(...).next(...).not(...);
stillMarkedWithErrors.text("").addClass(...)

(assuming I did understand the meaning of these chunks of your code! :) )

I'm not exactly sure what "module" means, actually -- is there a more specific name you could give to this variable?

Nice code, overall!

That's an excellent solution Nathan. Thanks a lot.

Here's a way making the above code work, in case someone runs into trouble integrating it, like I did:

Code inside the additional-methods.js file:

jQuery.validator.addMethod("require_from_group", function(value, element, options) {
...// Nathan's code without any changes
}, jQuery.format("Please fill out at least {0} of these fields."));


// "filone" is the class we will use for the input elements at this example
jQuery.validator.addClassRules("fillone", {
require_from_group: [1,".fillone"]
});

Code inside the html file:

<input id="field1" class="fillone" type="text" value="" name="field1" />
<input id="field2" class="fillone" type="text" value="" name="field2" />
<input id="field3" class="fillone" type="text" value="" name="field3" />
<input id="field4" class="fillone" type="text" value="" name="field4" />

Don't forget to include additional-methods.js file!

Because the form I'm working on has several cloned regions with grouped inputs like these, I passed an extra argument to the require_from_group constructor, changing exactly one line of your addMethod function:

var commonParent = $(element).parents(options[2]);

and this way a selector, ID or element name can be passed once:

jQuery.validator.addClassRules("reqgrp", {require_from_group: [1, ".reqgrp", 'fieldset']});

and the validator will restrict the validation to elements with that class only inside each fieldset, rather than try to count all the .reqgrp classed elements in the form.

Thanks, Nathan. You saved me a ton of time.

However, i must notice that this rule isn't jQuery.noConflict() ready. So, one must replace all $ with jQuery to work with, say, var $j = jQuery.noConflict()

And i have question: how would i make it behave like built-in rule? For example, if i enter email, the message "Please enter valid email" disappears automatically but if i fill one of group fields error message stays.

Nice solution. However, I had the problem of other required rules not working. Executing .valid() against the form fixed this issue for me.

if(!$(element).data('being_validated')) {
var fields = $(selector, element.form);
fields.data('being_validated', true);
$(element.form).valid();
fields.data('being_validated', false);
}

Thanks sean. That fixed the issue I had with the code ignoring other rules.

I also made a few changes so that 'Please fill out at least 1 field ..' message shows in a separate div instead of after all every field.

put in form validate script

showErrors: function(errorMap, errorList){
$("#form_error").html("Please fill out at least 1 field before submitting.");
this.defaultShowErrors();
},

add this somewhere in the page

<div class="error" id="form_error"></div>

add to the require_from_group method addMethod function

 if(validOrNot){
$("#form_error").hide();
}else{
$("#form_error").show();
}
......
}, jQuery.format(" &nbsp;(!)"));

I was having problems with other rules not being checked in conjunction with this, so I changed:

fields.valid();

To this:

var validator = this;
fields.each(function(){
validator.valid(this);
});

I also made a few (personal) improvements, and this is the version I'm using:

jQuery.validator.addMethod("require_from_group", function(value, element, options){
var numberRequired = options[0],
selector = options[1],
$fields = $(selector, element.form),
validOrNot = $fields.filter(function() {
return $(this).val();
}).length >= numberRequired,
validator = this;
if(!$(element).data('being_validated')) {
$fields.data('being_validated', true).each(function(){
validator.valid(this);
}).data('being_validated', false);
}
return validOrNot;
}, jQuery.format("Please fill out at least {0} of these fields."));

Here's my crack at Rocket Hazmat's answer, trying to solve the issue of other defined fields also needing to be validated, but marking all fields as valid on successful filling of one.

jQuery.validator.addMethod("require_from_group", function(value, element, options){
var numberRequired = options[0],
selector = options[1],
$fields = $(selector, element.form),
validOrNot = $fields.filter(function() {
return $(this).val();
}).length >= numberRequired,
validator = this;
if(!$(element).data('being_validated')) {
$fields.data('being_validated', true).each(function(){
validator.valid(this);
}).data('being_validated', false);
}
if (validOrNot) {
$(selector).each(function() {
$(this).removeClass('error');
$('label.error[for='+$(this).attr('id')+']').remove();
});
}
return validOrNot;
}, jQuery.format("Please fill out at least {0} of these fields."));

The only remaining issue with this now is the edge case where the field is empty, then filled, then empty again... in which case the error will by applied to the single field not the group. But that seems so unlikely to happen with any frequency and it still technically works in that case.

I've submitted a patch that doesn't suffer from the issues the current version does (whereby the "required" option stops working properly on other fields, a discussion of the problems with the current version is on github.

Example at http://jsfiddle.net/f887W/10/

jQuery.validator.addMethod("require_from_group", function (value, element, options) {
var validator = this;
var minRequired = options[0];
var selector = options[1];
var validOrNot = jQuery(selector, element.form).filter(function () {
return validator.elementValue(this);
}).length >= minRequired;


// remove all events in namespace require_from_group
jQuery(selector, element.form).off('.require_from_group');


//add the required events to trigger revalidation if setting is enabled in the validator
if (this.settings.onkeyup) {
jQuery(selector, element.form).on({
'keyup.require_from_group': function (e) {
jQuery(selector, element.form).valid();
}
});
}


if (this.settings.onfocusin) {
jQuery(selector, element.form).on({
'focusin.require_from_group': function (e) {
jQuery(selector, element.form).valid();
}
});
}


if (this.settings.click) {
jQuery(selector, element.form).on({
'click.require_from_group': function (e) {
jQuery(selector, element.form).valid();
}
});
}


if (this.settings.onfocusout) {
jQuery(selector, element.form).on({
'focusout.require_from_group': function (e) {
jQuery(selector, element.form).valid();
}
});
}


return validOrNot;
}, jQuery.format("Please fill at least {0} of these fields."));