Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bootstrap 4 - Open modal A; Close Modal A; Open Modal B - A not closing

I have jobs site. User fills out a form and selects CV (resume) file from PC, hits the Send button. This calls a ajaxSubmit() function which does local validation (removed from the code below) and if OK, does an AJAX file upload. The server side does further validation and returns SUCCESS or ERROR (plus error text) in JSON.

When the user hits the Send button I open a modal #apply_working_modal, which just shows a spinner icon. On ajax.done, I close #apply_working_modal, and if there were errors, put them on the body of modal #apply_errors_modal and open it.

The first time I try it, with an error on purpose, it works as described. If I try it again, with the error still in place, #apply_working_modal shows and will not close until I manually click the X or away from it.

It seems to be some timing issue because when you re-submit after an error, the CV file is already on the server so the JSON comes back very fast. In fact, If I throttle the network just a bit I can make it work.

I have tried variations of waiting on modal events, and also setTimeout() to delay opening of #apply_errors_modal

function ajaxSubmit(whichForm){

    $("#apply_working_modal").modal("show"); 
    console.log('showing apply_working_modal');

    var applyRequest =$.ajax({
        url: postUrl,
        etc: etc
    });


    applyRequest.done(function( data, textStatus, jqXHR ) {

        console.log(data);

        $("#apply_working_modal").modal("hide");

        console.log("hiding #apply_working_modal ");

        if (data.STATUS =='SUCCESS') {
            // validation OK at server
        }
        else {// validation error at server

            $('#apply_working_modal').on('hidden.bs.modal', function () {
                $("#apply_errors_modal_body").html(data.ErrorText); 
                $("#apply_errors_modal").modal("show");
                console.log("on hidden of #apply_working_modal");
            })
        }               
    });

}

This is the output of the console for good/bad tests:

//Expected behaviour


15:58:45.981 myscript.js:179 showing apply_working_modal

// 10 seconds pass while server uploads file and returns errors in JSON
15:58:56.696 myscript.js:206 {STATUS: "ERROR", ErrorText: "* The Comments are too short."}
15:58:56.700 myscript.js:217 hiding #apply_working_modal 
15:58:56.998 myscript.js:236 on hidden of #apply_working_modal


// Problem behaviour

15:59:26.704 myscript.js:179 showing apply_working_modal
15:59:26.723 myscript.js:206 {STATUS: "ERROR", ErrorText: "* The Comments are too short."}
15:59:26.724 myscript.js:217 hiding #apply_working_modal 


// #apply_working_modal never gets hidden, have to manually close it
// manual dismiss of modal after waitinf 13 secs ....


15:59:38.525 myscript.js:236 on hidden of #apply_working_modal
15:59:38.526 myscript.js:236 on hidden of #apply_working_modal


// error modal shown but logged many times? 

The HTML:

 <!-- apply working (spinner while sending cv) -->
  <div id="apply_working_modal" class="modal fade" role="dialog">
    <div class="modal-dialog">
      <div class="modal-content">
       <div class="modal-header"> 
          <h4  class="modal-title"><i class="fa fa-hourglass-end"></i><span class="sr-only">Loading...</span> Sending your CV</h4>
          <button type="button" class="close" data-dismiss="modal"><i class="fa fa-times-circle-o" aria-hidden="true"></i></button>
        </div>
        <div class="modal-body text-center">
          <i class="fa fa-spinner fa-spin fa-5x fa-fw"></i><span class="sr-only">Loading...</span>
        </div>
        <div class="modal-footer">
            &nbsp;
        </div>
      </div>
    </div>
  </div>



 <!-- apply errors modal-->
  <div id="apply_errors_modal" class="modal fade" role="dialog">
    <div class="modal-dialog">

      <!-- Modal content-->
      <div class="modal-content">

       <div class="modal-header">
          <h4 style='color:#cc0000;' class="modal-title"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> There were errors!</h4>
          <button type="button" class="close" data-dismiss="modal"><i class="fa fa-times-circle-o" aria-hidden="true"></i></button>
        </div>

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

        <div class="modal-footer">
            <button data-dismiss="modal" type="button" style="float:left;" class="btn btn-outline-info">Cancel</button>
        </div>

      </div>
    </div>
  </div>

Update based on answer/comments below.

For the sake of clarity while posting the question I simplified the ajax request as I did not think it was the cause - in my code I used 'etc:etc' as an ajax parameter, this was to indicate I simplified the code. I am indeed using cache:false. Full call below (postUrl is set earlier in code):

var applyRequest =$.ajax({
    url: postUrl,
    data: postData,
    type: "POST",
    xhr: function() {  // Custom XMLHttpRequest
        var myXhr = $.ajaxSettings.xhr();
        if(myXhr.upload){ // Check if upload property exists
            myXhr.upload.addEventListener('progress',progressHandler, false); // For handling the progress of the upload
        }
        return myXhr;
    },

    enctype: 'multipart/form-data',
    cache: false,
    processData: false,  // tell jQuery not to process the data (reqd for use with FormData)
    contentType: false,   // tell jQuery not to set contentType
    dataType: "json"
    });
like image 989
KevInSol Avatar asked Aug 01 '18 15:08

KevInSol


People also ask

How do I stop a Bootstrap modal from closing?

If you want to prevent boostrap modal from closing by those actions, you need to change the value of backdrop and keyboard configuration. The default value of backdrop and keyboard is set to true . You can change the configuration in HTML or Javascript directly.

How do I open and close a Bootstrap modal?

There are few ways to close modal in Bootstrap: click on a backdrop, close icon, or close button. You can also use JavaScript hide method. Click the button to launch the modal. Then click on the backdrop, close icon or close button to close the modal.

How do you stop a modal from closing?

You can prevent closing of modal dialog by setting the beforeClose event argument cancel value to true. In the following sample, the dialog is closed when you enter the username value with minimum 4 characters.

How do I close a Bootstrap modal?

Clicking on the modal “backdrop” will automatically close the modal. Bootstrap only supports one modal window at a time.


1 Answers

You're right about the timing, where the modal will not be hidden if the backdrop's animation hasn't completed yet. See https://getbootstrap.com/docs/4.1/getting-started/javascript/#asynchronous-functions-and-transitions

Asynchronous methods and transitions

All API methods are asynchronous and start a transition. They return to the caller as soon as the transition is started but before it ends. In addition, a method call on a transitioning component will be ignored.

— The above quote was taken from https://getbootstrap.com/docs/4.1/components/modal/#events

(Updated answer — I removed the cache: false stuff.)

So try hiding the modal like this:

// Wait for the modal to be made fully visible, where CSS transitions have completed.
$('#apply_working_modal').one('shown.bs.modal', function(){
    $(this).modal("hide");
});

See the description of the shown.bs.modal for more information.

Or use setTimeout() like this:

setTimeout(function(){
    $('#apply_working_modal').modal("hide");
}, 1000);

(Updated again) The above (shown.bs.modal) works, but in my tests, only if cache is true. Most likely because the shown.bs.modal has already been triggered before the .done() is triggered. So try doing it this way:

(Another update) Sorry, I myself forgot to use the .one().. and secondly, I combined this with the setTimeout() method so that the modal hiding isn't too soon..

(Aug 15th 2018) I added the missing piece — $("#apply_working_modal").modal("show"); (just in this answer; thanks @KevinSol for pointing it out!).

function ajaxSubmit(whichForm){

    var modal_shown = false;
    $('#apply_working_modal').one('shown.bs.modal', function(){
        modal_shown = true;
    });

    $("#apply_working_modal").modal("show");

    ...

    applyRequest.done(function( data, textStatus, jqXHR ) {

        ...

        // If the modal has been *fully* visible, simply hides it.
        if (modal_shown) {
            $('#apply_working_modal').modal("hide");
        // Else, wait until it's fully visible, and then hides it.
        } else {
            $('#apply_working_modal').one('shown.bs.modal', function(){
                // Wait a few ms before we hide the modal.
                setTimeout(function(){
                    $('#apply_working_modal').modal("hide");
                }, 100);

                modal_shown = true;
            });
        }

        ...
    });

}

And here's a the updated demo for the shown.bs.modal event, and another demo for the setTimeout() method.

Additional Note

Here you should use .one() instead of the .on():

//$('#apply_working_modal').on('hidden.bs.modal' // before
$('#apply_working_modal').one('hidden.bs.modal'  // correct

to prevent the event handler from being triggered multiple times:

enter image description here

like image 196
Sally CJ Avatar answered Oct 13 '22 05:10

Sally CJ