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..935f284584 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 `$modalProvider.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..cf9efc1934 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,670 @@ 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); + }); + } + }; + }]) + + .factory('$modalStack', [ + '$animate', '$timeout', '$document', '$compile', '$rootScope', + '$q', + '$injector', + '$$multiMap', + '$$stackedMap', + '$log', + '$modalSuppressWarning', + function($animate , $timeout , $document , $compile , $rootScope , + $q, + $injector, + $$multiMap, + $$stackedMap, + $log, + $modalSuppressWarning) { + if (!$modalSuppressWarning) { + $log.warn('$modalStack is now deprecated. Use $uibModalStack instead.'); + } + var $animateCss = null; + + if ($injector.has('$animateCss')) { + $animateCss = $injector.get('$animateCss'); + } + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var openedClasses = $$multiMap.createNew(); + var $modalStack = { + NOW_CLOSING_EVENT: 'modal.stack.now-closing' + }; + + //Modal focus behavior + var focusableElementList; + var focusIndex = 0; + var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' + + 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + + 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex) { + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance, elementToReceiveFocus) { + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { + var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; + openedClasses.remove(modalBodyClass, modalInstance); + body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass)); + toggleTopWindowClass(true); + }); + checkRemoveBackdrop(); + + //move focus to specified element if available, or else to body + if (elementToReceiveFocus && elementToReceiveFocus.focus) { + elementToReceiveFocus.focus(); + } else { + body.focus(); + } + } + + // Add or remove "windowTopClass" from the top window in the stack + function toggleTopWindowClass(toggleSwitch) { + var modalWindow; + + if (openedWindows.length() > 0) { + modalWindow = openedWindows.top().value; + modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); + } + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, function() { + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, done) { + var asyncDeferred; + var asyncPromise = null; + var setIsAsync = function() { + if (!asyncDeferred) { + asyncDeferred = $q.defer(); + asyncPromise = asyncDeferred.promise; + } + + return function asyncDone() { + asyncDeferred.resolve(); + }; + }; + scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); + + // Note that it's intentional that asyncPromise might be null. + // That's when setIsAsync has not been called during the + // NOW_CLOSING_EVENT broadcast. + return $q.when(asyncPromise).then(afterAnimating); + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + if ($animateCss) { + $animateCss(domEl, { + event: 'leave' + }).start().then(function() { + domEl.remove(); + }); + } else { + $animate.leave(domEl); + } + scope.$destroy(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function(evt) { + if (evt.isDefaultPrevented()) { + return evt; + } + + var modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + switch (evt.which){ + case 27: { + evt.preventDefault(); + $rootScope.$apply(function() { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + break; + } + case 9: { + $modalStack.loadFocusElementList(modal); + var focusChanged = false; + if (evt.shiftKey) { + if ($modalStack.isFocusInFirstItem(evt)) { + focusChanged = $modalStack.focusLastFocusableElement(); + } + } else { + if ($modalStack.isFocusInLastItem(evt)) { + focusChanged = $modalStack.focusFirstFocusableElement(); + } + } + + if (focusChanged) { + evt.preventDefault(); + evt.stopPropagation(); + } + break; + } + } + } + }); + + $modalStack.open = function(modalInstance, modal) { + var modalOpener = $document[0].activeElement, + modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; + + toggleTopWindowClass(false); + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + renderDeferred: modal.renderDeferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard, + openedClass: modal.openedClass, + windowTopClass: modal.windowTopClass + }); + + openedClasses.put(modalBodyClass, modalInstance); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + var angularBackgroundDomEl = angular.element('
'); + angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); + if (modal.animation) { + angularBackgroundDomEl.attr('modal-animation', 'true'); + } + backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
'); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'window-top-class': modal.windowTopClass, + 'size': modal.size, + 'index': openedWindows.length() - 1, + 'animate': 'animate' + }).html(modal.content); + if (modal.animation) { + angularDomEl.attr('modal-animation', 'true'); + } + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + openedWindows.top().value.modalOpener = modalOpener; + body.append(modalDomEl); + body.addClass(modalBodyClass); + + $modalStack.clearFocusListCache(); + }; + + function broadcastClosing(modalWindow, resultOrReason, closing) { + return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; + } + + $modalStack.close = function(modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow && broadcastClosing(modalWindow, result, true)) { + modalWindow.value.modalScope.$$uibDestructionScheduled = true; + modalWindow.value.deferred.resolve(result); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; + } + return !modalWindow; + }; + + $modalStack.dismiss = function(modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow && broadcastClosing(modalWindow, reason, false)) { + modalWindow.value.modalScope.$$uibDestructionScheduled = true; + modalWindow.value.deferred.reject(reason); + removeModalWindow(modalInstance, modalWindow.value.modalOpener); + return true; + } + return !modalWindow; + }; + + $modalStack.dismissAll = function(reason) { + var topModal = this.getTop(); + while (topModal && this.dismiss(topModal.key, reason)) { + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function() { + return openedWindows.top(); + }; + + $modalStack.modalRendered = function(modalInstance) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.renderDeferred.resolve(); + } + }; + + $modalStack.focusFirstFocusableElement = function() { + if (focusableElementList.length > 0) { + focusableElementList[0].focus(); + return true; + } + return false; + }; + $modalStack.focusLastFocusableElement = function() { + if (focusableElementList.length > 0) { + focusableElementList[focusableElementList.length - 1].focus(); + return true; + } + return false; + }; + + $modalStack.isFocusInFirstItem = function(evt) { + if (focusableElementList.length > 0) { + return (evt.target || evt.srcElement) == focusableElementList[0]; + } + return false; + }; + + $modalStack.isFocusInLastItem = function(evt) { + if (focusableElementList.length > 0) { + return (evt.target || evt.srcElement) == focusableElementList[focusableElementList.length - 1]; + } + return false; + }; + + $modalStack.clearFocusListCache = function() { + focusableElementList = []; + focusIndex = 0; + }; + + $modalStack.loadFocusElementList = function(modalWindow) { + if (focusableElementList === undefined || !focusableElementList.length0) { + if (modalWindow) { + var modalDomE1 = modalWindow.value.modalDomEl; + if (modalDomE1 && modalDomE1.length) { + focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector); + } + } + } + }; + + return $modalStack; + }]) + + .provider('$modal', function() { + var $modalProvider = { + options: { + animation: true, + backdrop: true, //can also be false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$modalStack', '$log', '$modalSuppressWarning', + function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack, $log, $modalSuppressWarning) { + if (!$modalSuppressWarning) { + $log.warn('$modal is now deprecated. Use $uibModal instead.'); + } + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function(value) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } else if (angular.isString(value)) { + promisesArr.push($q.when($injector.get(value))); + } else { + promisesArr.push($q.when(value)); + } + }); + return promisesArr; + } + + var promiseChain = null; + $modal.getPromiseChain = function() { + return promiseChain; + }; + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + var modalRenderDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + rendered: modalRenderDeferred.promise, + close: function (result) { + return $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + return $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + // Wait for the resolution of the existing promise chain. + // Then switch to our own combined promise dependency (regardless of how the previous modal fared). + // Then add to $modalStack and resolve opened. + // Finally clean up the chain variable if no subsequent modal has overwritten it. + var samePromise; + samePromise = promiseChain = $q.all([promiseChain]) + .then(function() { return templateAndResolvePromise; }, function() { return templateAndResolvePromise; }) + .then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + modalScope.$on('$destroy', function() { + if (!modalScope.$$uibDestructionScheduled) { + modalScope.$dismiss('$uibUnscheduledDestruction'); + } + }); + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function(value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + if (modalOptions.controllerAs) { + if (modalOptions.bindToController) { + angular.extend(ctrlInstance, modalScope); + } + + modalScope[modalOptions.controllerAs] = ctrlInstance; + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + renderDeferred: modalRenderDeferred, + content: tplAndVars[0], + animation: modalOptions.animation, + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowTopClass: modalOptions.windowTopClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size, + openedClass: modalOptions.openedClass + }); + modalOpenedDeferred.resolve(true); + + }, function resolveError(reason) { + modalOpenedDeferred.reject(reason); + modalResultDeferred.reject(reason); + }) + .finally(function() { + if (promiseChain === samePromise) { + promiseChain = null; + } + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js index 38afd1c4e4..d702ba300b 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,63 @@ 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($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(7); + expect($log.warn.calls.argsFor(0)).toEqual(['$modalStack is now deprecated. Use $uibModalStack instead.']); + expect($log.warn.calls.argsFor(1)).toEqual(['$modal is now deprecated. Use $uibModal 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-backdrop is now deprecated. Use uib-modal-backdrop instead.']); + expect($log.warn.calls.argsFor(4)).toEqual(['modal-animation-class is now deprecated. Use uib-modal-animation-class instead.']); + expect($log.warn.calls.argsFor(5)).toEqual(['modal-transclude is now deprecated. Use uib-modal-transclude instead.']); + expect($log.warn.calls.argsFor(6)).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 @@