Mathumatix
Mathumatix

Reputation: 93

Showing Bootstrap modal dialogs in sequence

I have a Bootstrap modal dialog, which when Yes is selected returns a resolved promise. Upon the promise being resolved, the modal should be displayed again. The line to display the modal is hit, but the modal does not display. What am I doing wrong?

$(function() {
  showModalDialog('Confirm1', "Select Yes or No", 'Yes', 'No')
    .done(function() {
      alert('You selected Yes once!');
      showModalDialog('Confirm2', "Select Yes or No", 'Yes', 'No')
        .done(function() {
          alert('You selected Yes twice!');
        });
    });
});

function showModalDialog(title, message, button1Caption, button2Caption) {
  var deferred = $.Deferred();
  $('#modalTitle').html(title);
  $('#modalMessage').html(message);
  $('#modalButton1').html(button1Caption);
  $('#modalButton2').html(button2Caption);
  $('#modalButton1').one('click', function() {
    deferred.resolve();
  });
  $('#modalButton2').one('click', function() {
    deferred.reject();
  });
  $('#modalDialog').one('hidden.bs.modal', function() {
    //remove the handler for the button in case it was never invoked, otherwise it will
    //still be there the next time the dialog is shown
    $('#modalButton1').off('click');
    deferred.reject();
  })
  $('#modalDialog').modal();
  return deferred.promise();
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>


<div class="modal fade" id="modalDialog" tabindex="-1" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">

      <div class="modal-header">
        <!--<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>-->
        <h4 class="modal-title" id="modalTitle"></h4>
      </div>

      <div class="modal-body" id="modalMessage"></div>

      <div class="modal-footer">
        <button type="button" id="modalButton1" class="btn btn-default" data-dismiss="modal"></button>
        <button type="button" id="modalButton2" class="btn btn-default" data-dismiss="modal"></button>
      </div>
    </div>
  </div>
</div>

Upvotes: 0

Views: 2168

Answers (1)

Nisarg Shah
Nisarg Shah

Reputation: 14541

Blame the animations! :)

The animation of the hiding modal takes some finite time to complete. If you try to "show" the modal again during this time, it will not work. A simple workaround is to delay the "show" action slightly.

For example, delaying it by one second will work just fine:

setTimeout(function() {
    showModalDialog('Confirm2', "Select Yes or No", 'Yes', 'No')
      .done(function() {
        alert('You selected Yes twice!');
      });
}, 1000);

$(function() {
  showModalDialog('Confirm1', "Select Yes or No", 'Yes', 'No')
    .done(function() {
      alert('You selected Yes once!');
      
      setTimeout(function() {
        showModalDialog('Confirm2', "Select Yes or No", 'Yes', 'No')
          .done(function() {
            alert('You selected Yes twice!');
          });
      }, 1000);
    });
});

function showModalDialog(title, message, button1Caption, button2Caption) {
  var deferred = $.Deferred();
  $('#modalTitle').html(title);
  $('#modalMessage').html(message);
  $('#modalButton1').html(button1Caption);
  $('#modalButton2').html(button2Caption);
  $('#modalButton1').one('click', function() {
    deferred.resolve();
  });
  $('#modalButton2').one('click', function() {
    deferred.reject();
  });
  $('#modalDialog').one('hidden.bs.modal', function() {
    //remove the handler for the button in case it was never invoked, otherwise it will
    //still be there the next time the dialog is shown
    $('#modalButton1').off('click');
    deferred.reject();
  })
  $('#modalDialog').modal();
  return deferred.promise();
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>


<div class="modal fade" id="modalDialog" tabindex="-1" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">

      <div class="modal-header">
        <!--<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>-->
        <h4 class="modal-title" id="modalTitle"></h4>
      </div>

      <div class="modal-body" id="modalMessage"></div>

      <div class="modal-footer">
        <button type="button" id="modalButton1" class="btn btn-default" data-dismiss="modal"></button>
        <button type="button" id="modalButton2" class="btn btn-default" data-dismiss="modal"></button>
      </div>
    </div>
  </div>
</div>

Note: I haven't checked the source code or experimented with these modals enough to know the minimum amount of delay required to make this work. But I don't mind a delay of one second anyway as it is barely noticeable.

Update

As you suggest, waiting for Bootstrap's hidden.bs.modal event is a better in this case. As described in the v3 Docs here:

This event is fired when the modal has finished being hidden from the user (will wait for CSS transitions to complete).

So moving the promise.resolved to the event handler for hidden.bs.modal would also do the trick.

To do that, I've added a variable modalResult which keeps track of user's choice, and triggers deferred.resolve or deferred.reject accordingly.

$(function() {
  showModalDialog('Confirm1', "Select Yes or No", 'Yes', 'No')
    .done(function() {
      alert('You selected Yes once!');
      
      showModalDialog('Confirm2', "Select Yes or No", 'Yes', 'No')
        .done(function() {
          alert('You selected Yes twice!');
        });
    });
});

function showModalDialog(title, message, button1Caption, button2Caption) {
  var modalResult = false;

  var deferred = $.Deferred();
  $('#modalTitle').html(title);
  $('#modalMessage').html(message);
  $('#modalButton1').html(button1Caption);
  $('#modalButton2').html(button2Caption);
  $('#modalButton1').one('click', function() {
    // Wait for the modal to get hidden.
    // deferred.resolve();
    modalResult = true;
  });
  $('#modalButton2').one('click', function() {
    // Wait for the modal to get hidden.
    // deferred.reject();
    modalResult = false;
  });
  $('#modalDialog').one('hidden.bs.modal', function() {
    //remove the handler for the button in case it was never invoked, otherwise it will
    //still be there the next time the dialog is shown
    $('#modalButton1').off('click');
    
    if(modalResult) {
      deferred.resolve();
    } else {
      deferred.reject();
    }
  })
  $('#modalDialog').modal();
  return deferred.promise();
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>


<div class="modal fade" id="modalDialog" tabindex="-1" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">

      <div class="modal-header">
        <!--<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>-->
        <h4 class="modal-title" id="modalTitle"></h4>
      </div>

      <div class="modal-body" id="modalMessage"></div>

      <div class="modal-footer">
        <button type="button" id="modalButton1" class="btn btn-default" data-dismiss="modal"></button>
        <button type="button" id="modalButton2" class="btn btn-default" data-dismiss="modal"></button>
      </div>
    </div>
  </div>
</div>

Upvotes: 4

Related Questions