diff --git a/src/modal/docs/demo.js b/src/modal/docs/demo.js index 4e1823c72f..07fba73d75 100644 --- a/src/modal/docs/demo.js +++ b/src/modal/docs/demo.js @@ -1,4 +1,4 @@ -angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $modal, $log) { +angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $uibModal, $log) { $scope.items = ['item1', 'item2', 'item3']; @@ -6,7 +6,7 @@ angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope $scope.open = function (size) { - var modalInstance = $modal.open({ + var modalInstance = $uibModal.open({ animation: $scope.animationsEnabled, templateUrl: 'myModalContent.html', controller: 'ModalInstanceCtrl', @@ -32,7 +32,7 @@ angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope }); // Please note that $modalInstance represents a modal window (instance) dependency. -// It is not the same as the $modal service used above. +// It is not the same as the $uibModal service used above. angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $modalInstance, items) { diff --git a/src/modal/docs/readme.md b/src/modal/docs/readme.md index fc3b085bbf..1bf72279b5 100644 --- a/src/modal/docs/readme.md +++ b/src/modal/docs/readme.md @@ -1,11 +1,11 @@ -`$modal` is a service to quickly create AngularJS-powered modal windows. +`$uibModal` is a service to quickly create AngularJS-powered modal windows. Creating custom modals is straightforward: create a partial view, its controller and reference them when using the service. -The `$modal` service has only one method: `open(options)` where available options are like follows: +The `$uibModal` service has only one method: `open(options)` where available options are like follows: * `templateUrl` - a path to a template representing modal's content * `template` - inline template representing the modal's content -* `scope` - a scope instance to be used for the modal's content (actually the `$modal` service is going to create a child scope of a provided scope). Defaults to `$rootScope` +* `scope` - a scope instance to be used for the modal's content (actually the `$uibModal` service is going to create a child scope of a provided scope). Defaults to `$rootScope` * `controller` - a controller for a modal instance - it can initialize scope used by modal. Accepts the "controller-as" syntax in the form 'SomeCtrl as myctrl'; can be injected with `$modalInstance` * `controllerAs` - an alternative to the controller-as syntax, matching the API of directive definitions. Requires the `controller` option to be provided as well * `bindToController` - when used with `controllerAs` & set to `true`, it will bind the $scope properties onto the controller directly @@ -20,7 +20,7 @@ The `$modal` service has only one method: `open(options)` where available option * `size` - optional suffix of modal window class. The value used is appended to the `modal-` class, i.e. a value of `sm` gives `modal-sm` * `openedClass` - class added to the `body` element when the modal is opened. Defaults to `modal-open` -Global defaults may be set for `$modal` via `$modalProvider.options`. +Global defaults may be set for `$uibModal` via `$uibModalProvider.options`. The `open` method returns a modal instance, an object with the following properties: diff --git a/src/modal/modal.js b/src/modal/modal.js index 85d159dafd..b75b50294e 100644 --- a/src/modal/modal.js +++ b/src/modal/modal.js @@ -57,8 +57,8 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) /** * A helper directive for the $modal service. It creates a backdrop element. */ - .directive('modalBackdrop', [ - '$animate', '$injector', '$modalStack', + .directive('uibModalBackdrop', [ + '$animate', '$injector', '$uibModalStack', function($animate , $injector, $modalStack) { var $animateCss = null; @@ -100,8 +100,8 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) } }]) - .directive('modalWindow', [ - '$modalStack', '$q', '$animate', '$injector', + .directive('uibModalWindow', [ + '$uibModalStack', '$q', '$animate', '$injector', function($modalStack , $q , $animate, $injector) { var $animateCss = null; @@ -203,18 +203,18 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) }; }]) - .directive('modalAnimationClass', [ + .directive('uibModalAnimationClass', [ function () { return { compile: function(tElement, tAttrs) { if (tAttrs.modalAnimation) { - tElement.addClass(tAttrs.modalAnimationClass); + tElement.addClass(tAttrs.uibModalAnimationClass); } } }; }]) - .directive('modalTransclude', function() { + .directive('uibModalTransclude', function() { return { link: function($scope, $element, $attrs, controller, $transclude) { $transclude($scope.$parent, function(clone) { @@ -225,7 +225,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) }; }) - .factory('$modalStack', [ + .factory('$uibModalStack', [ '$animate', '$timeout', '$document', '$compile', '$rootScope', '$q', '$injector', @@ -424,7 +424,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) if (currBackdropIndex >= 0 && !backdropDomEl) { backdropScope = $rootScope.$new(true); backdropScope.index = currBackdropIndex; - var angularBackgroundDomEl = angular.element('
'); + var angularBackgroundDomEl = angular.element('
'); angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); if (modal.animation) { angularBackgroundDomEl.attr('modal-animation', 'true'); @@ -433,7 +433,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) body.append(backdropDomEl); } - var angularDomEl = angular.element('
'); + var angularDomEl = angular.element('
'); angularDomEl.attr({ 'template-url': modal.windowTemplateUrl, 'window-class': modal.windowClass, @@ -547,14 +547,14 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) return $modalStack; }]) - .provider('$modal', function() { + .provider('$uibModal', function() { var $modalProvider = { options: { animation: true, backdrop: true, //can also be false or 'static' keyboard: true }, - $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$modalStack', + $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$uibModalStack', function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack) { var $modal = {}; @@ -689,3 +689,232 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) return $modalProvider; }); + +/* deprecated modal below */ + +angular.module('ui.bootstrap.modal') + + .value('$modalSuppressWarning', false) + + /** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', [ + '$animate', '$injector', '$modalStack', '$log', '$modalSuppressWarning', + function($animate , $injector, $modalStack, $log, $modalSuppressWarning) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + compile: function(tElement, tAttrs) { + tElement.addClass(tAttrs.backdropClass); + return linkFn; + } + }; + + function linkFn(scope, element, attrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-backdrop is now deprecated. Use uib-modal-backdrop instead.'); + } + if (attrs.modalInClass) { + if ($animateCss) { + $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + } + }]) + + .directive('modalWindow', [ + '$modalStack', '$q', '$animate', '$injector', '$log', '$modalSuppressWarning', + function($modalStack , $q , $animate, $injector, $log, $modalSuppressWarning) { + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + return { + restrict: 'EA', + scope: { + index: '@' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function(scope, element, attrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-window is now deprecated. Use uib-modal-window instead.'); + } + element.addClass(attrs.windowClass || ''); + element.addClass(attrs.windowTopClass || ''); + scope.size = attrs.size; + + scope.close = function(evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + + // moved from template to fix issue #2280 + element.on('click', scope.close); + + // This property is only added to the scope for the purpose of detecting when this directive is rendered. + // We can detect that by using this property in the template associated with this directive and then use + // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. + scope.$isRendered = true; + + // Deferred object that will be resolved when this modal is render. + var modalRenderDeferObj = $q.defer(); + // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. + // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. + attrs.$observe('modalRender', function(value) { + if (value == 'true') { + modalRenderDeferObj.resolve(); + } + }); + + modalRenderDeferObj.promise.then(function() { + var animationPromise = null; + + if (attrs.modalInClass) { + if ($animateCss) { + animationPromise = $animateCss(element, { + addClass: attrs.modalInClass + }).start(); + } else { + animationPromise = $animate.addClass(element, attrs.modalInClass); + } + + scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { + var done = setIsAsync(); + if ($animateCss) { + $animateCss(element, { + removeClass: attrs.modalInClass + }).start().then(done); + } else { + $animate.removeClass(element, attrs.modalInClass).then(done); + } + }); + } + + + $q.when(animationPromise).then(function() { + var inputsWithAutofocus = element[0].querySelectorAll('[autofocus]'); + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to lose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (inputsWithAutofocus.length) { + inputsWithAutofocus[0].focus(); + } else { + element[0].focus(); + } + }); + + // Notify {@link $modalStack} that modal is rendered. + var modal = $modalStack.getTop(); + if (modal) { + $modalStack.modalRendered(modal.key); + } + }); + } + }; + }]) + + .directive('modalAnimationClass', [ + '$log', '$modalSuppressWarning', + function ($log, $modalSuppressWarning) { + return { + compile: function(tElement, tAttrs) { + if (!$modalSuppressWarning) { + $log.warn('modal-animation-class is now deprecated. Use uib-modal-animation-class instead.'); + } + if (tAttrs.modalAnimation) { + tElement.addClass(tAttrs.modalAnimationClass); + } + } + }; + }]) + + .directive('modalTransclude', [ + '$log', '$modalSuppressWarning', + function ($log, $modalSuppressWarning) { + return { + link: function($scope, $element, $attrs, controller, $transclude) { + if (!$modalSuppressWarning) { + $log.warn('modal-transclude is now deprecated. Use uib-modal-transclude instead.'); + } + $transclude($scope.$parent, function(clone) { + $element.empty(); + $element.append(clone); + }); + } + }; + }]) + + .service('$modalStack', [ + '$animate', '$timeout', '$document', '$compile', '$rootScope', + '$q', + '$injector', + '$$multiMap', + '$$stackedMap', + '$uibModalStack', + '$log', + '$modalSuppressWarning', + function($animate , $timeout , $document , $compile , $rootScope , + $q, + $injector, + $$multiMap, + $$stackedMap, + $uibModalStack, + $log, + $modalSuppressWarning) { + if (!$modalSuppressWarning) { + $log.warn('$modalStack is now deprecated. Use $uibModalStack instead.'); + } + + angular.extend(this, $uibModalStack); + }]) + + .provider('$modal', ['$uibModalProvider', function($uibModalProvider) { + angular.extend(this, $uibModalProvider); + + this.$get = ['$injector', '$log', '$modalSuppressWarning', + function ($injector, $log, $modalSuppressWarning) { + if (!$modalSuppressWarning) { + $log.warn('$modal is now deprecated. Use $uibModal instead.'); + } + + return $injector.invoke($uibModalProvider.$get); + }]; + }]); diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js index 38afd1c4e4..7b9206133e 100644 --- a/src/modal/test/modal.spec.js +++ b/src/modal/test/modal.spec.js @@ -1,17 +1,17 @@ -describe('$modal', function () { +describe('$uibModal', function () { var $animate, $controllerProvider, $rootScope, $document, $compile, $templateCache, $timeout, $q; - var $modal, $modalStack, $modalProvider; + var $uibModal, $uibModalStack, $uibModalProvider; beforeEach(module('ngAnimateMock')); beforeEach(module('ui.bootstrap.modal')); beforeEach(module('template/modal/backdrop.html')); beforeEach(module('template/modal/window.html')); - beforeEach(module(function(_$controllerProvider_, _$modalProvider_){ + beforeEach(module(function(_$controllerProvider_, _$uibModalProvider_){ $controllerProvider = _$controllerProvider_; - $modalProvider = _$modalProvider_; + $uibModalProvider = _$uibModalProvider_; })); - beforeEach(inject(function(_$animate_, _$rootScope_, _$document_, _$compile_, _$templateCache_, _$timeout_, _$q_, _$modal_, _$modalStack_) { + beforeEach(inject(function(_$animate_, _$rootScope_, _$document_, _$compile_, _$templateCache_, _$timeout_, _$q_, _$uibModal_, _$uibModalStack_) { $animate = _$animate_; $rootScope = _$rootScope_; $document = _$document_; @@ -19,8 +19,8 @@ describe('$modal', function () { $templateCache = _$templateCache_; $timeout = _$timeout_; $q = _$q_; - $modal = _$modal_; - $modalStack = _$modalStack_; + $uibModal = _$uibModal_; + $uibModalStack = _$uibModalStack_; })); beforeEach(function() { @@ -145,7 +145,7 @@ describe('$modal', function () { } function open(modalOptions) { - var modal = $modal.open(modalOptions); + var modal = $uibModal.open(modalOptions); $rootScope.$digest(); $timeout.flush(0); return modal; @@ -478,7 +478,7 @@ describe('$modal', function () { describe('default options can be changed in a provider', function() { it('should allow overriding default options in a provider', function() { - $modalProvider.options.backdrop = false; + $uibModalProvider.options.backdrop = false; var modal = open({template: '
Content
'}); expect($document).toHaveModalOpenWithContent('Content', 'div'); @@ -486,7 +486,7 @@ describe('$modal', function () { }); it('should accept new objects with default options in a provider', function() { - $modalProvider.options = { + $uibModalProvider.options = { backdrop: false }; var modal = open({template: '
Content
'}); @@ -604,10 +604,10 @@ describe('$modal', function () { open({ controller: function($scope, $foo) { $scope.value = 'Content from resolve'; - expect($foo).toBe($modal); + expect($foo).toBe($uibModal); }, resolve: { - $foo: '$modal' + $foo: '$uibModal' }, template: '
{{value}}
' }); @@ -1014,7 +1014,7 @@ describe('$modal', function () { // Opens a modal for each element in array order. // Order is an array of non-repeating integers from 0..length-1 representing when to resolve that modal's promise. // For example [1,2,0] would resolve the 3rd modal's promise first and the 2nd modal's promise last. - // Tests that the modals are added to $modalStack and that each resolves its "opened" promise sequentially. + // Tests that the modals are added to $uibModalStack and that each resolves its "opened" promise sequentially. // If an element is {reject:n} then n is still the order, but the corresponding promise is rejected. // A rejection earlier in the open sequence should not affect modals opened later. function test(order) { @@ -1039,7 +1039,7 @@ describe('$modal', function () { x: function() { return ds[x].deferred.promise; } } }).opened.then(function() { - expect($modalStack.getTop().value.modalScope.index).toEqual(i); + expect($uibModalStack.getTop().value.modalScope.index).toEqual(i); actual += i; }); }); @@ -1054,7 +1054,7 @@ describe('$modal', function () { }); expect(actual).toEqual(expected); - expect($modal.getPromiseChain()).toEqual(null); + expect($uibModal.getPromiseChain()).toEqual(null); } // Calls emit n! times on arrays of length n containing all non-repeating permutations of the integers 0..n-1. @@ -1184,3 +1184,71 @@ describe('$modal', function () { }); }); }); + +/* deprecation tests below */ + +describe('$modal deprecation', function() { + beforeEach(module('ngAnimateMock')); + beforeEach(module('ui.bootstrap.modal')); + beforeEach(module('template/modal/backdrop.html')); + beforeEach(module('template/modal/window.html')); + + it('should suppress warning', function() { + module(function($provide) { + $provide.value('$modalSuppressWarning', true); + }); + + inject(function($modal, $timeout, $log, $rootScope) { + spyOn($log, 'warn'); + + $modal.open({template: '
Foo
'}); + $rootScope.$digest(); + $timeout.flush(0); + expect($log.warn.calls.count()).toBe(0); + }); + }); + + it('should give warning by default', inject(function($log) { + spyOn($log, 'warn'); + + inject(function($compile, $templateCache, $rootScope, $modal, $timeout) { + var backdropTemplate = + ''; + $templateCache.put('template/modal/backdrop.html', backdropTemplate); + + var windowTemplate = + ''; + $templateCache.put('template/modal/window.html', windowTemplate); + + $modal.open({template: '
Foo
'}); + $rootScope.$digest(); + $timeout.flush(0); + + expect($log.warn.calls.count()).toBe(5); + expect($log.warn.calls.argsFor(0)).toEqual(['$modal is now deprecated. Use $uibModal instead.']); + expect($log.warn.calls.argsFor(1)).toEqual(['$modalStack is now deprecated. Use $uibModalStack instead.']); + expect($log.warn.calls.argsFor(2)).toEqual(['modal-animation-class is now deprecated. Use uib-modal-animation-class instead.']); + expect($log.warn.calls.argsFor(3)).toEqual(['modal-animation-class is now deprecated. Use uib-modal-animation-class instead.']); + expect($log.warn.calls.argsFor(4)).toEqual(['modal-transclude is now deprecated. Use uib-modal-transclude instead.']); + + $log.warn.calls.reset(); + $compile('
')($rootScope); + $rootScope.$digest(); + expect($log.warn.calls.argsFor(1)).toEqual(['modal-backdrop is now deprecated. Use uib-modal-backdrop instead.']); + + $log.warn.calls.reset(); + $compile('
')($rootScope); + $rootScope.$digest(); + expect($log.warn.calls.argsFor(2)).toEqual(['modal-window is now deprecated. Use uib-modal-window instead.']); + }); + })); +}); diff --git a/src/modal/test/modalWindow.spec.js b/src/modal/test/modalWindow.spec.js index da5303f08c..60c00aed81 100644 --- a/src/modal/test/modalWindow.spec.js +++ b/src/modal/test/modalWindow.spec.js @@ -10,7 +10,7 @@ describe('modal window', function() { it('should not use transclusion scope for modals content - issue 2110', function() { $rootScope.animate = false; - $compile('
')($rootScope); + $compile('
')($rootScope); $rootScope.$digest(); expect($rootScope.foo).toBeTruthy(); @@ -18,7 +18,7 @@ describe('modal window', function() { it('should support custom CSS classes as string', function() { $rootScope.animate = false; - var windowEl = $compile('
content
')($rootScope); + var windowEl = $compile('
content
')($rootScope); $rootScope.$digest(); expect(windowEl).toHaveClass('test'); @@ -27,7 +27,7 @@ describe('modal window', function() { it('should support window top class', function () { $rootScope.animate = false; - var windowEl = $compile('
content
')($rootScope); + var windowEl = $compile('
content
')($rootScope); $rootScope.$digest(); expect(windowEl).toHaveClass('test'); @@ -37,7 +37,7 @@ describe('modal window', function() { it('should support custom template url', inject(function($templateCache) { $templateCache.put('window.html', '
'); - var windowEl = $compile('
content
')($rootScope); + var windowEl = $compile('
content
')($rootScope); $rootScope.$digest(); expect(windowEl).toHaveClass('mywindow'); diff --git a/template/modal/backdrop.html b/template/modal/backdrop.html index 5b6c49f201..1eeaa1a996 100644 --- a/template/modal/backdrop.html +++ b/template/modal/backdrop.html @@ -1,5 +1,5 @@ diff --git a/template/modal/window.html b/template/modal/window.html index 2efdd0fe24..ae17e400bb 100644 --- a/template/modal/window.html +++ b/template/modal/window.html @@ -1,6 +1,6 @@