Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

$modal: Cannot disable backdrop/keyboard after modal has been instantiated #967

Closed
georgiosd opened this issue Sep 8, 2013 · 13 comments
Closed

Comments

@georgiosd
Copy link

In a common modal with a submit action, we need to be able to change whether the modal can be closed by clicking on the backdrop or by hitting ESC.

example case:

  • User clicks submit and is waiting for response from server (there is even an activity indicator)
  • User hits ESC by mistake or clicks on backdrop because he gets bored, modal goes poof
@georgiosd
Copy link
Author

Also, the $modalProvider.options variable is unused and therefore it's not possible to configure the defaults.

This has no effect:

$modalProvider.options = {
            backdrop: 'static',
            keyboard: false          
        };

@pkozlowski-opensource
Copy link
Member

@georgiosd blocking ability to close modal is an interesting use case. I don't think I've got a nice solution to this right now, probably something like AngularJS does with route evens (that you can cancel) would work. But yeh, it is en interesting use-case that we should support this way or another.

As for the issue with default options - could you provide a plunker with a minimal reproduce scenario? We've got tests for this functionality so I would be very, very surprised if it is broken.

@georgiosd
Copy link
Author

Hm...

What about allowing to pass a function to keyboard/backdrop properties?
Or replicate the properties in modalInstance?

Re the options: I didn't explain myself fully. You use your own internal reference to the defaults which means that this will work:

$modalProvider.options.keyboard = true;

but this won't:

$modalProvider.options = {
            backdrop: 'static',
            keyboard: false          
        };
    var defaultOptions = {
      backdrop: true, //can be also false or 'static'
      keyboard: true
    };

    return {
      options: defaultOptions, // options is not used anywhere, defaultOptions is
...
}

@pkozlowski-opensource
Copy link
Member

Woops, actually 8e7fbf0 covers only part of the problem (default options), didn't mean to close.

@georgiosd
Copy link
Author

Another thought would be to allow binding the properties in the scope! Now that scope is supported :)

@ProLoser
Copy link
Member

@pkozlowski-opensource perhaps:

var modal = $modal.open( ... );
modal.options( { ... } );
// or
modal.options( 'optionName', newValue);

Might be a non-trivial thing to implement.

@mrextreme
Copy link

Any news on this?

@Narretz
Copy link
Contributor

Narretz commented Jun 19, 2014

You can do this since forever with the $modalStack: Inject it in the controller, then do something like $modalStack.getTop().value.keyboard = false; Not the nicest way, but it works.

@lucassus
Copy link

I believe I've found quite elegant workaround for this issue. We could decorate $modal and $modalStack services with some extra methods for temporary disabling $modalStack.dismiss method:

app.config(function($provide) {

  $provide.decorator('$modal', function($delegate) {
    var open = $delegate.open;

    // decorate newly created modalInstance with some custom methods
    $delegate.open = function() {
      var modalInstance = open.apply(this, arguments);

      modalInstance.freeze = function(freeze) {
        modalInstance._freezed = freeze;
      };

      // return true when the modal instance is freezed and
      // dismiss reason is 'backdrop click' or 'escape key press'
      modalInstance.freezed = function(reason) {
        if (!modalInstance._freezed) { return false; }
        return _.contains(['backdrop click', 'escape key press'], reason);
      };

      return modalInstance;
    };

    return $delegate;
  });

  $provide.decorator('$modalStack', function($delegate) {
    var dismiss = $delegate.dismiss;

    // do nothing when the modal is freezed
    // otherwise fallback to the old behaviour
    $delegate.dismiss = function(modalInstance, reason) {
      if (modalInstance.freezed(reason)) { return; }
      dismiss.apply(this, arguments);
    };

    return $delegate;
  });

});

...and in the modal's controller we could use $modalInstance.freeze method to temporary block backdrop click or esc key press:

var FormCtrl = function($scope, $modalInstance, list) {
  this.$modalInstance = $modalInstance;
  this.list = list;
};

angular.extend(FormCtrl.prototype, {

  submit: function(list) {
    // create/update new record
    var promise = list.isPersisted() ? list.$update() : list.$create();

    // freeze the modal keyboard/backdrop close until request is completed
    this.$modalInstance.freeze(true);
    promise.finally(angular.bind(this, function() {
      this.$modalInstance.freeze(false);
    }));

    // close the modal and resolve it with updated/created record
    promise.then(angular.bind(this, function(list) {
      this.$modalInstance.close(list);
    }));

    return promise;
  },

  cancel: function() {
    this.$modalInstance.dismiss();
  }

});

BONUS. Also we could use this solution for creating a directive for freezing modals when the embedded form was changed.

app.directive('glFreezeModal', function($parse) {
  return {
    restrict: 'A',
    require: 'form',

    link: function(scope, element, attrs, form) {
      var modalInstance = $parse(attrs.glFreezeModal)(scope);

      scope.$watch(function() { return form.$dirty; }, function(dirty) {
        modalInstance.freeze(dirty);
      });
    }
  }
});

This simple directive will prevent a user from accidentally lost his changes by pressing esc key etc.
When the form is modified the dialog could be closed only with the Cancel button.

The complete code is here https://gist.github.com/lucassus/9e7bbb89a630f3390b2f

@getvega
Copy link

getvega commented Oct 29, 2014

Great solution @lucassus, works perfectly.

@chrisirhc chrisirhc added this to the Purgatory milestone Nov 3, 2014
@wesleycho
Copy link
Contributor

I am closing this as I do not think this is an appropriate feature to move into this library. One can always work around this by having a modal controller with a close method that checks a condition before allowing the user to close the modal, which is the appropriate approach.

@thesaxonedone
Copy link

thesaxonedone commented Sep 5, 2017

@wesleycho Can we re-open this? As it stands, the 'backdrop' and 'keyboard' properties can only be specified when the modal is instantiated and @georgiosd use-case is reasonable and probably frequently utilized (disable escaping the modal via esc key or clicking outside the modal while data retrieval is in process).

Hitting escape on your keyboard or clicking outside the modal do not invoke (at least natively) any controller function and instead make calls to $dismiss and $close in the modalStack object, so your solution to have a "controller with a close method that checks a condition before allowing the user to close the modal" doesn't seem applicable unless I am misunderstanding.

As it stands the only solution that works is the decorator solution @lucassus provided, and I think its a reasonable request to be able to modify these properties within the modal itself, rather than only at insantiation time.

@abdullahmaswadeh
Copy link

$('#myModal').data('bs.modal')._isShown = false;
Just worked perfect for me on Bootstrap 4.1

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests