Search code examples
javascriptjquerytwitter-bootstrapfullcalendarpopover

Random duplicate content bug with Bootstrap popovers and FullCalendar


Note: I understand there's a bug in bootstrap that causes popovers/tooltips to call the content function() twice, but I do not think that is the case here.

I am using bootstrap popovers in conjunction with the FullCalendar gem to update events.

Inside the popover, there're multiple divs that get hidden/shown depending on which view the user is on (i.e. there is a view for displaying the event info and another for updating the view). The way I'm handling this is by using addClass('hide') and removeClass('hide'), so correct me if this is improper.

When I click on an event, it displays the popover with the content added it to once (good). When I navigate to the update div inside the popover and send an .ajax({type: 'PUT'}), the popover for the updated event displays two of the content divs (bad); however, this happens randomly.

If I reload the entire DOM and redo the process over and over again, this duplication happens ~half the time.

When popover is clicked just after DOM loads (good)

before updating

When popover is clicked after ajax PUT is called, this SOMETIMES HAPPENS (even if no fields are changed)

after updating

Here's how I send the .ajax request:

  // SAVE button for LESSONS, POPOVER ***/
  $('body').on('click', 'button.popover-lesson-save', function() {

    var title = $('.popover input.popover-lesson-title').val();
    var note = $('.popover input.popover-lesson-note').val();

    // Retrieve event data from popover
    var idStr = $('.popover div.popover-body').data('id');
    var id = parseInt(idStr);

    // Store event data into a hash for AJAX request and refetches events
    var data = {
      title: title,
      note: note
    };

    // Make PUT AJAX request to database
    $.ajax({
      url: '/lessons/' + id,
      type: 'PUT',
      data: data,
      dataType: 'json',

      success: function(resp){
        $('.popover div.popover-lessons-edit').addClass('hide');
        $('.popover div.popover-lessons-view').removeClass('hide');
        $('.popover').popover('hide');
        $('#calendar').fullCalendar( 'refetchEvents' );
      },
      error: function(resp){
        alert('Error code 2-502-1. Oops, something went wrong.');
      }

    });
  });

And here is what I do in my FullCalendar callbacks:

  // calendar options
  $('#calendar').fullCalendar({
    ..
    eventRender: function(calEvent, element, view){

        if (typeof $(element).data('bs.popover') === 'undefined'){
          // Find content html (append and clone prevents parent div from being overwritten)
          var content = $("<div />").append($('.popover-lessons-content').clone());
          var id = calEvent.id;

          // Attach popover to html element
          $(element).popover({
            container: 'body',
            placement: 'left',
            html: true,
            content: function(){
              return content.html(); // This randomly duplicates
            }
          });
        }
     }
  }

Solution

  • Thanks to @c-smile, I've realised that the bug lied in how I retrieve my html template from my view file:

    var content = $("<div />").append($('.popover-lessons-content').clone());
    

    Instead, I should have done this (and this worked after much tinkering):

          var content = $('.popover-lessons-content').html();
    
          $(element).popover({
            container: 'body',
            placement: 'left',
            html: true,
            content: function(){
              return content;
            }
          });
    

    It takes the direct html instead of cloning the div and making a big mess.