@@ 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 = { |