Pergunta

I am using Twitter Bootstrap modals, with the default options where you can click the backdrop or press [Esc] to close the modal.

However, when I initiate an ajax operation in the modal I want to disable the modal from being closed in any way. So I disable buttons and hide the modal's close button but I can't figure out how to disable the backdrop and the [Esc] key.

I tried:

$('#myModal').modal({
    backdrop: 'static',
    keyboard: false
});

But this doesn't seem to work on the fly.

I will also need to re-enable the backdrop and keyboard once the ajax operation is finished.

Foi útil?

Solução

Note: This solution is targeting twitter bootstrap 2.x! See this answer (just below) for differences according to bootstrap 3.


Extending bootstrap modal functionality without modifying original source.

Thanks to @David and his suggestion at How to Extend Twitter Bootstrap Plugin I finally got it to work. It is a slightly modified version of his solution with modal "lock" added. I post it as a additional answer since I think it may could be a starting point for others that like me have struggled hard with this issue.

// save the original function object
var _superModal = $.fn.modal;

// add locked as a new option
$.extend( _superModal.defaults, {
    locked: false
});

// create a new constructor
var Modal = function(element, options) {
    _superModal.Constructor.apply( this, arguments )
}

// extend prototype and add a super function
Modal.prototype = $.extend({}, _superModal.Constructor.prototype, {
    constructor: Modal

    , _super: function() {
        var args = $.makeArray(arguments)
        // call bootstrap core
        _superModal.Constructor.prototype[args.shift()].apply(this, args)
    }

    , lock : function() {
        this.options.locked = true
    }

    , unlock : function() {
        this.options.locked = false
    }

    , hide: function() {
        if (this.options.locked) return
        this._super('hide')
    }
});

// override the old initialization with the new constructor
$.fn.modal = $.extend(function(option) {
    var args = $.makeArray(arguments),
    option = args.shift()

    // this is executed everytime element.modal() is called
    return this.each(function() {
        var $this = $(this)
        var data = $this.data('modal'),
            options = $.extend({}, _superModal.defaults, $this.data(), typeof option == 'object' && option)

        if (!data) {
            $this.data('modal', (data = new Modal(this, options)))
        }
        if (typeof option == 'string') {
            data[option].apply( data, args )
        }
    });
}, $.fn.modal);

With this technique it should not be nessecary to alter bootstrap.js, and the same functionality can more easily be shared among bootstrap projects. This method should be applicable to all the other bootstrap plugins. Have so far only tried with button though, but I cant se why it shouldnt.

see working fiddle -> http://jsfiddle.net/Sz7ZS/

Outras dicas

There is an easier way to do it. This bootstrap pull request explains a little more. The solution disables all methods for closing the modal (keyboard, mouse click, close button).

All you have to do to disable closing the modal is:

$('#myModal').data('bs.modal').isShown = false;

To enable closing again:

$('#myModal').data('bs.modal').isShown = true;

Example

Here is some sample code that works in conjunction with a jQuery get:

// disable closing the modal
$('#myModal').data('bs.modal').isShown = false;

// Send an HTTP GET request to the server - replace this with getJSON, post or ajax as needed
$.get( "ajax/test.html", function( data ) {
  // enable closing the modal
  $('#myModal').data('bs.modal').isShown = true;

  // Do something with the data
  $( ".result" ).html( data );
});

You are not the only one who are missing that feature. I think bootstrap sometimes is too "minimalistic", the people behind has the idea a lot should be done in the "implementation layer", but it is to no use when the bootstrap jQuery plugins themselves makes it impossible!

You have to implement the functionality yourself, like this :

in bootstrap.js v2.1.1 modal begins at line 61.

in Modal.prototype, add two functions, lock and unlock, so it looks like this (I show here only the beginning of modal.prototype, becuase it is too much code)

  Modal.prototype = {

      constructor: Modal

      //add this function
    , lock: function () {
        this.options.locked = true
      }

      //add this function
    , unlock: function () {
        this.options.locked = false
      }

    , toggle: function () {
    ... 
    ...

Then, also in Modal.prototype, find the function hide, and add a line so it looks like this (again, only top of hide is showed)

, hide: function (e) {
    e && e.preventDefault()

    var that = this

    //add this line
    if (that.options.locked) return

    e = $.Event('hide')
    ...
    ...

And finally, alter $.fn.modal.defaults to :

  $.fn.modal.defaults = {
      backdrop: true
    , keyboard: true
    , show: true
    , locked: false //this line is added
  }

Now you have on-the-fly lock/unlock functionality in your bootstrap modal, preventing the user from closing the modal at critical moments.

Example :

This is an altered version of "Live Demo" from http://twitter.github.com/bootstrap/javascript.html#modals

<!-- Button to trigger modal -->
<a href="#myModal" role="button" class="btn" data-toggle="modal">Launch demo modal</a>

<!-- Modal -->
<div id="myModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
    <h3 id="myModalLabel">Modal header</h3>
  </div>
  <div class="modal-body">
    <p>One fine body…</p>
  </div>
  <div class="modal-footer">
    <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
    <button class="btn btn-primary" onclick="$('#myModal').modal('lock');">lock</button>
    <button class="btn btn-primary" onclick="$('#myModal').modal('unlock');">unLock</button>
  </div>
</div>
<script type="text/javascript">

I have inserted two buttons, "lock" and "unlock" - when clicked, they set the modal in either locked or normal mode (the settings it is initialised with)

Edit, in your case, you just have to call lock/onlock when doing ajax :

$("myModal").modal('lock');
$.ajax({
    url: url,
    ...
    ...
    , success(html) {
       ...
       ...
       $("#myModal").modal('unlock');
    }
});

You can create a variable isBlocked that you can set while doing AJAX calls to true, then you can check it on bootsrap modal hide event this way:

$('#myModal').on('hide.bs.modal', function (e) {
      //Prevent modal from closing is isBlocked is true
      if(isBlocked) return e.preventDefault();
})

I think this way is easier than extending Bootsrap, hope it helps someone :D

Thanks @davidkonrad for your work on this. I was trying to implement this as well and it seems that things have changed with bootstrap 3. Now instead of:

_superModal.defaults

it is now attached to the constructor so you have to do

_superModal.Constructor.DEFAULTS

Also the constructor has changed which meant that I had to copy it and modify it which is less than ideal. Instead I've come up with the following code that works and does not copy the constructor and should be more fool-proof if bootstrap changes going forward. Have a go:

// save the original function object
var _superModal = $.fn.modal;

// add locked as a new option
$.extend( _superModal.Constructor.DEFAULTS, {
    locked: false
});

// capture the original hide
var _hide = _superModal.Constructor.prototype.hide;
// console.log('HIDE:', _hide);

// add the lock, unlock and override the hide of modal
$.extend(_superModal.Constructor.prototype, {
    // locks the dialog so that it cannot be hidden
    lock: function() {
        // console.log('lock called');
        // console.log('OPTIONS',this.options);
        this.options.locked = true;
    }
    // unlocks the dialog so that it can be hidden by 'esc' or clicking on the backdrop (if not static)
    ,unlock: function() {
        // console.log('unlock called');
        this.options.locked = false;
    }
    // override the original hide so that the original is only called if the modal is unlocked
    ,hide: function() {
        // console.log('hide called');
        if (this.options.locked) return;

        _hide.apply(this, arguments);
    }
});

So in order to lock the modal:

$('#dlg').modal('lock');

and to unlock:

$('#dlg').modal('unlock');

Yay!

I updated this awesome script to work with Bootstrap 4.

var _superModal = $.fn.modal;

    $.extend(_superModal.Constructor.Default, {
        locked: false
    });

    var _hide = _superModal.Constructor.prototype.hide;

    $.extend(_superModal.Constructor.prototype, {
        lock: function () {
            this._config.locked = true;
        },
        unlock: function () {
            this._config.locked = false;
        },
        hide: function () {
            if (this._config.locked) return;

            _hide.apply(this, arguments);
        }
    });
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top