From 1a11471cefafdaf2f2f2f745c796d3b35d096f70 Mon Sep 17 00:00:00 2001 From: Chris Chua Date: Sun, 15 Dec 2013 23:04:56 -0800 Subject: [PATCH] fix(modal): support close animation Tests now have to wait for animation before checking for open modals. Tests should actually check for the 'in' class. Setting up both the transition end handler and a timeout ensures that the modal is always closed even if there was an issue with the transition. This was the implementation used by Twitter Bootstrap modal JS code. Note that unbinding the transition end event within the event handler isn't necessary, and, worse is currently buggy in jqLite (see https://github.com/angular/angular.js/pull/5109 ). Note that the scope is already destroyed when the dom is removed so the $destroy call isn't needed. Closes #1341 --- src/modal/modal.js | 35 +++++++++++++++++++++++++++-------- src/modal/test/modal.spec.js | 3 +++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/modal/modal.js b/src/modal/modal.js index 12ddfa8ee4..18fa74cb5f 100644 --- a/src/modal/modal.js +++ b/src/modal/modal.js @@ -1,4 +1,4 @@ -angular.module('ui.bootstrap.modal', []) +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) /** * A helper, internal data structure that acts as a map but also allows getting / removing @@ -87,7 +87,8 @@ angular.module('ui.bootstrap.modal', []) return { restrict: 'EA', scope: { - index: '@' + index: '@', + animate: '=' }, replace: true, transclude: true, @@ -106,8 +107,8 @@ angular.module('ui.bootstrap.modal', []) }; }]) - .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap', - function ($document, $compile, $rootScope, $$stackedMap) { + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { var OPENED_MODAL_CLASS = 'modal-open'; @@ -140,17 +141,34 @@ angular.module('ui.bootstrap.modal', []) openedWindows.remove(modalInstance); //remove window DOM element - modalWindow.modalDomEl.remove(); + removeAfterAnimating(modalWindow.modalDomEl, modalWindow.modalScope); body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); //remove backdrop if no longer needed if (backdropDomEl && backdropIndex() == -1) { - backdropDomEl.remove(); + removeAfterAnimating(backdropDomEl, backdropScope); backdropDomEl = undefined; } + } + + function removeAfterAnimating(domEl, scope) { + // Closing animation + scope.animate = false; - //destroy scope - modalWindow.modalScope.$destroy(); + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(function () { + domEl.remove(); + }, 500); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + domEl.remove(); + }); + } else { + domEl.remove(); + } } $document.bind('keydown', function (evt) { @@ -186,6 +204,7 @@ angular.module('ui.bootstrap.modal', []) var angularDomEl = angular.element('
'); angularDomEl.attr('window-class', modal.windowClass); angularDomEl.attr('index', openedWindows.length() - 1); + angularDomEl.attr('animate', 'animate'); angularDomEl.html(modal.content); var modalDomEl = $compile(angularDomEl)(modal.scope); diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js index 697eec6861..85a0a2bf1c 100644 --- a/src/modal/test/modal.spec.js +++ b/src/modal/test/modal.spec.js @@ -104,6 +104,7 @@ describe('$modal', function () { function dismiss(modal, reason) { modal.dismiss(reason); + $timeout.flush(); $rootScope.$digest(); } @@ -144,6 +145,7 @@ describe('$modal', function () { expect($document).toHaveModalsOpen(1); triggerKeyDown($document, 27); + $timeout.flush(); $rootScope.$digest(); expect($document).toHaveModalsOpen(0); @@ -155,6 +157,7 @@ describe('$modal', function () { expect($document).toHaveModalsOpen(1); $document.find('body > div.modal-backdrop').click(); + $timeout.flush(); $rootScope.$digest(); expect($document).toHaveModalsOpen(0);