(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
);
}
}
}
})();