1

I have a modal window script that we are trying to bring up to speed for accessibility requirements. The requirement says that when you tab away from the window, it should close. It also says that upon dismissing the window, the original triggering element must get focus back.

After doing some digging I found this: jQuery figuring out if parent has lost 'focus'. It seemed that the easiest way to tell when you've tabbed away from the window is to track the focusin event, and when focus fires on an element that is not a child of the open modal, dismiss the window. This method however, is problematic as you'll see (not to mention a little too heavy for my liking). Here is the code that handles this:

$('body').focusin(function(e) {
        if (!$(e.target).parent().is('.current-window')) {
            closeModal();
        }
    });

and the function that closes the window:

function closeModal() {
        $('.modal-mask, .current-window').fadeOut('fast', function(){
            $(this).removeClass('current-window');
            $('.modal-mask').removeAttr('style');
            $('.modal-trigger').focus();
        });
    }

Now obviously, when I run this code, closeModal() fires back and forth between the focusin event up to the maximum call stack and therefore throws the "maximum call stack exceeded" error message before giving focus to the triggering element.

Refer to this fiddle for full code: http://jsfiddle.net/pbredenberg/wxX4T/

I'm trying to think of a better way to handle this requirement, or at the very least avoid the infinite loop. Can anyone point me in the right direction?

Community
  • 1
  • 1
pwbred
  • 123
  • 7
  • So `$('.modal-trigger').focus()` creates the modal? – Explosion Pills Oct 10 '12 at 14:54
  • No, if you check the fiddle I posted you'll see where the window is created. `$('.modal-trigger').focus()` gives focus back to the triggering element after the window is dismissed. – pwbred Oct 10 '12 at 15:05

2 Answers2

0

I am still not able to just comment, so I have to submit this as an answer: Why dont you track if you are actually closing the window with a bool var like window.closing? I updated the example: http://jsfiddle.net/kevkong/wxX4T/8/ Is this, what you intended to do?

Kevkong
  • 460
  • 3
  • 16
0

Whenever you open a modal, store a reference to the element that was clicked. Then when the modal is closed, you can retrieve that and re-focus the element.

Working demo: http://jsfiddle.net/wxX4T/12/

function closeModal(e) {
    if (e) e.preventDefault();

    // Rather than using a .current-window class
    // we can use the jQuery :visible pseudo-selector
    var $window = $(".window:visible");

    // Get our refernce to the focus target that was 
    // stored when the window was open.
    var $trigger = $window.data("focusTarget");

    // Close the window and mask
    $('.modal-mask, .window').fadeOut('fast', function() {
        // Focus on the original trigger
        $trigger.focus();
    });
}

$('.modal').click(function(e) {
    e.preventDefault();

    var $trigger = $(this);
    var $modal = $($trigger.attr("href")).fadeIn(300)
    $('.modal-mask').fadeIn(200);

    // I have omitted all the code that positions
    // the window (for the sake of clarity)
    // Feel free to add it back.
    // Store a reference to the trigger link in the modal's "data" hash 
    // so that when we close it later, we can retrieve the original 
    // trigger that opened the window.
    $modal.data("focusTarget", $trigger);
});


// Trigger window close based on Tab key
$(document).on("keydown", function(e) {
    // Only close modal if the tab key is pressed
    // AND if there is a visible modal window.
    if ((e.keyCode || e.which) === 9 && $(".window:visible").length > 0) {
        e.preventDefault();
        closeModal();
    }
});

$(document).on("click", ".window .close", closeModal);

$(document).on("click", ".modal-mask", function() {
    $(this).hide();
    closeModal();
});​
jessegavin
  • 74,067
  • 28
  • 136
  • 164