Joyrex
Joyrex

Reputation: 1103

jQuery Validate, Select2, and Bootstrap 3 Popovers - How to Bind Popover To Select2's Parent Elements Instead of Hidden Select Element

I've implemented the excellent method to use Bootstrap 3's Popovers to show validation error messages, and for the most part it works well - except when it comes to hidden or replaced elements, like Select2 or CKEditor. The popover positions itself correctly on regular elements like input and normal selects, but not a select enhanced by Select 2.

I've created a Fiddle that Sparky fixed so it actually works: http://jsfiddle.net/jemxtthb/4/

$("#noticeSentTo").select2();

var validator = $("#documentAdmin").validate({
        debug: true,
        ignore: "",
        showErrors: function(errorMap, errorList) {
            $.each(this.successList, function(index, value) {
            return $(value).popover("hide").parents(".form-group").removeClass('has-error').addClass('has-success');
            });
            return $.each(errorList, function(index, value) {
            var _popover;
            _popover = $(value.element).popover({
                trigger: "manual",
                placement: "auto top",
                content: value.message,
                container: "body",
                template: "<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><div class=\"popover-content\"><p></p></div></div>"
            });  
            _popover.data("bs.popover").options.content = value.message;
            return $(value.element).popover("show").parents(".form-group").addClass('has-error').removeClass('has-success');
        });
        }
    });
});

Here's a screen capture of what I see within my application: Popover not positioned correctly

I've tried using the selector option on the popovers, but can't figure out how to specify the current element the popover's being applied to (if that's even the right way to do it).

As you can see from my screen capture, the popover appears up and to the far left of the element (in red) it's supposed to be bound to because Select2 is hiding the original select.

UPDATE: Based on @Sparky's suggestion, I've changed it to use errorPlacement instead of the method I used previously - the only issue now is getting the condition where if it's a hidden element (like Select2 or CKEditor) to provide the Popup to the parent object instead of the element itself (since Popovers cannot work on hidden elements, apparently) - I've put in some debug console.log code just to make sure the if statement in the jQuery is firing correctly:

var validator = $("#documentAdmin").validate({
    ignore: [],
    errorPlacement: function (error, element) {

        var lastError = $(element).data('lastError'),
                newError = $(error).text();

        $(element).data('lastError', newError);

        if (newError !== '' && newError !== lastError) {
                $(element).popover({
                    trigger: "manual",
                    placement: "auto top",
                    content: newError,
                    container: "body",
                    template: "<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><div class=\"popover-content\"><p></p></div></div>"
            });
             if (element.is(':hidden')) {
                    // $(element).next().parents(".form-group").addClass('has-error').removeClass('has-success').popover("show");
                    $(element).next('span').popover('show').addClass('has-error').removeClass('has-success');
                    console.log('hidden element');
             } else {
            $(element).popover("show").parents(".form-group").addClass('has-error').removeClass('has-success');
                console.log('normal element');
            }
        }
    },  
    success: function (label, element) {
        $(element).popover("hide").parents(".form-group").removeClass('has-error').addClass('has-success');
    }
});

And here's an updated Fiddle: http://jsfiddle.net/jemxtthb/6/

OK - really odd - the Fiddle above actually works as expected! Now I need to figure out why it works in the Fiddle and not my app...

Any help/guidance is appreciated!

Upvotes: 1

Views: 1750

Answers (2)

Joyrex
Joyrex

Reputation: 1103

As it turns out, the above code worked correctly from the beginning - after @Sparky fixed my Fiddle, the popover worked correctly, positioning itself in relation to the Select2's parent element.

What benefit came out of this was @Sparky's suggestion to use errorPlacement versus the method I was originally using, which gives more control over individual elements' error message placement methods.

I am currently investigating (and suspect) the culprit is an offcanvas plugin I'm using for a sidebar - I didn't include this in the Fiddle because I wanted to keep the Fiddle as concise as possible to the issue and not complicate things. I'll update this when I find out what the problem is. If anyone's interested in implementing this method for validation errors in Bootstrap 3 Popovers, I would use the code in the second Fiddle I posted.

Upvotes: 0

Sparky
Sparky

Reputation: 98748

Looking at the errors in the JavaScript console and fixing the following in your jsFiddle...

  1. There is no point in having a select element with validation if it comes pre-loaded with a value. In other words, there is nothing to validate if every option already contains a value or there is an option with a value that has the selected attribute. Typically, the first option contains value="".

    <option value="">Please select<option>
    
  2. There was a 404 error on your Bootstrap script file. The CDN URL was mangled.

  3. Not critical, but it should be ignore: [], not ignore: "".

  4. You're getting a "no 'name' assigned" error in the debug console because the hidden input element created by Select2 does not contain a name attribute. It's apparently not an issue now, but it could be an issue when you have more elements on the page. The jQuery Validate plugin mandates that all elements to be validated contain a name attribute.

DEMO: http://jsfiddle.net/jemxtthb/4/


for the most part it works well - except when it comes to hidden or replaced elements, like Select2 or CKEditor. The popover positions itself correctly on regular elements like input and normal selects, but not a select enhanced by Select 2.

You should not use showErrors for tooltips because this callback function is typically used for generating a list of all messages, like for a summary at the top of the form. "Gets the map of errors as the first argument and an array of errors as the second."

Instead, you'll need to integrate your tooltips plugin with jQuery Validate by using its errorPlacement and success callback functions; which will put the single pending error message inside a single tooltip. This integration also depends on the available options of your popovers plugin... like can you dynamically change the text inside a tooltip? Can you dynamically/programmatically show/hide them, etc?

Then within errorPlacement you can conditionally target certain elements to tweak the placement when the element is hidden by Select2, etc.

Something more like this...

$("#documentAdmin").validate({
    ignore: [],
    errorPlacement: function (error, element) {
        // put text of error into tooltip with $(error).text()
        // conditional placement of the tooltip
        // show the tooltip
    },
    success: function (label, element) {
        // hide the tooltip
    },
    // any other options
});

I'm using the ToolTipster jQuery plugin like this.

Upvotes: 1

Related Questions