Search code examples
javascriptjquerytwitter-bootstraptwitter-bootstrap-3popover

Bootstrap Popup - Attach event to button for popover dynamically


I intend to use the Bootstrap Popover module as a confirmation for a delete event, such that when a <button> is clicked, instead of simply firing a function it opens a popup which let's the user confirm or dismiss the action.

It should be noted for context that the environment is a self-built, one-page ~mvc webapp, where the elements are added dynamically (I thus do not know the number of elements beforehand, nor can I hardcode the actions for each element)


In the current situation (without the popover) I do something similar to this to trigger the direct (non-conformation) action:

$view.on('click', '.deleteButton', function () {
    var id = $(this).attr('data-id');
    api.delete('someUrl' + id, successFunction);
}):

Where the button would have a markup similar to this:

<button class="deleteButton" data-id="2">Delete me!</button>

However, now I wish to add a confirmation step (e.g. "Are you sure you want to delete this magnificent button? (Y/N)"), and thought of using bootstrap popover.

After brainstorming a bit, what I came up with was a strategy like the following. It is not the optimal solution (the optimal solution would be to extend the popover module so that I would be able to pass in confirmationFunction and cancelFunction to the popover).

Firstly, I would have to initiate the popovers.

$view.find('.deleteButton').popover({
  title: 'Are you sure?', //Example title
  html: true, //Essential for the html to work as expected
  selector: '.deleteButton', //Allow ajaxing in of new elements
  container: '.viewName', //So that the popovers reside w/in the current view and thus can be bound to $view (a jQuery object for the view)
  content: [...], //Two <button>s with a specified class, e.g. .viewName__deleteButton--popover
});

Secondly, I would bind an event to the buttons inside the popover

$view.on('click', '.viewName__deleteButton--popover', function () {
    var id = ??; // Here comes the troubling part - how do I get the ID of the clicked button? How can I target the original button? If I can do that, I think it would solve everything.
    api.delete('someUrl' + id, successFunction);
});

The problem that arises is how do I target the originally clicked button?

The only soultion which I can come up with, which isn't very neat at all is to do something like this:

var popoverId = $(this).parents('.popover').attr('id'); //returns popoverXXXXXX
var parentElement = $view.find('[aria-describedBy="' + popoverId + '"]');

It works, but it is a very dirty solution and does not feel 'nice' att all.

Is there any way this can be done in a neater manner? Preferably I would be able to define a general function such as $element.confirmationPopover({popoverSettings...}, confirmationFunction, declineFunction); which could be used in multiple cases.

(PS: I couldn't figure out a concise title for this Q, advice is as always appreciated!)


Solution

  • I solved it by creating an extension to jQuery, such that:

    $.fn.confirmationPopover = function (passedInOptions) {
      var $view = this;
    
      var defaultOptions = {
        confirmationLabel: 'Yes',
        container: '.' + this.attr('class'),
        declineLabel: 'No',
        html: true,
        placement: 'left',  //top | left | right | bottom
        rowClasses: false
      };
    
      if (!passedInOptions.declineClass) {
        passedInOptions.declineClass = passedInOptions.selector + '__popover__decline';
      }
    
      var obj = {}; //Merged options
      $.extend(true, obj, defaultOptions, passedInOptions);
    
      var popoverContent = '<div class="row ' + (obj.rowClasses ? obj.rowClasses : 'tac') + '">' +
                            '<button class="button button--small ' + obj.confirmationClass + '">' + obj.confirmationLabel + '</button>' +
                            '<button class="button button--small button--outline ' + obj.declineClass + '">' + obj.declineLabel + '</button>' +
                          '</div>';
    
      if (!obj.content) {
        obj.content = popoverContent;
      }
    
      /* Initiate Popover */
      $view.popover(obj);
    
      /**
       * Bind confirmation Button
       */
      $view.on('click', '.' + obj.confirmationClass, function () {
        var popoverId = $(this).parents('.popover').attr('id');
        var $this = $view.find('[aria-describedBy="' + popoverId + '"]');
        obj.confirmationFunction($this);
      });
    
      /**
       * Bind decline button
       */
      $view.on('click', '.' + obj.declineClass, function () {
        var popoverId = $(this).parents('.popover').attr('id');
        var $this = $view.find('[aria-describedBy="' + popoverId + '"]');
        if (typeof obj.declineFunction === 'function') {
          obj.declineFunction($this);
        } else {
          $this.popover('hide');
        }
      });
    
      return this;
    };