How to use Twitter Bootstrap popovers for jQuery validation notifications?
JqueryValidationTwitter BootstrapPopoverJquery Problem Overview
I can make popovers appear using bootstrap easily enough, and I can also do validations using the standard jQuery validation plugin or the jQuery validation engine, but I can't figure out how to feed one into the other.
I think what I need is some hook which is called by the validator when it wants to display a notification, give it a closure that passes the message and the target element to a popover. This seems like a kind of dependency injection.
All nice in theory, but I just can't figure out where that hook is, or even if one exists in either validation engine. They both seem intent on taking responsibility for displaying notifications with all kinds of elaborate options for placement, wrappers, styles when all I'm after is the error type(s) (I don't necessarily even need message text) and element it relates to. I've found hooks for the entire form, not the individual notifications.
I much prefer validation systems that use classes to define rules, as they play nicely with dynamically created forms.
Anyone have a solution or a better idea?
Jquery Solutions
Solution 1 - Jquery
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);
}
});
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:
Check out the jsFiddle.
Solution 2 - Jquery
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.
Solution 3 - Jquery
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?
Solution 4 - Jquery
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'
});
Solution 5 - Jquery
This jQuery extension for jQuery Validation Plugin (tested with version 1.9.0) will do the trick.
This also adds in some Rails-esk error messaging.
Solution 6 - Jquery
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;
}
Solution 7 - Jquery
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');
}});
Solution 8 - Jquery
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');
});
}
});
Solution 9 - Jquery
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');
});
}
});
Solution 10 - Jquery
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
- apply this fix - https://github.com/bsrykt/jquery-validation/commit/6c3f53ee00d8862bd4ee89bb627de5a53a7ed20a
- 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.
Solution 11 - Jquery
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');
}
Solution 12 - Jquery
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");
});
}
Solution 13 - Jquery
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.
Solution 14 - Jquery
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.
Solution 15 - Jquery
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.
Solution 16 - Jquery
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");
}
}