| @@ 3673-4034 (lines=362) @@ | ||
| 3670 | ||
| 3671 | .factory('$uibModalStack', ['$animate', '$animateCss', '$document', |
|
| 3672 | '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition', |
|
| 3673 | function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) { |
|
| 3674 | var OPENED_MODAL_CLASS = 'modal-open'; |
|
| 3675 | ||
| 3676 | var backdropDomEl, backdropScope; |
|
| 3677 | var openedWindows = $$stackedMap.createNew(); |
|
| 3678 | var openedClasses = $$multiMap.createNew(); |
|
| 3679 | var $modalStack = { |
|
| 3680 | NOW_CLOSING_EVENT: 'modal.stack.now-closing' |
|
| 3681 | }; |
|
| 3682 | var topModalIndex = 0; |
|
| 3683 | var previousTopOpenedModal = null; |
|
| 3684 | ||
| 3685 | //Modal focus behavior |
|
| 3686 | var tabableSelector = 'a[href], area[href], input:not([disabled]), ' + |
|
| 3687 | 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + |
|
| 3688 | 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; |
|
| 3689 | var scrollbarPadding; |
|
| 3690 | ||
| 3691 | function isVisible(element) { |
|
| 3692 | return !!(element.offsetWidth || |
|
| 3693 | element.offsetHeight || |
|
| 3694 | element.getClientRects().length); |
|
| 3695 | } |
|
| 3696 | ||
| 3697 | function backdropIndex() { |
|
| 3698 | var topBackdropIndex = -1; |
|
| 3699 | var opened = openedWindows.keys(); |
|
| 3700 | for (var i = 0; i < opened.length; i++) { |
|
| 3701 | if (openedWindows.get(opened[i]).value.backdrop) { |
|
| 3702 | topBackdropIndex = i; |
|
| 3703 | } |
|
| 3704 | } |
|
| 3705 | ||
| 3706 | // If any backdrop exist, ensure that it's index is always |
|
| 3707 | // right below the top modal |
|
| 3708 | if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) { |
|
| 3709 | topBackdropIndex = topModalIndex; |
|
| 3710 | } |
|
| 3711 | return topBackdropIndex; |
|
| 3712 | } |
|
| 3713 | ||
| 3714 | $rootScope.$watch(backdropIndex, function(newBackdropIndex) { |
|
| 3715 | if (backdropScope) { |
|
| 3716 | backdropScope.index = newBackdropIndex; |
|
| 3717 | } |
|
| 3718 | }); |
|
| 3719 | ||
| 3720 | function removeModalWindow(modalInstance, elementToReceiveFocus) { |
|
| 3721 | var modalWindow = openedWindows.get(modalInstance).value; |
|
| 3722 | var appendToElement = modalWindow.appendTo; |
|
| 3723 | ||
| 3724 | //clean up the stack |
|
| 3725 | openedWindows.remove(modalInstance); |
|
| 3726 | previousTopOpenedModal = openedWindows.top(); |
|
| 3727 | if (previousTopOpenedModal) { |
|
| 3728 | topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10); |
|
| 3729 | } |
|
| 3730 | ||
| 3731 | removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { |
|
| 3732 | var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; |
|
| 3733 | openedClasses.remove(modalBodyClass, modalInstance); |
|
| 3734 | var areAnyOpen = openedClasses.hasKey(modalBodyClass); |
|
| 3735 | appendToElement.toggleClass(modalBodyClass, areAnyOpen); |
|
| 3736 | if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { |
|
| 3737 | if (scrollbarPadding.originalRight) { |
|
| 3738 | appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'}); |
|
| 3739 | } else { |
|
| 3740 | appendToElement.css({paddingRight: ''}); |
|
| 3741 | } |
|
| 3742 | scrollbarPadding = null; |
|
| 3743 | } |
|
| 3744 | toggleTopWindowClass(true); |
|
| 3745 | }, modalWindow.closedDeferred); |
|
| 3746 | checkRemoveBackdrop(); |
|
| 3747 | ||
| 3748 | //move focus to specified element if available, or else to body |
|
| 3749 | if (elementToReceiveFocus && elementToReceiveFocus.focus) { |
|
| 3750 | elementToReceiveFocus.focus(); |
|
| 3751 | } else if (appendToElement.focus) { |
|
| 3752 | appendToElement.focus(); |
|
| 3753 | } |
|
| 3754 | } |
|
| 3755 | ||
| 3756 | // Add or remove "windowTopClass" from the top window in the stack |
|
| 3757 | function toggleTopWindowClass(toggleSwitch) { |
|
| 3758 | var modalWindow; |
|
| 3759 | ||
| 3760 | if (openedWindows.length() > 0) { |
|
| 3761 | modalWindow = openedWindows.top().value; |
|
| 3762 | modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); |
|
| 3763 | } |
|
| 3764 | } |
|
| 3765 | ||
| 3766 | function checkRemoveBackdrop() { |
|
| 3767 | //remove backdrop if no longer needed |
|
| 3768 | if (backdropDomEl && backdropIndex() === -1) { |
|
| 3769 | var backdropScopeRef = backdropScope; |
|
| 3770 | removeAfterAnimate(backdropDomEl, backdropScope, function() { |
|
| 3771 | backdropScopeRef = null; |
|
| 3772 | }); |
|
| 3773 | backdropDomEl = undefined; |
|
| 3774 | backdropScope = undefined; |
|
| 3775 | } |
|
| 3776 | } |
|
| 3777 | ||
| 3778 | function removeAfterAnimate(domEl, scope, done, closedDeferred) { |
|
| 3779 | var asyncDeferred; |
|
| 3780 | var asyncPromise = null; |
|
| 3781 | var setIsAsync = function() { |
|
| 3782 | if (!asyncDeferred) { |
|
| 3783 | asyncDeferred = $q.defer(); |
|
| 3784 | asyncPromise = asyncDeferred.promise; |
|
| 3785 | } |
|
| 3786 | ||
| 3787 | return function asyncDone() { |
|
| 3788 | asyncDeferred.resolve(); |
|
| 3789 | }; |
|
| 3790 | }; |
|
| 3791 | scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); |
|
| 3792 | ||
| 3793 | // Note that it's intentional that asyncPromise might be null. |
|
| 3794 | // That's when setIsAsync has not been called during the |
|
| 3795 | // NOW_CLOSING_EVENT broadcast. |
|
| 3796 | return $q.when(asyncPromise).then(afterAnimating); |
|
| 3797 | ||
| 3798 | function afterAnimating() { |
|
| 3799 | if (afterAnimating.done) { |
|
| 3800 | return; |
|
| 3801 | } |
|
| 3802 | afterAnimating.done = true; |
|
| 3803 | ||
| 3804 | $animate.leave(domEl).then(function() { |
|
| 3805 | domEl.remove(); |
|
| 3806 | if (closedDeferred) { |
|
| 3807 | closedDeferred.resolve(); |
|
| 3808 | } |
|
| 3809 | }); |
|
| 3810 | ||
| 3811 | scope.$destroy(); |
|
| 3812 | if (done) { |
|
| 3813 | done(); |
|
| 3814 | } |
|
| 3815 | } |
|
| 3816 | } |
|
| 3817 | ||
| 3818 | $document.on('keydown', keydownListener); |
|
| 3819 | ||
| 3820 | $rootScope.$on('$destroy', function() { |
|
| 3821 | $document.off('keydown', keydownListener); |
|
| 3822 | }); |
|
| 3823 | ||
| 3824 | function keydownListener(evt) { |
|
| 3825 | if (evt.isDefaultPrevented()) { |
|
| 3826 | return evt; |
|
| 3827 | } |
|
| 3828 | ||
| 3829 | var modal = openedWindows.top(); |
|
| 3830 | if (modal) { |
|
| 3831 | switch (evt.which) { |
|
| 3832 | case 27: { |
|
| 3833 | if (modal.value.keyboard) { |
|
| 3834 | evt.preventDefault(); |
|
| 3835 | $rootScope.$apply(function() { |
|
| 3836 | $modalStack.dismiss(modal.key, 'escape key press'); |
|
| 3837 | }); |
|
| 3838 | } |
|
| 3839 | break; |
|
| 3840 | } |
|
| 3841 | case 9: { |
|
| 3842 | var list = $modalStack.loadFocusElementList(modal); |
|
| 3843 | var focusChanged = false; |
|
| 3844 | if (evt.shiftKey) { |
|
| 3845 | if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) { |
|
| 3846 | focusChanged = $modalStack.focusLastFocusableElement(list); |
|
| 3847 | } |
|
| 3848 | } else { |
|
| 3849 | if ($modalStack.isFocusInLastItem(evt, list)) { |
|
| 3850 | focusChanged = $modalStack.focusFirstFocusableElement(list); |
|
| 3851 | } |
|
| 3852 | } |
|
| 3853 | ||
| 3854 | if (focusChanged) { |
|
| 3855 | evt.preventDefault(); |
|
| 3856 | evt.stopPropagation(); |
|
| 3857 | } |
|
| 3858 | ||
| 3859 | break; |
|
| 3860 | } |
|
| 3861 | } |
|
| 3862 | } |
|
| 3863 | } |
|
| 3864 | ||
| 3865 | $modalStack.open = function(modalInstance, modal) { |
|
| 3866 | var modalOpener = $document[0].activeElement, |
|
| 3867 | modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; |
|
| 3868 | ||
| 3869 | toggleTopWindowClass(false); |
|
| 3870 | ||
| 3871 | // Store the current top first, to determine what index we ought to use |
|
| 3872 | // for the current top modal |
|
| 3873 | previousTopOpenedModal = openedWindows.top(); |
|
| 3874 | ||
| 3875 | openedWindows.add(modalInstance, { |
|
| 3876 | deferred: modal.deferred, |
|
| 3877 | renderDeferred: modal.renderDeferred, |
|
| 3878 | closedDeferred: modal.closedDeferred, |
|
| 3879 | modalScope: modal.scope, |
|
| 3880 | backdrop: modal.backdrop, |
|
| 3881 | keyboard: modal.keyboard, |
|
| 3882 | openedClass: modal.openedClass, |
|
| 3883 | windowTopClass: modal.windowTopClass, |
|
| 3884 | animation: modal.animation, |
|
| 3885 | appendTo: modal.appendTo |
|
| 3886 | }); |
|
| 3887 | ||
| 3888 | openedClasses.put(modalBodyClass, modalInstance); |
|
| 3889 | ||
| 3890 | var appendToElement = modal.appendTo, |
|
| 3891 | currBackdropIndex = backdropIndex(); |
|
| 3892 | ||
| 3893 | if (!appendToElement.length) { |
|
| 3894 | throw new Error('appendTo element not found. Make sure that the element passed is in DOM.'); |
|
| 3895 | } |
|
| 3896 | ||
| 3897 | if (currBackdropIndex >= 0 && !backdropDomEl) { |
|
| 3898 | backdropScope = $rootScope.$new(true); |
|
| 3899 | backdropScope.modalOptions = modal; |
|
| 3900 | backdropScope.index = currBackdropIndex; |
|
| 3901 | backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>'); |
|
| 3902 | backdropDomEl.attr('backdrop-class', modal.backdropClass); |
|
| 3903 | if (modal.animation) { |
|
| 3904 | backdropDomEl.attr('modal-animation', 'true'); |
|
| 3905 | } |
|
| 3906 | $compile(backdropDomEl)(backdropScope); |
|
| 3907 | $animate.enter(backdropDomEl, appendToElement); |
|
| 3908 | scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement); |
|
| 3909 | if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { |
|
| 3910 | appendToElement.css({paddingRight: scrollbarPadding.right + 'px'}); |
|
| 3911 | } |
|
| 3912 | } |
|
| 3913 | ||
| 3914 | // Set the top modal index based on the index of the previous top modal |
|
| 3915 | topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0; |
|
| 3916 | var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>'); |
|
| 3917 | angularDomEl.attr({ |
|
| 3918 | 'template-url': modal.windowTemplateUrl, |
|
| 3919 | 'window-class': modal.windowClass, |
|
| 3920 | 'window-top-class': modal.windowTopClass, |
|
| 3921 | 'size': modal.size, |
|
| 3922 | 'index': topModalIndex, |
|
| 3923 | 'animate': 'animate' |
|
| 3924 | }).html(modal.content); |
|
| 3925 | if (modal.animation) { |
|
| 3926 | angularDomEl.attr('modal-animation', 'true'); |
|
| 3927 | } |
|
| 3928 | ||
| 3929 | appendToElement.addClass(modalBodyClass); |
|
| 3930 | $animate.enter($compile(angularDomEl)(modal.scope), appendToElement); |
|
| 3931 | ||
| 3932 | openedWindows.top().value.modalDomEl = angularDomEl; |
|
| 3933 | openedWindows.top().value.modalOpener = modalOpener; |
|
| 3934 | }; |
|
| 3935 | ||
| 3936 | function broadcastClosing(modalWindow, resultOrReason, closing) { |
|
| 3937 | return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; |
|
| 3938 | } |
|
| 3939 | ||
| 3940 | $modalStack.close = function(modalInstance, result) { |
|
| 3941 | var modalWindow = openedWindows.get(modalInstance); |
|
| 3942 | if (modalWindow && broadcastClosing(modalWindow, result, true)) { |
|
| 3943 | modalWindow.value.modalScope.$$uibDestructionScheduled = true; |
|
| 3944 | modalWindow.value.deferred.resolve(result); |
|
| 3945 | removeModalWindow(modalInstance, modalWindow.value.modalOpener); |
|
| 3946 | return true; |
|
| 3947 | } |
|
| 3948 | return !modalWindow; |
|
| 3949 | }; |
|
| 3950 | ||
| 3951 | $modalStack.dismiss = function(modalInstance, reason) { |
|
| 3952 | var modalWindow = openedWindows.get(modalInstance); |
|
| 3953 | if (modalWindow && broadcastClosing(modalWindow, reason, false)) { |
|
| 3954 | modalWindow.value.modalScope.$$uibDestructionScheduled = true; |
|
| 3955 | modalWindow.value.deferred.reject(reason); |
|
| 3956 | removeModalWindow(modalInstance, modalWindow.value.modalOpener); |
|
| 3957 | return true; |
|
| 3958 | } |
|
| 3959 | return !modalWindow; |
|
| 3960 | }; |
|
| 3961 | ||
| 3962 | $modalStack.dismissAll = function(reason) { |
|
| 3963 | var topModal = this.getTop(); |
|
| 3964 | while (topModal && this.dismiss(topModal.key, reason)) { |
|
| 3965 | topModal = this.getTop(); |
|
| 3966 | } |
|
| 3967 | }; |
|
| 3968 | ||
| 3969 | $modalStack.getTop = function() { |
|
| 3970 | return openedWindows.top(); |
|
| 3971 | }; |
|
| 3972 | ||
| 3973 | $modalStack.modalRendered = function(modalInstance) { |
|
| 3974 | var modalWindow = openedWindows.get(modalInstance); |
|
| 3975 | if (modalWindow) { |
|
| 3976 | modalWindow.value.renderDeferred.resolve(); |
|
| 3977 | } |
|
| 3978 | }; |
|
| 3979 | ||
| 3980 | $modalStack.focusFirstFocusableElement = function(list) { |
|
| 3981 | if (list.length > 0) { |
|
| 3982 | list[0].focus(); |
|
| 3983 | return true; |
|
| 3984 | } |
|
| 3985 | return false; |
|
| 3986 | }; |
|
| 3987 | ||
| 3988 | $modalStack.focusLastFocusableElement = function(list) { |
|
| 3989 | if (list.length > 0) { |
|
| 3990 | list[list.length - 1].focus(); |
|
| 3991 | return true; |
|
| 3992 | } |
|
| 3993 | return false; |
|
| 3994 | }; |
|
| 3995 | ||
| 3996 | $modalStack.isModalFocused = function(evt, modalWindow) { |
|
| 3997 | if (evt && modalWindow) { |
|
| 3998 | var modalDomEl = modalWindow.value.modalDomEl; |
|
| 3999 | if (modalDomEl && modalDomEl.length) { |
|
| 4000 | return (evt.target || evt.srcElement) === modalDomEl[0]; |
|
| 4001 | } |
|
| 4002 | } |
|
| 4003 | return false; |
|
| 4004 | }; |
|
| 4005 | ||
| 4006 | $modalStack.isFocusInFirstItem = function(evt, list) { |
|
| 4007 | if (list.length > 0) { |
|
| 4008 | return (evt.target || evt.srcElement) === list[0]; |
|
| 4009 | } |
|
| 4010 | return false; |
|
| 4011 | }; |
|
| 4012 | ||
| 4013 | $modalStack.isFocusInLastItem = function(evt, list) { |
|
| 4014 | if (list.length > 0) { |
|
| 4015 | return (evt.target || evt.srcElement) === list[list.length - 1]; |
|
| 4016 | } |
|
| 4017 | return false; |
|
| 4018 | }; |
|
| 4019 | ||
| 4020 | $modalStack.loadFocusElementList = function(modalWindow) { |
|
| 4021 | if (modalWindow) { |
|
| 4022 | var modalDomE1 = modalWindow.value.modalDomEl; |
|
| 4023 | if (modalDomE1 && modalDomE1.length) { |
|
| 4024 | var elements = modalDomE1[0].querySelectorAll(tabableSelector); |
|
| 4025 | return elements ? |
|
| 4026 | Array.prototype.filter.call(elements, function(element) { |
|
| 4027 | return isVisible(element); |
|
| 4028 | }) : elements; |
|
| 4029 | } |
|
| 4030 | } |
|
| 4031 | }; |
|
| 4032 | ||
| 4033 | return $modalStack; |
|
| 4034 | }]) |
|
| 4035 | ||
| 4036 | .provider('$uibModal', function() { |
|
| 4037 | var $modalProvider = { |
|
| @@ 3672-4033 (lines=362) @@ | ||
| 3669 | ||
| 3670 | .factory('$uibModalStack', ['$animate', '$animateCss', '$document', |
|
| 3671 | '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition', |
|
| 3672 | function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) { |
|
| 3673 | var OPENED_MODAL_CLASS = 'modal-open'; |
|
| 3674 | ||
| 3675 | var backdropDomEl, backdropScope; |
|
| 3676 | var openedWindows = $$stackedMap.createNew(); |
|
| 3677 | var openedClasses = $$multiMap.createNew(); |
|
| 3678 | var $modalStack = { |
|
| 3679 | NOW_CLOSING_EVENT: 'modal.stack.now-closing' |
|
| 3680 | }; |
|
| 3681 | var topModalIndex = 0; |
|
| 3682 | var previousTopOpenedModal = null; |
|
| 3683 | ||
| 3684 | //Modal focus behavior |
|
| 3685 | var tabableSelector = 'a[href], area[href], input:not([disabled]), ' + |
|
| 3686 | 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + |
|
| 3687 | 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; |
|
| 3688 | var scrollbarPadding; |
|
| 3689 | ||
| 3690 | function isVisible(element) { |
|
| 3691 | return !!(element.offsetWidth || |
|
| 3692 | element.offsetHeight || |
|
| 3693 | element.getClientRects().length); |
|
| 3694 | } |
|
| 3695 | ||
| 3696 | function backdropIndex() { |
|
| 3697 | var topBackdropIndex = -1; |
|
| 3698 | var opened = openedWindows.keys(); |
|
| 3699 | for (var i = 0; i < opened.length; i++) { |
|
| 3700 | if (openedWindows.get(opened[i]).value.backdrop) { |
|
| 3701 | topBackdropIndex = i; |
|
| 3702 | } |
|
| 3703 | } |
|
| 3704 | ||
| 3705 | // If any backdrop exist, ensure that it's index is always |
|
| 3706 | // right below the top modal |
|
| 3707 | if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) { |
|
| 3708 | topBackdropIndex = topModalIndex; |
|
| 3709 | } |
|
| 3710 | return topBackdropIndex; |
|
| 3711 | } |
|
| 3712 | ||
| 3713 | $rootScope.$watch(backdropIndex, function(newBackdropIndex) { |
|
| 3714 | if (backdropScope) { |
|
| 3715 | backdropScope.index = newBackdropIndex; |
|
| 3716 | } |
|
| 3717 | }); |
|
| 3718 | ||
| 3719 | function removeModalWindow(modalInstance, elementToReceiveFocus) { |
|
| 3720 | var modalWindow = openedWindows.get(modalInstance).value; |
|
| 3721 | var appendToElement = modalWindow.appendTo; |
|
| 3722 | ||
| 3723 | //clean up the stack |
|
| 3724 | openedWindows.remove(modalInstance); |
|
| 3725 | previousTopOpenedModal = openedWindows.top(); |
|
| 3726 | if (previousTopOpenedModal) { |
|
| 3727 | topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10); |
|
| 3728 | } |
|
| 3729 | ||
| 3730 | removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { |
|
| 3731 | var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; |
|
| 3732 | openedClasses.remove(modalBodyClass, modalInstance); |
|
| 3733 | var areAnyOpen = openedClasses.hasKey(modalBodyClass); |
|
| 3734 | appendToElement.toggleClass(modalBodyClass, areAnyOpen); |
|
| 3735 | if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { |
|
| 3736 | if (scrollbarPadding.originalRight) { |
|
| 3737 | appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'}); |
|
| 3738 | } else { |
|
| 3739 | appendToElement.css({paddingRight: ''}); |
|
| 3740 | } |
|
| 3741 | scrollbarPadding = null; |
|
| 3742 | } |
|
| 3743 | toggleTopWindowClass(true); |
|
| 3744 | }, modalWindow.closedDeferred); |
|
| 3745 | checkRemoveBackdrop(); |
|
| 3746 | ||
| 3747 | //move focus to specified element if available, or else to body |
|
| 3748 | if (elementToReceiveFocus && elementToReceiveFocus.focus) { |
|
| 3749 | elementToReceiveFocus.focus(); |
|
| 3750 | } else if (appendToElement.focus) { |
|
| 3751 | appendToElement.focus(); |
|
| 3752 | } |
|
| 3753 | } |
|
| 3754 | ||
| 3755 | // Add or remove "windowTopClass" from the top window in the stack |
|
| 3756 | function toggleTopWindowClass(toggleSwitch) { |
|
| 3757 | var modalWindow; |
|
| 3758 | ||
| 3759 | if (openedWindows.length() > 0) { |
|
| 3760 | modalWindow = openedWindows.top().value; |
|
| 3761 | modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); |
|
| 3762 | } |
|
| 3763 | } |
|
| 3764 | ||
| 3765 | function checkRemoveBackdrop() { |
|
| 3766 | //remove backdrop if no longer needed |
|
| 3767 | if (backdropDomEl && backdropIndex() === -1) { |
|
| 3768 | var backdropScopeRef = backdropScope; |
|
| 3769 | removeAfterAnimate(backdropDomEl, backdropScope, function() { |
|
| 3770 | backdropScopeRef = null; |
|
| 3771 | }); |
|
| 3772 | backdropDomEl = undefined; |
|
| 3773 | backdropScope = undefined; |
|
| 3774 | } |
|
| 3775 | } |
|
| 3776 | ||
| 3777 | function removeAfterAnimate(domEl, scope, done, closedDeferred) { |
|
| 3778 | var asyncDeferred; |
|
| 3779 | var asyncPromise = null; |
|
| 3780 | var setIsAsync = function() { |
|
| 3781 | if (!asyncDeferred) { |
|
| 3782 | asyncDeferred = $q.defer(); |
|
| 3783 | asyncPromise = asyncDeferred.promise; |
|
| 3784 | } |
|
| 3785 | ||
| 3786 | return function asyncDone() { |
|
| 3787 | asyncDeferred.resolve(); |
|
| 3788 | }; |
|
| 3789 | }; |
|
| 3790 | scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); |
|
| 3791 | ||
| 3792 | // Note that it's intentional that asyncPromise might be null. |
|
| 3793 | // That's when setIsAsync has not been called during the |
|
| 3794 | // NOW_CLOSING_EVENT broadcast. |
|
| 3795 | return $q.when(asyncPromise).then(afterAnimating); |
|
| 3796 | ||
| 3797 | function afterAnimating() { |
|
| 3798 | if (afterAnimating.done) { |
|
| 3799 | return; |
|
| 3800 | } |
|
| 3801 | afterAnimating.done = true; |
|
| 3802 | ||
| 3803 | $animate.leave(domEl).then(function() { |
|
| 3804 | domEl.remove(); |
|
| 3805 | if (closedDeferred) { |
|
| 3806 | closedDeferred.resolve(); |
|
| 3807 | } |
|
| 3808 | }); |
|
| 3809 | ||
| 3810 | scope.$destroy(); |
|
| 3811 | if (done) { |
|
| 3812 | done(); |
|
| 3813 | } |
|
| 3814 | } |
|
| 3815 | } |
|
| 3816 | ||
| 3817 | $document.on('keydown', keydownListener); |
|
| 3818 | ||
| 3819 | $rootScope.$on('$destroy', function() { |
|
| 3820 | $document.off('keydown', keydownListener); |
|
| 3821 | }); |
|
| 3822 | ||
| 3823 | function keydownListener(evt) { |
|
| 3824 | if (evt.isDefaultPrevented()) { |
|
| 3825 | return evt; |
|
| 3826 | } |
|
| 3827 | ||
| 3828 | var modal = openedWindows.top(); |
|
| 3829 | if (modal) { |
|
| 3830 | switch (evt.which) { |
|
| 3831 | case 27: { |
|
| 3832 | if (modal.value.keyboard) { |
|
| 3833 | evt.preventDefault(); |
|
| 3834 | $rootScope.$apply(function() { |
|
| 3835 | $modalStack.dismiss(modal.key, 'escape key press'); |
|
| 3836 | }); |
|
| 3837 | } |
|
| 3838 | break; |
|
| 3839 | } |
|
| 3840 | case 9: { |
|
| 3841 | var list = $modalStack.loadFocusElementList(modal); |
|
| 3842 | var focusChanged = false; |
|
| 3843 | if (evt.shiftKey) { |
|
| 3844 | if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) { |
|
| 3845 | focusChanged = $modalStack.focusLastFocusableElement(list); |
|
| 3846 | } |
|
| 3847 | } else { |
|
| 3848 | if ($modalStack.isFocusInLastItem(evt, list)) { |
|
| 3849 | focusChanged = $modalStack.focusFirstFocusableElement(list); |
|
| 3850 | } |
|
| 3851 | } |
|
| 3852 | ||
| 3853 | if (focusChanged) { |
|
| 3854 | evt.preventDefault(); |
|
| 3855 | evt.stopPropagation(); |
|
| 3856 | } |
|
| 3857 | ||
| 3858 | break; |
|
| 3859 | } |
|
| 3860 | } |
|
| 3861 | } |
|
| 3862 | } |
|
| 3863 | ||
| 3864 | $modalStack.open = function(modalInstance, modal) { |
|
| 3865 | var modalOpener = $document[0].activeElement, |
|
| 3866 | modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; |
|
| 3867 | ||
| 3868 | toggleTopWindowClass(false); |
|
| 3869 | ||
| 3870 | // Store the current top first, to determine what index we ought to use |
|
| 3871 | // for the current top modal |
|
| 3872 | previousTopOpenedModal = openedWindows.top(); |
|
| 3873 | ||
| 3874 | openedWindows.add(modalInstance, { |
|
| 3875 | deferred: modal.deferred, |
|
| 3876 | renderDeferred: modal.renderDeferred, |
|
| 3877 | closedDeferred: modal.closedDeferred, |
|
| 3878 | modalScope: modal.scope, |
|
| 3879 | backdrop: modal.backdrop, |
|
| 3880 | keyboard: modal.keyboard, |
|
| 3881 | openedClass: modal.openedClass, |
|
| 3882 | windowTopClass: modal.windowTopClass, |
|
| 3883 | animation: modal.animation, |
|
| 3884 | appendTo: modal.appendTo |
|
| 3885 | }); |
|
| 3886 | ||
| 3887 | openedClasses.put(modalBodyClass, modalInstance); |
|
| 3888 | ||
| 3889 | var appendToElement = modal.appendTo, |
|
| 3890 | currBackdropIndex = backdropIndex(); |
|
| 3891 | ||
| 3892 | if (!appendToElement.length) { |
|
| 3893 | throw new Error('appendTo element not found. Make sure that the element passed is in DOM.'); |
|
| 3894 | } |
|
| 3895 | ||
| 3896 | if (currBackdropIndex >= 0 && !backdropDomEl) { |
|
| 3897 | backdropScope = $rootScope.$new(true); |
|
| 3898 | backdropScope.modalOptions = modal; |
|
| 3899 | backdropScope.index = currBackdropIndex; |
|
| 3900 | backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>'); |
|
| 3901 | backdropDomEl.attr('backdrop-class', modal.backdropClass); |
|
| 3902 | if (modal.animation) { |
|
| 3903 | backdropDomEl.attr('modal-animation', 'true'); |
|
| 3904 | } |
|
| 3905 | $compile(backdropDomEl)(backdropScope); |
|
| 3906 | $animate.enter(backdropDomEl, appendToElement); |
|
| 3907 | scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement); |
|
| 3908 | if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { |
|
| 3909 | appendToElement.css({paddingRight: scrollbarPadding.right + 'px'}); |
|
| 3910 | } |
|
| 3911 | } |
|
| 3912 | ||
| 3913 | // Set the top modal index based on the index of the previous top modal |
|
| 3914 | topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0; |
|
| 3915 | var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>'); |
|
| 3916 | angularDomEl.attr({ |
|
| 3917 | 'template-url': modal.windowTemplateUrl, |
|
| 3918 | 'window-class': modal.windowClass, |
|
| 3919 | 'window-top-class': modal.windowTopClass, |
|
| 3920 | 'size': modal.size, |
|
| 3921 | 'index': topModalIndex, |
|
| 3922 | 'animate': 'animate' |
|
| 3923 | }).html(modal.content); |
|
| 3924 | if (modal.animation) { |
|
| 3925 | angularDomEl.attr('modal-animation', 'true'); |
|
| 3926 | } |
|
| 3927 | ||
| 3928 | appendToElement.addClass(modalBodyClass); |
|
| 3929 | $animate.enter($compile(angularDomEl)(modal.scope), appendToElement); |
|
| 3930 | ||
| 3931 | openedWindows.top().value.modalDomEl = angularDomEl; |
|
| 3932 | openedWindows.top().value.modalOpener = modalOpener; |
|
| 3933 | }; |
|
| 3934 | ||
| 3935 | function broadcastClosing(modalWindow, resultOrReason, closing) { |
|
| 3936 | return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; |
|
| 3937 | } |
|
| 3938 | ||
| 3939 | $modalStack.close = function(modalInstance, result) { |
|
| 3940 | var modalWindow = openedWindows.get(modalInstance); |
|
| 3941 | if (modalWindow && broadcastClosing(modalWindow, result, true)) { |
|
| 3942 | modalWindow.value.modalScope.$$uibDestructionScheduled = true; |
|
| 3943 | modalWindow.value.deferred.resolve(result); |
|
| 3944 | removeModalWindow(modalInstance, modalWindow.value.modalOpener); |
|
| 3945 | return true; |
|
| 3946 | } |
|
| 3947 | return !modalWindow; |
|
| 3948 | }; |
|
| 3949 | ||
| 3950 | $modalStack.dismiss = function(modalInstance, reason) { |
|
| 3951 | var modalWindow = openedWindows.get(modalInstance); |
|
| 3952 | if (modalWindow && broadcastClosing(modalWindow, reason, false)) { |
|
| 3953 | modalWindow.value.modalScope.$$uibDestructionScheduled = true; |
|
| 3954 | modalWindow.value.deferred.reject(reason); |
|
| 3955 | removeModalWindow(modalInstance, modalWindow.value.modalOpener); |
|
| 3956 | return true; |
|
| 3957 | } |
|
| 3958 | return !modalWindow; |
|
| 3959 | }; |
|
| 3960 | ||
| 3961 | $modalStack.dismissAll = function(reason) { |
|
| 3962 | var topModal = this.getTop(); |
|
| 3963 | while (topModal && this.dismiss(topModal.key, reason)) { |
|
| 3964 | topModal = this.getTop(); |
|
| 3965 | } |
|
| 3966 | }; |
|
| 3967 | ||
| 3968 | $modalStack.getTop = function() { |
|
| 3969 | return openedWindows.top(); |
|
| 3970 | }; |
|
| 3971 | ||
| 3972 | $modalStack.modalRendered = function(modalInstance) { |
|
| 3973 | var modalWindow = openedWindows.get(modalInstance); |
|
| 3974 | if (modalWindow) { |
|
| 3975 | modalWindow.value.renderDeferred.resolve(); |
|
| 3976 | } |
|
| 3977 | }; |
|
| 3978 | ||
| 3979 | $modalStack.focusFirstFocusableElement = function(list) { |
|
| 3980 | if (list.length > 0) { |
|
| 3981 | list[0].focus(); |
|
| 3982 | return true; |
|
| 3983 | } |
|
| 3984 | return false; |
|
| 3985 | }; |
|
| 3986 | ||
| 3987 | $modalStack.focusLastFocusableElement = function(list) { |
|
| 3988 | if (list.length > 0) { |
|
| 3989 | list[list.length - 1].focus(); |
|
| 3990 | return true; |
|
| 3991 | } |
|
| 3992 | return false; |
|
| 3993 | }; |
|
| 3994 | ||
| 3995 | $modalStack.isModalFocused = function(evt, modalWindow) { |
|
| 3996 | if (evt && modalWindow) { |
|
| 3997 | var modalDomEl = modalWindow.value.modalDomEl; |
|
| 3998 | if (modalDomEl && modalDomEl.length) { |
|
| 3999 | return (evt.target || evt.srcElement) === modalDomEl[0]; |
|
| 4000 | } |
|
| 4001 | } |
|
| 4002 | return false; |
|
| 4003 | }; |
|
| 4004 | ||
| 4005 | $modalStack.isFocusInFirstItem = function(evt, list) { |
|
| 4006 | if (list.length > 0) { |
|
| 4007 | return (evt.target || evt.srcElement) === list[0]; |
|
| 4008 | } |
|
| 4009 | return false; |
|
| 4010 | }; |
|
| 4011 | ||
| 4012 | $modalStack.isFocusInLastItem = function(evt, list) { |
|
| 4013 | if (list.length > 0) { |
|
| 4014 | return (evt.target || evt.srcElement) === list[list.length - 1]; |
|
| 4015 | } |
|
| 4016 | return false; |
|
| 4017 | }; |
|
| 4018 | ||
| 4019 | $modalStack.loadFocusElementList = function(modalWindow) { |
|
| 4020 | if (modalWindow) { |
|
| 4021 | var modalDomE1 = modalWindow.value.modalDomEl; |
|
| 4022 | if (modalDomE1 && modalDomE1.length) { |
|
| 4023 | var elements = modalDomE1[0].querySelectorAll(tabableSelector); |
|
| 4024 | return elements ? |
|
| 4025 | Array.prototype.filter.call(elements, function(element) { |
|
| 4026 | return isVisible(element); |
|
| 4027 | }) : elements; |
|
| 4028 | } |
|
| 4029 | } |
|
| 4030 | }; |
|
| 4031 | ||
| 4032 | return $modalStack; |
|
| 4033 | }]) |
|
| 4034 | ||
| 4035 | .provider('$uibModal', function() { |
|
| 4036 | var $modalProvider = { |
|