Source:thumb-view.directive.js

(function () {
    'use strict';
    // Thumbnail view
    angular.module('mohistory').directive('thumbView', thumbView);
    thumbView.$inject = ['$timeout'];
    /**
     * Wrapper for the code required for the directive, including the controller,
     * as well as, a link function to handle keyboard listeners.
     * @memberof mohistory
     * @name thumbView
     * @ngdoc directive
     * @param $timeout AngularJS wrapper for the native window.setTimeout function.
     */
    function thumbView($timeout) {
        var directive = {
            templateUrl: 'app/components/thumb-view/thumb-view.directive.html',
            controller: thumbViewCtrl,
            controllerAs: 'thumbView',
            bindToController: true,
            scope: {
                context: '<',
                items: '<',
                addFacet: '<',
                isFacetSelected: '<',
                getInfiniteNextPage: '<',
                ISBusy: '<',
                endOfList: '<',
            },
            link: thumbViewLink,
            restrict: 'E',
        };
        return directive;
        function thumbViewCtrl() {
            var vm = this;
            /* ----- Variables ----- */
            vm.itemToShow = null;
            vm.itemDialogVisible = false;
            vm.locationLookup = {
                'MHS': 'Society',
                'LRC': 'Library',
                'MHM': 'Museum',
                'SM': 'Memorial',
                'Off-Site': 'Off-Site',
            };
            /* ----- Function Bindings ----- */
            vm.openItemDialog = openItemDialog;
            vm.closeItemDialog = closeItemDialog;
            vm.addFacetAndClose = addFacetAndClose;
            /**
             * Opens a dialog window populated with the selected item's details.
             * Triggered when a user clicks on a thumbnail image.
             * @memberof thumbView
             * @function openItemDialog
             * @param {object} item Object containing the item's details.
             */
            function openItemDialog(item) {
                vm.itemToShow = item;
                vm.itemDialogVisible = true;
            }
            /**
             * Closes the dialog window which was populated with a given item's details.
             * Triggered either by clicking the "X" icon, clicking on the grayed out background,
             * or hitting the escape key.
             * @memberof thumbView
             * @function closeItemDialog
             */
            function closeItemDialog() {
                vm.itemToShow = null;
                vm.itemDialogVisible = false;
            }
            /**
             * Close the dialog window and add selected facet to the hash.
             * @memberof thumbView
             * @function addFacetAndClose
             */
            function addFacetAndClose(facetType, currentFacet) {
                console.log('addFacetAndClose called')
                vm.closeItemDialog();
                vm.addFacet(facetType, currentFacet);
            }
        }
        /**
         * This link function for the Thumbnail view directive is used to implement
         * keyboard accessibility for the item dialog window. The dialog window traps
         * tab navigation and pressing escape exits the menu.
         * @memberof thumbView
         * @function thumbViewLink
         * @param {object} $scope an AngularJS scope object.
         * @param {object} $element the jqLite-wrapped element that this directive matches.
         * @param {object} $attrs a hash object with key-value pairs of normalized attribute 
         * names and their corresponding attribute values.
         * @param {object} $ctrl A reference to the directive's controller.
         */
        function thumbViewLink($scope, $element, $attrs, $ctrl) {
            /* ----- Variables ----- */
            var w = $(window);
            var body = $('body');
            var html = $('html');
            var page = $('html, body');
            var oldFocus = null;
            var dialogWindow = $('#search-result-dialog');
            var backToTop = $('#back-to-top');
            /* ----- Scope and DOM Listeners ----- */
            $scope.$watch(function () {
                return $ctrl.itemDialogVisible;
            }, toggleFocusLock);
            w.on('scroll', scrollManager);
            backToTop.on('click', animateToTop);
            /* --- Lifecycle Hook ----- */
            $scope.$on('$destroy', function () {
                // Allow scrolling
                body.removeClass('locked');
                html.removeClass('locked');
                // Clean up listeners
                w.off('scroll', scrollManager);
                backToTop.off('click', animateToTop);
                body.off('keyup', closeOnEscape);
                dialogWindow.find('#slideshow-dialog-close').off('keydown', onShiftTabGoToLast);
                dialogWindow.find('button').last().off('keydown', onTabGoToFirst);
            });
            /* ----- Function Definitions ----- */
            /**
             * Manages focus when the dialog window is toggled open or closed.
             * @memberof thumbViewLink
             * @function toggleFocusLock
             * @param {String} value true if the dialog window is open, false otherwise.
             */
            function toggleFocusLock(value) {
                value = Boolean(value);
                if (value) {
                    // Save current focused element, so we can return to it when the dialog is closed.
                    oldFocus = $(document.activeElement);
                    // Prevent main document from scrolling
                    body.addClass('locked');
                    html.addClass('locked');
                    // Set focus to the close button and set up listeners for elements internal to the
                    // dialog window. Since the window could be hidden/shown multiple times, these listeners
                    // are destroyed and recreated each time to prevent linking update issues.
                    $timeout(function () {
                        dialogWindow.find('#item-dialog-close').focus();
                        body.on('keyup', closeOnEscape);
                        dialogWindow.find('#item-dialog-close').on('keydown', onShiftTabGoToLast);
                        dialogWindow.find('button').last().on('keydown', onTabGoToFirst);
                    });
                } else {
                    // Allow main document scrolling
                    body.removeClass('locked');
                    html.removeClass('locked');
                    // Restore focus and remove listeners where appropriate.
                    $timeout(function () {
                        if (!$ctrl.itemDialogVisible && oldFocus) {
                            oldFocus.focus();
                            body.off('keyup', closeOnEscape);
                            dialogWindow.find('#slideshow-dialog-close').off('keydown', onShiftTabGoToLast);
                            dialogWindow.find('button').last().off('keydown', onTabGoToFirst);
                        }
                    })
                }
            }
            /**
             * Close the dialog window when escape is pressed.
             * @memberof thumbViewLink
             * @function closeOnEscape
             * @param {object} e event object
             */
            function closeOnEscape(e) {
                var keyCode = e.keyCode || e.which;
                if (keyCode === 27) {
                    $ctrl.closeItemDialog();
                    $scope.$apply();
                }
            }
            /**
             * Animate the scroll to the top of the page when the "to the top" button is pressed. Users can easily
             * interrupt this animation by using the scroll wheel.
             * @memberof thumbViewLink
             * @function animateToTop
             * @param {object} e event object
             */
            function animateToTop(e) {
                page.animate({
                    scrollTop: $('h1').first().offset().top - $('#site-navigation').outerHeight() - 10,
                }, 3000, function () {
                    page.off("scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove");
                });
            }
            /**
             * When the dialog window is open. Pressing shift+tab will adjust focus to the last
             * button in the dialog window.
             * @memberof thumbViewLink
             * @function onShiftTabGoToLast
             * @param {object} e event object
             */
            function onShiftTabGoToLast(e) {
                var keyCode = e.keyCode || e.which;
                if ($ctrl.itemDialogVisible && keyCode === 9 && e.shiftKey) {
                    dialogWindow.find('button').last().focus();
                    e.preventDefault();
                }
            }
            /**
             * When the dialog window is open pressing tab on the last button
             * will move focus to the first button in the window.
             * @memberof thumbViewLink
             * @function onTabGoToFirst
             * @param {Object} e event object
             */
            function onTabGoToFirst(e) {
                var keyCode = e.keyCode || e.which;
                if ($ctrl.itemDialogVisible && keyCode === 9 && !e.shiftKey) {
                    dialogWindow.find('button').first().focus();
                    e.preventDefault();
                }
            }
            /**
             * If the documents has been scrolled its full height go back 10px.
             * HACK: Infinite scroll was not loading new material if the scroll bar ever reached
             * bottom of the page. I added this, so the user could never reach the absolute bottom
             * of the page while infinite scroll was active.
             * @memberof thumbViewLink
             * @function scrollManager
             */
            function scrollManager() {
                if (w.scrollTop() + w.height() === getDocHeight()) {
                    w.scrollTop(w.scrollTop() - 1);
                }
            }
            /**
             * Helper function for scrollManager. Determines the document height with cross-browser
             * fallbacks.
             * @memberof thumbViewLink
             * @function getDocHeight
             */
            function getDocHeight() {
                var D = document;
                return Math.max(
                    D.body.scrollHeight, D.documentElement.scrollHeight,
                    D.body.offsetHeight, D.documentElement.offsetHeight,
                    D.body.clientHeight, D.documentElement.clientHeight
                );
            }
        }
    }
})();