From d44fbf87a2fde8a81a999fcb879cb6137adfc288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= Date: Tue, 2 Apr 2013 16:23:27 -0400 Subject: [PATCH] Issue #1959306 by jessebeach: Drupal.announce does not handle multiple messages at the same time; Improve the utility so that simultaneously calls are read. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: J. ReneĢe Beach --- core/misc/announce.js | 103 +++++++++++++++++++++++++++++++++++++ core/misc/drupal.js | 63 ----------------------- core/modules/system/system.module | 13 +++++ 3 files changed, 116 insertions(+), 63 deletions(-) create mode 100644 core/misc/announce.js diff --git a/core/misc/announce.js b/core/misc/announce.js new file mode 100644 index 0000000..4e45a21 --- /dev/null +++ b/core/misc/announce.js @@ -0,0 +1,103 @@ +/** + * Adds an HTML element and method to trigger audio UAs to read system messages. + * + * Use Drupal.announce() to indicate to screen reader users that an element on + * the page has changed state. For instance, if clicking a link loads 10 more + * items into a list, one might announce the change like this. + * $('#search-list') + * .on('itemInsert', function (event, data) { + * // Insert the new items. + * $(data.container.el).append(data.items.el); + * // Announce the change to the page contents. + * Drupal.announce(Drupal.t('@count items added to @container', + * {'@count': data.items.length, '@container': data.container.title} + * )); + * }); + */ +(function (Drupal, debounce) { + + var liveElement; + var announcements = []; + + /** + * Builds a div element with the aria-live attribute and attaches it + * to the DOM. + */ + Drupal.behaviors.drupalAnnounce = { + attach: function (context, settings) { + liveElement = document.createElement('div'); + liveElement.id = 'drupal-live-announce'; + liveElement.className = 'element-invisible'; + liveElement.setAttribute('aria-live', 'polite'); + liveElement.setAttribute('aria-busy', 'false'); + document.body.appendChild(liveElement); + } + }; + + /** + * Concatenates announcements to a single string; appends to the live region. + */ + function announce () { + var text = []; + var priority = 'polite'; + var announcement; + + // Create an array of announcement strings to be joined and appended to the + // aria live region. + // + // If any of the announcements has a priority of assertive, the entire + // group of announcements will have this priority. + while (announcement = announcements.pop()) { + text.unshift(announcement.text); + if (announcement.priority === 'assertive') { + priority = 'assertive'; + } + } + + if (text.length) { + // Clear the liveElement so that repeated strings will be read. + liveElement.innerHTML = ''; + // Set the busy state to true until the node changes are complete. + liveElement.setAttribute('aria-busy', 'true'); + // Set the priority to assertive, or default to polite. + liveElement.setAttribute('aria-live', priority); + // Print the text to the live region. Text should be run through + // Drupal.t() before being passed to Drupal.announce(). + liveElement.innerHTML = text.join('. '); + // The live text area is updated. Allow the AT to announce the text. + liveElement.setAttribute('aria-busy', 'false'); + } + } + + /** + * Triggers audio UAs to read the supplied text. + * + * The aria-live region will only read the text that currently populates its + * text node. Replacing text quickly in rapid calls to announce results in + * only the text from the most recent call to Drupal.announce() being read. + * By wrapping the call to announce in a debounce function, we allow for + * time for multiple calls to Drupal.announce() to queue up their messages. + * These messages are then joined and append to the aria-live region as one + * text node. + * + * @param String text + * A string to be read by the UA. + * @param String priority + * A string to indicate the priority of the message. Can be either + * 'polite' or 'assertive'. Polite is the default. + * + * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops + */ + Drupal.announce = function (text, priority) { + // Save the text and priority into a closure variable. Multiple simultaneous + // announcements will be concatenated and read in sequence. + announcements.push({ + text: text, + priority: priority + }); + // Immediately invoke the function that debounce returns. 200 ms is right at + // the cusp where humans notice a pause, so we will wait + // at most this much time before the set of queued announcements is read. + return (debounce(announce, 200)()); + }; +}(Drupal, Drupal.debounce)); diff --git a/core/misc/drupal.js b/core/misc/drupal.js index d7a4e40..627e264 100644 --- a/core/misc/drupal.js +++ b/core/misc/drupal.js @@ -259,69 +259,6 @@ Drupal.t = function (str, args, options) { }; /** - * Adds an HTML element and method to trigger audio UAs to read system messages. - */ -(function (document, Drupal) { - - var liveElement; - - /** - * Builds a div element with the aria-live attribute and attaches it - * to the DOM. - */ - Drupal.behaviors.drupalAnnounce = { - attach: function (settings, context) { - liveElement = document.createElement('div'); - liveElement.id = 'drupal-live-announce'; - liveElement.className = 'element-invisible'; - liveElement.setAttribute('aria-live', 'polite'); - liveElement.setAttribute('aria-busy', 'false'); - document.body.appendChild(liveElement); - } - }; - - /** - * Triggers audio UAs to read the supplied text. - * - * @param {String} text - * - A string to be read by the UA. - * - * @param {String} priority - * - A string to indicate the priority of the message. Can be either - * 'polite' or 'assertive'. Polite is the default. - * - * Use Drupal.announce to indicate to screen reader users that an element on - * the page has changed state. For instance, if clicking a link loads 10 more - * items into a list, one might announce the change like this. - * $('#search-list') - * .on('itemInsert', function (event, data) { - * // Insert the new items. - * $(data.container.el).append(data.items.el); - * // Announce the change to the page contents. - * Drupal.announce(Drupal.t('@count items added to @container', - * {'@count': data.items.length, '@container': data.container.title} - * )); - * }); - * - * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops - */ - Drupal.announce = function (text, priority) { - if (typeof text === 'string') { - // Clear the liveElement so that repeated strings will be read. - liveElement.innerHTML = ''; - // Set the busy state to true until the node changes are complete. - liveElement.setAttribute('aria-busy', 'true'); - // Set the priority to assertive, or default to polite. - liveElement.setAttribute('aria-live', (priority === 'assertive') ? 'assertive' : 'polite'); - // Print the text to the live region. - liveElement.innerHTML = Drupal.checkPlain(text); - // The live text area is updated. Allow the AT to announce the text. - liveElement.setAttribute('aria-busy', 'false'); - } - }; -}(document, Drupal)); - -/** * Returns the URL to a Drupal page. */ Drupal.url = function (path) { diff --git a/core/modules/system/system.module b/core/modules/system/system.module index c4304f4..84c220e 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1248,6 +1248,19 @@ function system_library_info() { ), ); + // Drupal's Screen Reader change announcement utility. + $libraries['drupal.announce'] = array( + 'title' => 'Drupal announce', + 'version' => VERSION, + 'js' => array( + 'core/misc/announce.js' => array('group' => JS_LIBRARY), + ), + 'dependencies' => array( + array('system', 'drupal'), + array('system', 'drupal.debounce'), + ), + ); + // Drupal's batch API. $libraries['drupal.batch'] = array( 'title' => 'Drupal batch API', -- 1.7.10.4