如何在 jQuery 验证通知中使用 Bootstrap 弹出窗口?

我可以使用 鞋带轻松地显示弹出窗口,我也可以使用标准的 JQuery 验证插件JQuery 验证引擎进行验证,但是我不知道如何将其中一个插入到另一个中。

我想我需要的是一些钩子,当验证程序想要显示一个通知时,它会调用这些钩子,给它一个闭包,将消息和目标元素传递给 popover。这看起来像是一种依赖注入。

理论上都不错,但我就是想不出钩子在哪里,或者哪个验证引擎中是否存在钩子。他们似乎都愿意承担起显示通知的责任,提供各种复杂的布局、包装、样式选项,而我所追求的只是错误类型(我甚至不一定需要消息文本)和与之相关的元素。我找到了整个表单的钩子,而不是单独的通知。

我更喜欢使用类来定义规则的验证系统,因为它们可以很好地处理动态创建的表单。

有人有解决办法或者更好的主意吗?

83234 次浏览

Take a look at the highlight and showErrors jQuery Validator options, these will let you hook in your own custom error highlights that trigger Bootstrap popovers.

This is a hands-on example:

$('form').validate({
errorClass:'error',
validClass:'success',
errorElement:'span',
highlight: function (element, errorClass, validClass) {
$(element).parents("div[class='clearfix']").addClass(errorClass).removeClass(validClass);
},
unhighlight: function (element, errorClass, validClass) {
$(element).parents(".error").removeClass(errorClass).addClass(validClass);
}
});

enter image description here

It doesn't really use bootstrap popovers, but it looks really nice and is easy to achieve.

UPDATE

So, to have popover validation you can use this code:

$("form").validate({
rules : {
test : {
minlength: 3 ,
required: true
}
},
showErrors: function(errorMap, errorList) {
$.each(this.successList, function(index, value) {
return $(value).popover("hide");
});
return $.each(errorList, function(index, value) {
var _popover;
_popover = $(value.element).popover({
trigger: "manual",
placement: "top",
content: value.message,
template: "<div class=\"popover\"><div class=\"arrow\"></div><div class=\"popover-inner\"><div class=\"popover-content\"><p></p></div></div></div>"
});
// Bootstrap 3.x :
//_popover.data("bs.popover").options.content = value.message;
// Bootstrap 2.x :
_popover.data("popover").options.content = value.message;
return $(value.element).popover("show");
});
}
});

You get something like this:

enter image description here

Check out the jsFiddle.

I prefer to change the CSS of bootstrap. Just added the classes of jQuery validate in the right place. field-validation-error and input-validation-error

    form .clearfix.error > label, form .clearfix.error .help-block, form .clearfix.error .help-inline, .field-validation-error {
color: #b94a48;
}
form .clearfix.error input, form .clearfix.error textarea, .input-validation-error {
color: #b94a48;
border-color: #ee5f5b;
}
form .clearfix.error input:focus, form .clearfix.error textarea:focus, .input-validation-error:focus {
border-color: #e9322d;
-webkit-box-shadow: 0 0 6px #f8b9b7;
-moz-box-shadow: 0 0 6px #f8b9b7;
box-shadow: 0 0 6px #f8b9b7;
}

Chris Fulstow had it right, but it still took me a while, so heres the complete code:

This shows the popover on error, and hides the default error labels:

$('#login').validate({
highlight: function(element, errClass) {
$(element).popover('show');
},
unhighlight: function(element, errClass) {
$(element).popover('hide');
},
errorPlacement: function(err, element) {
err.hide();
}
}).form();

This sets up the popover. The only thing you need from this is trigger: 'manual'

$('#password').popover({
placement: 'below',
offset: 20,
trigger: 'manual'
});

The title and content attributes passed in to popover weren't working, so I specified them inline in my #password input with data-content='Minimum 5 characters' and data-original-title='Invalid Password'. You also need rel='popover' in your form.

This works, but the popover flickers upon unselecting. Any idea how to fix that?

This is how I made it happen. But it involves making 2 changes to the validate script (I got the code for bootstrap 1.4 here and then modified it - http://mihirchitnis.net/2012/01/customizing-error-messages-using-jquery-validate-plugin-for-twitter-bootstrap/)

My call to validate:

    $("#loginForm").validate({
errorClass: "control-group error",
validClass: "control-group success",
errorElement: "span", // class='help-inline'
highlight: function(element, errorClass, validClass) {
if (element.type === 'radio') {
this.findByName(element.name).parent("div").parent("div").removeClass(validClass).addClass(errorClass);
} else {
$(element).parent("div").parent("div").removeClass(validClass).addClass(errorClass);
}
},
unhighlight: function(element, errorClass, validClass) {
if (element.type === 'radio') {
this.findByName(element.name).parent("div").parent("div").removeClass(errorClass).addClass(validClass);
} else {
$(element).parent("div").parent("div").removeClass(errorClass).addClass(validClass);
}
}
});

Then you need to change 2 things in jquery.validate.js
1. apply this fix - https://github.com/bsrykt/jquery-validation/commit/6c3f53ee00d8862bd4ee89bb627de5a53a7ed20a
2. After line 647 (in the showLabel function, create label part) after line .addClass(this.settings.errorClass) add line: .addClass("help-inline")
Someone can maybe find a way to apply the second fix in the validate function, but I havent found a way, since showLabel is called after highlight.

This is what I put in my validate to conform to the Twitter Bootstrap guidelines. The error validation message is put in a <span class=help-inline> and we want to highlight the outer container as an error or success:

errorClass:'help-inline',
errorElement:'span',
highlight: function (element, errorClass, validClass) {
$(element).parents("div.clearfix").addClass('error').removeClass('success');
},
unhighlight: function (element, errorClass, validClass) {
$(element).parents(".error").removeClass('error').addClass('success');
}

This is how I did it with Bootstrap 2.x and jQuery Validate 1.9

$('#form-register').validate({ errorElement: 'span', errorClass:'help-inline', highlight:    function (element, errorClass) {
$(element).parent().parent().addClass('error');
}, unhighlight: function (element, errorClass) {
$(element).parent().parent().removeClass('error');
}});

This jQuery extension for jQuery Validation Plugin (tested with version 1.9.0) will do the trick.

https://github.com/tonycoco/rails_template/blob/master/files/assets/javascripts/jquery.validate.bootstrap.js

This also adds in some Rails-esk error messaging.

Please take a look at the following:
- https://gist.github.com/3030983
I think it's the simplest of all.

EDIT

Code from link:

$('form').validate({
rules: {
numero: {
required: true
},
descricao: {
minlength: 3,
email: true,
required: true
}
},


showErrors: function (errorMap, errorList) {


$.each(this.successList, function (index, value) {
$(value).popover('hide');
});




$.each(errorList, function (index, value) {


console.log(value.message);


var _popover = $(value.element).popover({
trigger: 'manual',
placement: 'top',
content: value.message,
template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><div class="popover-content"><p></p></div></div></div>'
});


_popover.data('popover').options.content = value.message;


$(value.element).popover('show');


});


}


});

Here's a follow up to the excellent suggestion from Varun Singh which prevents the "flicker" issue of the validation constantly trying to "show" even though the popup is already present. I've simply added an error states array to capture which elements are showing errors and which aren't. Works like a charm!

var errorStates = [];


$('#LoginForm').validate({
errorClass:'error',
validClass:'success',
errorElement:'span',
highlight: function (element, errorClass) {
if($.inArray(element, errorStates) == -1){
errorStates[errorStates.length] = element;
$(element).popover('show');
}
},
unhighlight: function (element, errorClass, validClass) {
if($.inArray(element, errorStates) != -1){
this.errorStates = $.grep(errorStates, function(value) {
return value != errorStates;
});
$(element).popover('hide');
}
},
errorPlacement: function(err, element) {
err.hide();
}
});


$('#Login_unique_identifier').popover({
placement: 'right',
offset: 20,
trigger: 'manual'
});


$('#Login_password').popover({
placement: 'right',
offset: 20,
trigger: 'manual'
});

Many thanks for the heads up! Here is my version for Bootstrap but with Tooltips. In my opinion it's more elegant than popovers. I know the question was for popovers so please do not vote down for this reason. Maybe somebody will like it this way. I love when I'm searching for something and I found new ideas on Stackoverflow. Note: no markup on form is necessary.

    $('#LoginForm').validate({
rules: {
password: {
required: true,
minlength: 6
},


email_address: {
required: true,
email: true
}
},
messages: {
password: {
required: "Password is required",
minlength: "Minimum length is 6 characters"
},
email_address: {
required: "Email address is required",
email: "Email address is not valid"
}
},
submitHandler: function(form) {
form.submit();
},


showErrors: function (errorMap, errorList) {


$.each(this.successList, function (index, value) {
$('#'+value.id+'').tooltip('destroy');
});




$.each(errorList, function (index, value) {


$('#'+value.element.id+'').attr('title',value.message).tooltip({
placement: 'bottom',
trigger: 'manual',
delay: { show: 500, hide: 5000 }
}).tooltip('show');


});


}


});

Not sure if this is relevant to the discussion because the original poster asked for hooks to show/hide bootstrap popovers.

I was looking for simple validation and popovers didn't matter. A related post and the first in google search results has already been marked duplicate of this question. So it made sense to mention this excellent @ReactiveRaven's jqValidation JS, aptly called jqBootstrapValidation, that weds well with Twitter Bootstrap. Setup takes a few minutes only. Download here.

Hope this adds value.

tl;dr avoid needing to enumerate explicit popovers by using a hash map to store the ids of the elements, and creating popovers on-the-fly (mashup Jeffrey Gilbert and Kenny Meyer's approaches).

Here's my take, which fixes the flickering problem mentioned by others, but unlike @Jeffrey Gilbert's answer, does not use a list (errorStates) but rather uses an error map. Hash maps FTW. I think I remember reading somewhere that every problem in CS can be solved with a hash map :)

var err_map = new Object();     // <--- n.b.
$("form#set_draws").validate({
rules: {
myinput: { required: true, number: true },
},
showErrors: function(errorMap, errorList) {
$.each(this.successList, function(index, value) {
if (value.id in err_map)
{
var k = err_map[value.id];
delete err_map[value.id]; // so validation can transition between valid/invalid states
k.popover("hide");
}
});
return $.each(errorList, function(index, value) {
var element = $(value.element);
if( ! (value.element.id in err_map) ) {
var _popover = element.popover({
trigger: "manual",
placement: "top",
content: value.message,
template: "<div class=\"popover\"><div class=\"arrow\"></div><div class=\"popover-inner\"><div class=\"popover-content\"><p></p></div></div></div>"
});
_popover.data("popover").options.content = value.message;
err_map[value.element.id] = _popover;
return err_map[value.element.id].popover("show");
}
});
}
});

Thanks to all others who posted ideas on this.

If using the above Kenny Meyer code for popups, beware that rules that check a field's content but isn't required such as a valid URL will cause the popup to not disappear upon clearing the field. See below onkeyup for solution. If anyone has a better solution, please post.

onkeyup: function(element, event) {
if($(element).valid())  {
return $(element).popover("hide");
}
}

Checkout this: https://github.com/mingliangfeng/jquery.validate.bootstrap.popover

It shows how to use Bootstrap popover css, instead of JS. JS method popup will cause blinking issue.

Here is an update to Kenny Meyer's excellent answer above. There were a couple of issues that prevented it from working for me, which I have addressed in this snippet:

showErrors: function (errorMap, errorList) {
$.each(this.successList, function (index, element) {
return $(element).popover("destroy");
});


$.each(errorList, function (index, error) {
var ele = $(error.element); //Instead of referencing the popover directly, I use the element that is the target for the popover


ele.popover({
trigger: "manual",
placement: "top",
content: function(){ //use a function to assign the error message to content
return error.message
},
template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><div class="popover-content"><p></p></div></div></div>'
});


//bs.popover must be used, not just popover
ele.data("bs.popover").options.content = error.message;


return $(error.element).popover("show");
});
}