Skip to content
Snippets Groups Projects
Commit b0877275 authored by Robert Lange's avatar Robert Lange Committed by Dorian Merz
Browse files

refs #17576 [master] adapt BARF keyboard and focus controllers from Micromodal

* backported for Vufind 5 to keep focus in modal when tabbing with keyboard
* also see https://github.com/vufind-org/vufind/pull/1667
parent 00f68faf
Branches
Tags
No related merge requests found
/*global recaptchaOnLoad, resetCaptcha, VuFind, TEMPORARY BARF CK*/ /*global grecaptcha, recaptchaOnLoad, resetCaptcha, VuFind, TEMPORARY BARF CK */
VuFind.register('lightbox', function Lightbox() { VuFind.register('lightbox', function Lightbox() {
// State // State
var _originalUrl = false; var _originalUrl = false;
...@@ -7,7 +7,7 @@ VuFind.register('lightbox', function Lightbox() { ...@@ -7,7 +7,7 @@ VuFind.register('lightbox', function Lightbox() {
var refreshOnClose = false; var refreshOnClose = false;
var _modalParams = {}; var _modalParams = {};
// Elements // Elements
var _modal, _modalBody, _modalTitle, _clickedButton = null; var _modal, _modalBody, _clickedButton = null;
// Utilities // Utilities
function _storeClickedStatus() { function _storeClickedStatus() {
_clickedButton = this; _clickedButton = this;
...@@ -15,10 +15,11 @@ VuFind.register('lightbox', function Lightbox() { ...@@ -15,10 +15,11 @@ VuFind.register('lightbox', function Lightbox() {
function _html(content) { function _html(content) {
_modalBody.html(content); _modalBody.html(content);
// Set or update title if we have one // Set or update title if we have one
if (_lightboxTitle) { var $h2 = _modalBody.find("h2:first-of-type");
_modalTitle.text(_lightboxTitle); if (_lightboxTitle && $h2) {
_lightboxTitle = false; $h2.text(_lightboxTitle);
} }
_lightboxTitle = false;
_modal.modal('handleUpdate'); _modal.modal('handleUpdate');
} }
function _emit(msg, _details) { function _emit(msg, _details) {
...@@ -329,7 +330,7 @@ VuFind.register('lightbox', function Lightbox() { ...@@ -329,7 +330,7 @@ VuFind.register('lightbox', function Lightbox() {
submit.attr('disabled', 'disabled'); submit.attr('disabled', 'disabled');
} }
// Store custom title // Store custom title
_lightboxTitle = submit.data('lightboxTitle') || $(form).data('lightboxTitle') || ''; _lightboxTitle = submit.data('lightbox-title') || $(form).data('lightbox-title') || false;
// Get Lightbox content // Get Lightbox content
ajax({ ajax({
url: $(form).attr('action') || _currentUrl, url: $(form).attr('action') || _currentUrl,
...@@ -343,6 +344,86 @@ VuFind.register('lightbox', function Lightbox() { ...@@ -343,6 +344,86 @@ VuFind.register('lightbox', function Lightbox() {
return false; return false;
}; };
/**
* Keyboard and focus controllers
* Adapted from Micromodal
* - https://github.com/ghosh/Micromodal/blob/master/lib/src/index.js
* FIXME: backported for VuFind 5, remove with Vufind 7
*/
var FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', 'select:not([disabled]):not([aria-hidden])', 'textarea:not([disabled]):not([aria-hidden])', 'button:not([disabled]):not([aria-hidden])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])'];
function getFocusableNodes () {
var nodes = _modal[0].querySelectorAll(FOCUSABLE_ELEMENTS);
return Array.apply(null, nodes);
}
/**
* Tries to set focus on a node which is not a close trigger
* if no other nodes exist then focuses on first close trigger
*/
function setFocusToFirstNode() {
var focusableNodes = getFocusableNodes();
// no focusable nodes
if (focusableNodes.length === 0) return;
// remove nodes on whose click, the modal closes
var nodesWhichAreNotCloseTargets = focusableNodes.filter(function(node) {
return !node.hasAttribute("data-lightbox-close") && (
!node.hasAttribute("data-dismiss") ||
node.getAttribute("data-dismiss") != "modal"
);
});
if (nodesWhichAreNotCloseTargets.length > 0) nodesWhichAreNotCloseTargets[0].focus();
if (nodesWhichAreNotCloseTargets.length === 0) focusableNodes[0].focus();
}
function retainFocus(event) {
var focusableNodes = getFocusableNodes();
// no focusable nodes
if (focusableNodes.length === 0) return;
/**
* Filters nodes which are hidden to prevent
* focus leak outside modal
*/
focusableNodes = focusableNodes.filter(function(node) {
return (node.offsetParent !== null);
});
// if disableFocus is true
if (!_modal[0].contains(document.activeElement)) {
focusableNodes[0].focus();
} else {
var focusedItemIndex = focusableNodes.indexOf(document.activeElement);
if (event.shiftKey && focusedItemIndex === 0) {
focusableNodes[focusableNodes.length - 1].focus();
event.preventDefault();
}
if (!event.shiftKey && focusableNodes.length > 0 && focusedItemIndex === focusableNodes.length - 1) {
focusableNodes[0].focus();
event.preventDefault();
}
}
}
function onKeydown(e) {
if (event.keyCode === 27) { // esc
close();
}
if (event.keyCode === 9) { // tab
retainFocus(event);
}
}
function bindFocus() {
document.addEventListener('keydown', onKeydown);
setFocusToFirstNode();
}
function unbindFocus() {
document.removeEventListener('keydown', onKeydown);
}
// Public: Attach listeners to the page // Public: Attach listeners to the page
function bind(el) { function bind(el) {
var target = el || document; var target = el || document;
...@@ -389,24 +470,28 @@ VuFind.register('lightbox', function Lightbox() { ...@@ -389,24 +470,28 @@ VuFind.register('lightbox', function Lightbox() {
_html(VuFind.translate('loading') + '...'); _html(VuFind.translate('loading') + '...');
_originalUrl = false; _originalUrl = false;
_currentUrl = false; _currentUrl = false;
_lightboxTitle = ''; _lightboxTitle = false;
_modalParams = {}; _modalParams = {};
} }
function init() { function init() {
_modal = $('#modal'); _modal = $('#modal');
_modalBody = _modal.find('.modal-body'); _modalBody = _modal.find('.modal-body');
_modalTitle = _modal.find("#modal-title");
_modal.on('hide.bs.modal', function lightboxHide() { _modal.on('hide.bs.modal', function lightboxHide() {
if (VuFind.lightbox.refreshOnClose) { if (VuFind.lightbox.refreshOnClose) {
VuFind.refreshPage(); VuFind.refreshPage();
} else {
unbindFocus();
this.setAttribute('aria-hidden', true);
_emit('VuFind.lightbox.closing');
} }
this.setAttribute('aria-hidden', true);
_emit('VuFind.lightbox.closing');
}); });
_modal.on('hidden.bs.modal', function lightboxHidden() { _modal.on('hidden.bs.modal', function lightboxHidden() {
VuFind.lightbox.reset(); VuFind.lightbox.reset();
_emit('VuFind.lightbox.closed'); _emit('VuFind.lightbox.closed');
}); });
_modal.on("shown.bs.modal", function lightboxShown() {
bindFocus();
});
VuFind.modal = function modalShortcut(cmd) { VuFind.modal = function modalShortcut(cmd) {
if (cmd === 'show') { if (cmd === 'show') {
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment