diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 5da51e8..cc3fd71 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -233,6 +233,15 @@ drupal.machine-name:
- core/drupalSettings
- core/drupal.form
+drupal.message:
+ version: VERSION
+ js:
+ misc/message.js: {}
+ dependencies:
+ - core/jquery
+ - core/drupal
+ - core/drupal.debounce
+
drupal.progress:
version: VERSION
js:
diff --git a/core/lib/Drupal/Core/Render/Element/StatusMessages.php b/core/lib/Drupal/Core/Render/Element/StatusMessages.php
index 530457f..cd3bf52 100644
--- a/core/lib/Drupal/Core/Render/Element/StatusMessages.php
+++ b/core/lib/Drupal/Core/Render/Element/StatusMessages.php
@@ -75,19 +75,17 @@ public static function generatePlaceholder(array $element) {
public static function renderMessages($type) {
$render = [];
$messages = drupal_get_messages($type);
- if ($messages) {
- // Render the messages.
- $render = [
- '#theme' => 'status_messages',
- // @todo Improve when https://www.drupal.org/node/2278383 lands.
- '#message_list' => $messages,
- '#status_headings' => [
- 'status' => t('Status message'),
- 'error' => t('Error message'),
- 'warning' => t('Warning message'),
- ],
- ];
- }
+ // Render the messages.
+ $render = [
+ '#theme' => 'status_messages',
+ // @todo Improve when https://www.drupal.org/node/2278383 lands.
+ '#message_list' => $messages,
+ '#status_headings' => [
+ 'status' => t('Status message'),
+ 'error' => t('Error message'),
+ 'warning' => t('Warning message'),
+ ],
+ ];
return $render;
}
diff --git a/core/misc/announce.js b/core/misc/announce.js
index acf850a..cd1ee5b 100644
--- a/core/misc/announce.js
+++ b/core/misc/announce.js
@@ -50,21 +50,23 @@
/**
* Concatenates announcements to a single string; appends to the live region.
*/
- function announce() {
+ function processAnnounce() {
var text = [];
var priority = 'polite';
var announcement;
- // Create an array of announcement strings to be joined and appended to the
- // aria live region.
- var il = announcements.length;
- for (var i = 0; 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 (announcements.length) {
+ // Create an array of announcement strings to be joined and appended to
+ // the aria live region.
+ var il = announcements.length;
+ for (var i = 0; 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';
+ }
}
}
@@ -81,8 +83,13 @@
// The live text area is updated. Allow the AT to announce the text.
liveElement.setAttribute('aria-busy', 'false');
}
+
}
+ // 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.
+ var debouncedProcessAnnounce = debounce(processAnnounce, 200);
+
/**
* Triggers audio UAs to read the supplied text.
*
@@ -96,25 +103,24 @@
*
* @param {string} text
* A string to be read by the UA.
- * @param {string} [priority='polite']
+ * @param {string} priority
* A string to indicate the priority of the message. Can be either
* 'polite' or 'assertive'.
*
- * @return {function}
- * The return of the call to debounce.
- *
* @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)());
+ if (typeof text === 'string') {
+ // 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
+ });
+ debouncedProcessAnnounce(announcements);
+ }
+ else {
+ throw new Error(Drupal.t('"text" passed Drupal.announce must be a string.'));
+ }
};
}(Drupal, Drupal.debounce));
diff --git a/core/misc/message.js b/core/misc/message.js
new file mode 100644
index 0000000..867bf67
--- /dev/null
+++ b/core/misc/message.js
@@ -0,0 +1,111 @@
+/**
+ * @file
+ * Message API.
+ */
+(function ($, Drupal, debounce) {
+
+ 'use strict';
+
+ var messages = [];
+ var messagesElement;
+ var debouncedProcessMessages;
+ var messageWrapperSelector = '[data-drupal-messages]';
+
+ /**
+ * Builds a div element with the aria-live attribute and attaches it to the
+ * DOM.
+ */
+ Drupal.behaviors.drupalMessage = {
+ attach: function () {
+ if (!messagesElement) {
+ var $messagesWrapper = $(messageWrapperSelector);
+ if (!$messagesWrapper.length) {
+ throw new Error(Drupal.t('There is no element with a [data-drupal-messages] attribute'));
+ }
+ messagesElement = $messagesWrapper[0];
+ }
+ }
+ };
+
+ // Debounce the function in case Drupal.message() is used in a loop.
+ debouncedProcessMessages = debounce(processMessages, 100);
+
+ /**
+ * Displays all queued messages in one pass instead of one after the other.
+ * @see debouncedProcessMessages
+ */
+ function processMessages() {
+ var text = [];
+
+ messages.reverse().forEach(function (message) {
+ text.unshift(Drupal.theme('message', message));
+ });
+ if (text.length) {
+ messagesElement.innerHTML += text.join('');
+ }
+ // Reset the messages array after the messages are processed.
+ messages = [];
+
+ }
+
+ Drupal.message = {};
+
+ /**
+ * Displays a message on the page.
+ *
+ * @param {string} message
+ * The message to display
+ * @param {string} type
+ * Message type, can be either 'status', 'error' or 'warning'.
+ * Default to 'status'
+ * @param {string} context
+ * The context of the message, used for removing messages again.
+ */
+ Drupal.message.add = function (message, type, context) {
+ if (typeof message === 'string') {
+ // Save the text and priority into a closure variable. Multiple
+ // simultaneous announcements will be concatenated and read in sequence.
+ messages.push({
+ context: context || 'default',
+ text: message,
+ type: type || 'status'
+ });
+ debouncedProcessMessages();
+ }
+ };
+
+ /**
+ * Removes messages from the page.
+ *
+ * @param {string} context
+ * The message context to remove
+ * @param {string} type
+ * Message type, can be either 'status', 'error' or 'warning'.
+ */
+ Drupal.message.remove = function (context, type) {
+ var selector = '.js-messages-context--' + (context || 'default');
+ if (type) {
+ selector = '.messages--' + (type || 'status') + selector;
+ }
+ $(selector).remove();
+ };
+
+ /**
+ * Theme function for a message.
+ *
+ * @param {object} message
+ * The message object.
+ * @param {string} message.text
+ * The message text.
+ * @param {string} message.type
+ * The message type.
+ * @param {string} message.context
+ * The message context.
+ * @return {string}
+ * A string representing a DOM fragment.
+ */
+ Drupal.theme.message = function (message) {
+ return '