Logo de hearcolors


La ventana modal accesible en Acción

  • Descripción
  • Código HTML
  • Código JAVASCRIPT
  • Código CSS
Las ventanas modal accesibles necesitan contar con tres caracteristicas sumamente importantes, la primera es que permitan que al acceder a la ventana el foco entre directo a ella y no se mantenga fuera; la segunda es que una vez dentro, el foco no pueda salir de la ventana modal hasta cerrarla y finalmente, al cerrar la ventana, es necesario que el foco sea redirigido al último elemento navegado y no se mueva al inicio o al final de la página.

<div id="mainPage" aria-hidden="false">
            
    <h2>La ventana modal accesible en Acción</h2>
    <p><button id="startModal">Ver la ventana Modal</button></p>
      
</div>
        
<div id="modal" aria-hidden="true" role="dialog">

  <form name="form1" onSubmit="return false;">
    <p>
      <label for="firstName">Nombre:</label>
      <input type="text" name="firstName" id="firstName">
    </p>
    <p>
      <label for="lastName">Apellidos:</label>
      <input type="text" name="lastName" id="lastName">
    </p>
    <p>
      <label for="email">Correo Electrónico:</label>
      <input type="text" name="email" id="email">
    </p>
    <p>
      <input type="button" name="button" id="enter" value="Enviar">
      <input type="button" name="cancelButton" id="cancelButton" value="Cancelar">
        
    <button id="modalCloseButton" class="modalCloseButton" title="Cerrar"><img id="cancel" src="./content/modal_overlay/img/x.png" alt="Cerrar"></button>

  </form>
</div>

<div id="modalOverlay" tabindex="-1"></div>

           
// jQuery formatted selector to search for focusable items
var focusableElementsString = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";

// store the item that has focus before opening the modal window
var focusedElementBeforeModal;

$(document).ready(function() {
    jQuery('#startModal').click(function(e) {
        showModal($('#modal'));
    });
    jQuery('#cancel').click(function(e) {
        hideModal();
    });
    jQuery('#cancelButton').click(function(e) {
        hideModal();
    });
    jQuery('#enter').click(function(e) {
        enterButtonModal();
    });
    jQuery('#modalCloseButton').click(function(e) {
        hideModal();
    });
    jQuery('#modal').keydown(function(event) {
        trapTabKey($(this), event);
    })
    jQuery('#modal').keydown(function(event) {
        trapEscapeKey($(this), event);
    })

});


function trapSpaceKey(obj, evt, f) {
    // if space key pressed
    if (evt.which == 32) {
        // fire the user passed event
        f();
        evt.preventDefault();
    }
}

function trapEscapeKey(obj, evt) {

    // if escape pressed
    if (evt.which == 27) {

        // get list of all children elements in given object
        var o = obj.find('*');

        // get list of focusable items
        var cancelElement;
        cancelElement = o.filter("#cancel")

        // close the modal window
        cancelElement.click();
        evt.preventDefault();
    }

}

function trapTabKey(obj, evt) {

    // if tab or shift-tab pressed
    if (evt.which == 9) {

        // get list of all children elements in given object
        var o = obj.find('*');

        // get list of focusable items
        var focusableItems;
        focusableItems = o.filter(focusableElementsString).filter(':visible')

        // get currently focused item
        var focusedItem;
        focusedItem = jQuery(':focus');

        // get the number of focusable items
        var numberOfFocusableItems;
        numberOfFocusableItems = focusableItems.length

        // get the index of the currently focused item
        var focusedItemIndex;
        focusedItemIndex = focusableItems.index(focusedItem);

        if (evt.shiftKey) {
            //back tab
            // if focused on first item and user preses back-tab, go to the last focusable item
            if (focusedItemIndex == 0) {
                focusableItems.get(numberOfFocusableItems - 1).focus();
                evt.preventDefault();
            }

        } else {
            //forward tab
            // if focused on the last item and user preses tab, go to the first focusable item
            if (focusedItemIndex == numberOfFocusableItems - 1) {
                focusableItems.get(0).focus();
                evt.preventDefault();
            }
        }
    }

}

function setInitialFocusModal(obj) {
    // get list of all children elements in given object
    var o = obj.find('*');

    // set focus to first focusable item
    var focusableItems;
    focusableItems = o.filter(focusableElementsString).filter(':visible').first().focus();

}

function enterButtonModal() {
    // BEGIN logic for executing the Enter button action for the modal window
    alert('form submitted');
    // END logic for executing the Enter button action for the modal window
    hideModal();
}

function showModal(obj) {
    jQuery('#mainPage').attr('aria-hidden', 'true'); // mark the main page as hidden
    jQuery('#modalOverlay').css('display', 'block'); // insert an overlay to prevent clicking and make a visual change to indicate the main apge is not available
    jQuery('#modal').css('display', 'block'); // make the modal window visible
    jQuery('#modal').attr('aria-hidden', 'false'); // mark the modal window as visible

    // save current focus
    focusedElementBeforeModal = jQuery(':focus');

    // get list of all children elements in given object
    var o = obj.find('*');

    // Safari and VoiceOver shim
    // if VoiceOver in Safari is used, set the initial focus to the modal window itself instead of the first keyboard focusable item. This causes VoiceOver to announce the aria-labelled attributes. Otherwise, Safari and VoiceOver will not announce the labels attached to the modal window.

    // set the focus to the first keyboard focusable item
    o.filter(focusableElementsString).filter(':visible').first().focus();


}

function hideModal() {
    jQuery('#modalOverlay').css('display', 'none'); // remove the overlay in order to make the main screen available again
    jQuery('#modal').css('display', 'none'); // hide the modal window
    jQuery('#modal').attr('aria-hidden', 'true'); // mark the modal window as hidden
    jQuery('#mainPage').attr('aria-hidden', 'false'); // mark the main page as visible

    // set focus back to element that had it before the modal was opened
    focusedElementBeforeModal.focus();
}
             

#modalOverlay {
  width:100%;
  height:100%;
  z-index:2; /* places the modal overlay between the main page and the modal dialog*/
  background-color:#000;
  opacity:0.5;
  position:fixed;
  top:0;
  left:0;
  display:none;
  margin:0;
  padding:0;
}

#modal {
  width:50%;
  margin-left:auto;
  margin-right:auto;
  padding: 5px;
  border: thin #000 solid;
  background-color:#fff;
  z-index:3; /* places the modal dialog on top of everything else */
  position:fixed;
  top:25%;
  left:25%;
  display:none;
}
#modal h1 {
  text-align:center;
}

.modalCloseButton {
  float:right;
  position:absolute;
  top:10px;
  left:92%;
  height:25px;
}
.modalCloseButton img {
  border:0;
}

.screen-reader-offscreen {
  position:absolute;
  left:-999px;
  width:1px;
  height:1px;
  top:auto;
}