Issue #1959306 by jessebeach: Drupal.announce does not handle multiple messages at the same time; Improve the utility so that simultaneously calls are read.
parent
ea38078c81
commit
7981d5d07b
|
@ -0,0 +1,106 @@
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
// Create only one aria-live element.
|
||||||
|
if (!liveElement) {
|
||||||
|
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.
|
||||||
|
for (var i = 0, il = announcements.length; i < il; i++) {
|
||||||
|
announcement = announcements.pop();
|
||||||
|
text.unshift(announcement.text);
|
||||||
|
// If any of the announcements has a priority of assertive then the group
|
||||||
|
// of joined announcements will have this priority.
|
||||||
|
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('\n');
|
||||||
|
// 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));
|
|
@ -258,66 +258,6 @@ Drupal.t = function (str, args, options) {
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an HTML element and method to trigger audio UAs to read system messages.
|
|
||||||
*/
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the URL to a Drupal page.
|
* Returns the URL to a Drupal page.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -110,6 +110,7 @@ function contextual_library_info() {
|
||||||
array('system', 'jquery.once'),
|
array('system', 'jquery.once'),
|
||||||
array('system', 'backbone'),
|
array('system', 'backbone'),
|
||||||
array('system', 'drupal.tabbingmanager'),
|
array('system', 'drupal.tabbingmanager'),
|
||||||
|
array('system', 'drupal.announce'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,7 @@ function overlay_library_info() {
|
||||||
array('system', 'drupalSettings'),
|
array('system', 'drupalSettings'),
|
||||||
array('system', 'drupal.displace'),
|
array('system', 'drupal.displace'),
|
||||||
array('system', 'drupal.tabbingmanager'),
|
array('system', 'drupal.tabbingmanager'),
|
||||||
|
array('system', 'drupal.announce'),
|
||||||
array('system', 'jquery.ui.core'),
|
array('system', 'jquery.ui.core'),
|
||||||
array('system', 'jquery.bbq'),
|
array('system', 'jquery.bbq'),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1252,6 +1252,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.
|
// Drupal's batch API.
|
||||||
$libraries['drupal.batch'] = array(
|
$libraries['drupal.batch'] = array(
|
||||||
'title' => 'Drupal batch API',
|
'title' => 'Drupal batch API',
|
||||||
|
|
Loading…
Reference in New Issue