(function ($) {
var focusableElementsSelector = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"; // Based on the incredible accessible modal dialog. window.AccessibleDialog = function(modalDiv, $returnElement, dialogRole, title, $descDiv, closeButtonLabel, width, fullscreen, escapeHook) { this.title = title; this.closeButtonLabel = closeButtonLabel; this.focusedElementBeforeModal = $returnElement; this.escapeHook = escapeHook; this.baseId = $(modalDiv).attr('id') || Math.floor(Math.random() * 1000000000).toString(); var thisObj = this; var modal = modalDiv; this.modal = modal; modal.css({ 'width': width || '50%', 'top': (fullscreen ? '0' : '5%') }); modal.addClass('able-modal-dialog'); if (!fullscreen) { var closeButton = $('<button>',{ 'class': 'modalCloseButton', 'title': thisObj.closeButtonLabel, 'aria-label': thisObj.closeButtonLabel }).text('X'); closeButton.keydown(function (e) { // Space key down if (e.which === 32) { thisObj.hide(); } }).click(function () { thisObj.hide(); }); var titleH1 = $('<h1></h1>'); titleH1.attr('id', 'modalTitle-' + this.baseId); titleH1.css('text-align', 'center'); titleH1.text(title); $descDiv.attr('id', 'modalDesc-' + this.baseId); modal.attr({ 'aria-labelledby': 'modalTitle-' + this.baseId, 'aria-describedby': 'modalDesc-' + this.baseId }); modal.prepend(titleH1); modal.prepend(closeButton); } modal.attr({ 'aria-hidden': 'true', 'role': dialogRole }); modal.keydown(function (e) { // Escape if (e.which === 27) { if (thisObj.escapeHook) { thisObj.escapeHook(e, this); } else { thisObj.hide(); e.preventDefault(); } } // Tab else if (e.which === 9) { // Manually loop tab navigation inside the modal. var parts = modal.find('*'); var focusable = parts.filter(focusableElementsSelector).filter(':visible'); if (focusable.length === 0) { return; } var focused = $(':focus'); var currentIndex = focusable.index(focused); if (e.shiftKey) { // If backwards from first element, go to last. if (currentIndex === 0) { focusable.get(focusable.length - 1).focus(); e.preventDefault(); } } else { if (currentIndex === focusable.length - 1) { focusable.get(0).focus(); e.preventDefault(); } } } e.stopPropagation(); }); $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('aria-hidden', 'false'); }; AccessibleDialog.prototype.show = function () { if (!this.overlay) { // Generate overlay. var overlay = $('<div></div>').attr({ 'class': 'able-modal-overlay', 'tabindex': '-1' }); this.overlay = overlay; $('body').append(overlay); // Keep from moving focus out of dialog when clicking outside of it. overlay.on('mousedown.accessibleModal', function (e) { e.preventDefault(); }); } $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('aria-hidden', 'true'); this.overlay.css('display', 'block'); this.modal.css('display', 'block'); this.modal.attr({ 'aria-hidden': 'false', 'tabindex': '-1' }); var focusable = this.modal.find("*").filter(focusableElementsSelector).filter(':visible'); if (focusable.length === 0) { this.focusedElementBeforeModal.blur(); } var thisObj = this; setTimeout(function () { // originally set focus on the first focusable element // thisObj.modal.find('button.modalCloseButton').first().focus(); // but setting focus on dialog seems to provide more reliable access to ALL content within thisObj.modal.focus(); }, 300); }; AccessibleDialog.prototype.hide = function () { if (this.overlay) { this.overlay.css('display', 'none'); } this.modal.css('display', 'none'); this.modal.attr('aria-hidden', 'true'); $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('aria-hidden', 'false'); this.focusedElementBeforeModal.focus(); };
})(jQuery);