diff options
author | Dries Buytaert <dries@buytaert.net> | 2010-10-30 03:44:37 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2010-10-30 03:44:37 +0000 |
commit | 6009496d8e0a56b738200a6c17d3a4fd5bfe41a5 (patch) | |
tree | 2c2ea03da7bec0cdc08d6be492f2320c89f39e91 /modules/overlay | |
parent | a201a78234ddca624a77f17a96a160ab3098f644 (diff) | |
download | brdo-6009496d8e0a56b738200a6c17d3a4fd5bfe41a5.tar.gz brdo-6009496d8e0a56b738200a6c17d3a4fd5bfe41a5.tar.bz2 |
- Patch #841184 by ksenzee, aaronbauman, aspilicious: 'Skip to main content' link doesn't work correctly in the overlay.
Diffstat (limited to 'modules/overlay')
-rw-r--r-- | modules/overlay/overlay-parent.js | 186 |
1 files changed, 147 insertions, 39 deletions
diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js index 04a5fa74c..a4eb5aaa2 100644 --- a/modules/overlay/overlay-parent.js +++ b/modules/overlay/overlay-parent.js @@ -7,6 +7,10 @@ */ Drupal.behaviors.overlayParent = { attach: function (context, settings) { + if (Drupal.overlay.isOpen) { + Drupal.overlay.makeDocumentUntabbable(context); + } + if (this.processed) { return; } @@ -79,6 +83,7 @@ Drupal.overlay.open = function (url) { this.isOpening = false; this.isOpen = true; $(document.documentElement).addClass('overlay-open'); + this.makeDocumentUntabbable(); // Allow other scripts to respond to this event. $(document).trigger('drupalOverlayOpen'); @@ -121,8 +126,7 @@ Drupal.overlay.create = function () { .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage')) .bind('drupalOverlayBeforeClose' + eventClass + ' drupalOverlayBeforeLoad' + eventClass + - ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent')) - .bind('keydown' + eventClass, $.proxy(this, 'eventhandlerRestrictKeyboardNavigation')); + ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent')); if ($('.overlay-displace-top, .overlay-displace-bottom').length) { $(document) @@ -158,8 +162,6 @@ Drupal.overlay.load = function (url) { // entry using URL fragments. iframeDocument.location.replace(url); - // Immediately move the focus to the iframe. - this.inactiveFrame.focus(); return true; }; @@ -188,6 +190,7 @@ Drupal.overlay.close = function () { $(document.documentElement).removeClass('overlay-open'); // Restore the original document title. document.title = this.originalTitle; + this.makeDocumentTabbable(); // Allow other scripts to respond to this event. $(document).trigger('drupalOverlayClose'); @@ -206,8 +209,6 @@ Drupal.overlay.close = function () { */ Drupal.overlay.destroy = function () { $([document, window]).unbind('.drupal-overlay-open'); - this.$iframeA.unbind('.drupal-overlay'); - this.$iframeB.unbind('.drupal-overlay'); this.$container.remove(); this.$container = null; @@ -271,7 +272,7 @@ Drupal.overlay.loadChild = function (event) { this.isLoading = false; $(document.documentElement).removeClass('overlay-loading'); - event.data.sibling.removeClass('overlay-active'); + event.data.sibling.removeClass('overlay-active').attr({ 'tabindex': -1 }); // Only continue when overlay is still open and not closing. if (this.isOpen && !this.isClosing) { @@ -283,12 +284,18 @@ Drupal.overlay.loadChild = function (event) { this.activeFrame = $(iframe) .addClass('overlay-active') // Add a title attribute to the iframe for accessibility. - .attr('title', Drupal.t('@title dialog', { '@title': iframeWindow.jQuery('#overlay-title').text() })); + .attr('title', Drupal.t('@title dialog', { '@title': iframeWindow.jQuery('#overlay-title').text() })).removeAttr('tabindex'); this.inactiveFrame = event.data.sibling; // Load an empty document into the inactive iframe. (this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document).location.replace('about:blank'); + // Move the focus to just before the "skip to main content" link inside + // the overlay. + this.activeFrame.focus(); + var skipLink = iframeWindow.jQuery('a:first'); + Drupal.overlay.setFocusBefore(skipLink, iframeWindow.document); + // Allow other scripts to respond to this event. $(document).trigger('drupalOverlayLoad'); } @@ -302,6 +309,35 @@ Drupal.overlay.loadChild = function (event) { }; /** + * Creates a placeholder element to receive document focus. + * + * Setting the document focus to a link will make it visible, even if it's a + * "skip to main content" link that should normally be visible only when the + * user tabs to it. This function can be used to set the document focus to + * just before such an invisible link. + * + * @param $element + * The jQuery element that should receive focus on the next tab press. + * @param document + * The iframe window element to which the placeholder should be added. The + * placeholder element has to be created inside the same iframe as the element + * it precedes, to keep IE happy. (http://bugs.jquery.com/ticket/4059) + */ +Drupal.overlay.setFocusBefore = function ($element, document) { + // Create an anchor inside the placeholder document. + var placeholder = document.createElement('a'); + var $placeholder = $(placeholder).addClass('element-invisible').attr('href', '#'); + // Put the placeholder where it belongs, and set the document focus to it. + $placeholder.insertBefore($element); + $placeholder.focus(); + // Make the placeholder disappear as soon as it loses focus, so that it + // doesn't appear in the tab order again. + $placeholder.one('blur', function () { + $(this).remove(); + }); +} + +/** * Check if the given link is in the administrative section of the site. * * @param url @@ -625,37 +661,6 @@ Drupal.overlay.eventhandlerRefreshPage = function (event) { }; /** - * Event handler: makes sure that when the overlay is open no elements (except - * for elements inside any displaced elements) of the parent document are - * reachable through keyboard (TAB) navigation. - * - * @param event - * Event being triggered, with the following restrictions: - * - event.type: keydown - * - event.currentTarget: document - */ -Drupal.overlay.eventhandlerRestrictKeyboardNavigation = function (event) { - if (!this.$tabbables) { - this.$tabbables = $(':tabbable'); - } - - if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) { - // Whenever the focus is not inside the overlay (or a displaced element) - // move the focus along until it is. - var direction = event.shiftKey ? -1 : 1; - var current = this.$tabbables.index(event.target); - var $allowedParent = '#overlay-container, .overlay-displace-top, .overlay-displace-bottom'; - if (current != -1 && this.$tabbables[current + direction] && !this.$tabbables.eq(current + direction).closest($allowedParent).length) { - while (this.$tabbables[current + direction] && !this.$tabbables.eq(current + direction).closest($allowedParent).length) { - current = current + direction; - } - // Move focus. - this.$tabbables.eq(current).focus(); - } - } -}; - -/** * Event handler: dispatches events to the overlay document. * * @param event @@ -813,6 +818,109 @@ Drupal.overlay.getDisplacement = function (region) { }; /** + * Makes elements outside the overlay unreachable via the tab key. + * + * @param context + * The part of the DOM that should have its tabindexes changed. Defaults to + * the entire page. + */ +Drupal.overlay.makeDocumentUntabbable = function (context) { + // Manipulating tabindexes is unacceptably slow in IE6 and IE7, so in those + // browsers, the underlying page will still be reachable via the tab key. + if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) { + return; + } + + context = context || document.body; + var $overlay, $tabbable, $hasTabindex; + + // Determine which elements on the page already have a tabindex. + $hasTabindex = $('[tabindex] :not(.overlay-element)', context); + // Record the tabindex for each element, so we can restore it later. + $hasTabindex.each(Drupal.overlay._recordTabindex); + // Add the tabbable elements from the current context to any that we might + // have previously recorded. + Drupal.overlay._hasTabindex = $hasTabindex.add(Drupal.overlay._hasTabindex); + + // Set tabindex to -1 on everything outside the overlay and toolbars, so that + // the underlying page is unreachable. + + // By default, browsers make a, area, button, input, object, select, textarea, + // and iframe elements reachable via the tab key. + $tabbable = $('a, area, button, input, object, select, textarea, iframe'); + // If another element (like a div) has a tabindex, it's also tabbable. + $tabbable = $tabbable.add($hasTabindex); + // Leave links inside the overlay and toolbars alone. + $overlay = $('.overlay-element, #overlay-container, .overlay-displace-top, .overlay-displace-bottom').find('*'); + $tabbable = $tabbable.not($overlay); + // We now have a list of everything in the underlying document that could + // possibly be reachable via the tab key. Make it all unreachable. + $tabbable.attr('tabindex', -1); +}; + +/** + * Restores the original tabindex value of a group of elements. + * + * @param context + * The part of the DOM that should have its tabindexes restored. Defaults to + * the entire page. + */ +Drupal.overlay.makeDocumentTabbable = function (context) { + // Manipulating tabindexes is unacceptably slow in IE6 and IE7. In those + // browsers, the underlying page was never made unreachable via tab, so + // there is no work to be done here. + if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) { + return; + } + + var $needsTabindex; + context = context || document.body; + + // Make the underlying document tabbable again by removing all existing + // tabindex attributes. + var $tabindex = $('[tabindex]', context); + if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) { + // removeAttr('tabindex') is broken in IE6-7, but the DOM function + // removeAttribute works. + var i; + var length = $tabindex.length; + for (i = 0; i < length; i++) { + $tabindex[i].removeAttribute('tabIndex'); + } + } + else { + $tabindex.removeAttr('tabindex'); + } + + // Restore the tabindex attributes that existed before the overlay was opened. + $needsTabindex = $(Drupal.overlay._hasTabindex, context); + $needsTabindex.each(Drupal.overlay._restoreTabindex); + Drupal.overlay._hasTabindex = Drupal.overlay._hasTabindex.not($needsTabindex); +}; + +/** + * Record the tabindex for an element, using $.data. + * + * Meant to be used as a jQuery.fn.each callback. + */ +Drupal.overlay._recordTabindex = function () { + var $element = $(this); + var tabindex = $(this).attr('tabindex'); + $element.data('drupalOverlayOriginalTabIndex', tabindex); +} + +/** + * Restore an element's original tabindex. + * + * Meant to be used as a jQuery.fn.each callback. + */ +Drupal.overlay._restoreTabindex = function () { + var $element = $(this); + var tabindex = $element.data('drupalOverlayOriginalTabIndex'); + $element.attr('tabindex', tabindex); +}; + +/** * Theme function to create the overlay iframe element. */ Drupal.theme.prototype.overlayContainer = function () { |