Source:slideshow-dialog.directive.js

(function () {
    'use strict';
    // Slideshow dialog window
    angular
        .module('mohistory')
        .directive('slideshowDialog', slideshowDialog);
    slideshowDialog.$inject = ['$transitions', '$rootScope', '$timeout'];
    /**
     * Directive for the photo slideshow accessible via blog posts.
     * @memberof mohistory
     * @name slideshowDialog
     * @ngdoc directive
     */
    function slideshowDialog($transitions, $rootScope, $timeout) {
        var directive = {
            templateUrl: 'app/components/slideshow-dialog/slideshow-dialog.directive.html',
            controller: slideshowDialogCtrl,
            controllerAs: 'slideD',
            bindToController: true,
            scope: {
                isDialogOpen: '<',
                startIdx: '<',
                images: '<',
                closeDialog: '<',
            },
            link: slideshowDialogLink,
            restrict: 'E',
        };
        return directive;
        function slideshowDialogCtrl() {
            var vm = this;
            /* ----- Variables ----- */
            vm.curIdx = 0;
            /* ----- Function Bindings ----- */
            vm.$onInit = onInit;
            vm.$onChanges = onChanges;
            vm.slideNext = slideNext;
            vm.slidePrevious = slidePrevious;
            /* ----- Function Definitions ----- */
            /**
             * Called when component is created.
             * @function onInit
             * @memberof slideshowDialog
             */
            function onInit() {
                vm.curIdx = vm.startIdx;
            }
            /** 
             * Any changes to the information passed into this component from
             * the parent will trigger this hook.
             * @function onChanges
             * @memberof slideshowDialog
            */
            function onChanges() {
                vm.curIdx = vm.startIdx;
            }
            /**
             * Advance slide show. Only available when there is content in the mhs:slideshow
             * portion of the dataset
             * @function slideNext
             * @memberof slideshowDialog
             */
            function slideNext() {
                if (vm.curIdx < (vm.images.length - 1)) {
                    vm.curIdx = vm.curIdx + 1;
                } else {
                    vm.curIdx = 0
                }
            }
            /**
             * Retreat the slide show. Only available when there is content in the mhs:slideshow
             * portion of the dataset
             * @function slidePrevious
             * @memberof slideshowDialog
             */
            function slidePrevious(x) {
                if (vm.curIdx == 0) {
                    vm.curIdx = vm.images.length - 1
                } else {
                    vm.curIdx = vm.curIdx - 1;
                }
            }
        }
        /**
         * Update the DOM and handle keyboard events for slideshow-dialog.
         * @param {String} $scope an AngularJS scope object.
         * @param {String} $element the jqLite-wrapped element that this directive matches.
         * @param {String} $attrs a hash object with key-value pairs of normalized attribute 
         * names and their corresponding attribute values.
         * @param {String} $ctrl the controller associated with the directive
         * @function slideshowDialogLink
         * @memberof slideshowDialog
         */
        function slideshowDialogLink($scope, $element, $attrs, $ctrl) {
            /* ----- Variables ----- */
            var body = $('body');
            var html = $('html');
            var oldFocus = null;
            var dialogWindow = $('#slideshow-dialog-window');
            /* --- Scope Listeners --- */
            // Watch the boolean value passed in from the slideshow-dialog component
            $scope.$watch(function () {
                return $ctrl.isDialogOpen;
            }, toggleFocusLock);
            // Remove JQuery listeners when directive is destroyed
            $scope.$on('$destroy', function () {
                body.off('keyup', closeOnEscape);
                body.off('keyup', onArrowKey);
                dialogWindow.find('#slideshow-dialog-close').off('keydown', onShiftTabGoToLast);
                dialogWindow.find('button').last().off('keydown', onTabGoToFirst);
            });
            /* ----- Function Definitions ----- */
            /**
             * When menu is open make sure the close button has focus and scrolling
             * behind the menu is disabled.
             * @param {String} value true if the menu is open, false otherwise
             * @function toggleFocusLock
             * @memberof slideshowDialogLink
             */
            function toggleFocusLock(value) {
                value = Boolean(value);
                if (value) {
                    oldFocus = $(document.activeElement);
                    body.addClass('locked');
                    html.addClass('locked');
                    // Use timeout to add commands to the end of the command stack
                    // this gives the DOM enough time to fully render
                    $timeout(function () {
                        dialogWindow = $('#slideshow-dialog-window');
                        dialogWindow.find('#slideshow-dialog-close').focus();
                        body.on('keyup', closeOnEscape);
                        body.on('keyup', onArrowKey);
                        dialogWindow.find('#slideshow-dialog-close').on('keydown', onShiftTabGoToLast);
                        dialogWindow.find('button').last().on('keydown', onTabGoToFirst);
                    });
                } else {
                    body.removeClass('locked');
                    html.removeClass('locked');
                    // Use timeout to add commands to the end of the command stack
                    // this gives the DOM enough time to fully render
                    $timeout(function () {
                        if (!$ctrl.isDialogOpen && oldFocus) {
                            oldFocus.focus();
                            body.off('keyup', closeOnEscape);
                            body.off('keyup', onArrowKey);
                            dialogWindow.find('#slideshow-dialog-close').off('keydown', onShiftTabGoToLast);
                            dialogWindow.find('button').last().off('keydown', onTabGoToFirst);
                        }
                    })
                }
            }
            /**
             * Pressing Shift plus Tab on the first focusable element will
             * move focus to the last focusable element.
             * @function onShiftTabGoToLast
             * @memberof slideshowDialogLink
             * @param {object} e Event object
             */
            function onShiftTabGoToLast(e) {
                var keyCode = e.keyCode || e.which;
                if($ctrl.isDialogOpen && keyCode === 9 && e.shiftKey) {
                    dialogWindow.find('button').last().focus();
                    e.preventDefault();
                }
            }
            /**
             * If the left arrow key is pressed, move the slideshow back.
             * If the right arrow key is pressed, move the slideshow forward.
             * @function onArrowKey
             * @memberof slideshowDialogLink
             * @param {object} e Event object
             */
            function onArrowKey(e) {
                var keyCode = e.keyCode || e.which;
                if($ctrl.isDialogOpen && keyCode === 37) {
                    $ctrl.slidePrevious();
                } else if($ctrl.isDialogOpen && keyCode == 39) {
                    $ctrl.slideNext();
                }
                $scope.$apply();
            }
            /**
             * Close the menu when the escape key is pressed.
             * @function closeOnEscape
             * @memberof slideshowDialogLink
             * @param {Object} e event Object 
             */
            function closeOnEscape(e) {
                var keyCode = e.keyCode || e.which;
                if (keyCode === 27 && $ctrl.isDialogOpen) {
                    $ctrl.closeDialog();
                    $scope.$apply();
                }
            }
            /**
             * When menu is open pressing tab on the last link in the menu
             * will move focus to the top of the menu.
             * @function onTabGoToFirst
             * @memberof slideshowDialogLink
             * @param {Object} e event object
             */
            function onTabGoToFirst(e) {
                var keyCode = e.keyCode || e.which;
                if ($ctrl.isDialogOpen && keyCode === 9 && !e.shiftKey) {
                    dialogWindow.find('button').first().focus();
                    e.preventDefault();
                }
            }
        }
    }
})();