/**
* @file
* Message API.
*/
((Drupal) => {
/**
* @typedef {class} Drupal.Message~messageDefinition
*/
/**
* Constructs a new instance of the Drupal.Message class.
*
* This provides a uniform interface for adding and removing messages to a
* specific location on the page.
*
* @param {HTMLElement} messageWrapper
* The zone where to add messages. If no element is provided an attempt is
* made to determine a default location.
*
* @return {Drupal.Message~messageDefinition}
* Class to add and remove messages.
*/
Drupal.Message = class {
constructor(messageWrapper = null) {
if (!messageWrapper) {
this.messageWrapper = Drupal.Message.defaultWrapper();
} else {
this.messageWrapper = messageWrapper;
}
}
/**
* Attempt to determine the default location for
* inserting JavaScript messages or create one if needed.
*
* @return {HTMLElement}
* The default destination for JavaScript messages.
*/
static defaultWrapper() {
let wrapper = document.querySelector('[data-drupal-messages]');
if (!wrapper) {
wrapper = document.querySelector('[data-drupal-messages-fallback]');
wrapper.removeAttribute('data-drupal-messages-fallback');
wrapper.setAttribute('data-drupal-messages', '');
wrapper.classList.remove('hidden');
}
return wrapper.innerHTML === ''
? Drupal.Message.messageInternalWrapper(wrapper)
: wrapper.firstElementChild;
}
/**
* Provide an object containing the available message types.
*
* @return {Object}
* An object containing message type strings.
*/
static getMessageTypeLabels() {
return {
status: Drupal.t('Status message'),
error: Drupal.t('Error message'),
warning: Drupal.t('Warning message'),
};
}
/**
* Sequentially adds a message to the message area.
*
* @name Drupal.Message~messageDefinition.add
*
* @param {string} message
* The message to display
* @param {object} [options]
* The context of the message.
* @param {string} [options.id]
* The message ID, it can be a simple value: `'filevalidationerror'`
* or several values separated by a space: `'mymodule formvalidation'`
* which can be used as an explicit selector for a message.
* @param {string} [options.type=status]
* Message type, can be either 'status', 'error' or 'warning'.
* @param {string} [options.announce]
* Screen-reader version of the message if necessary. To prevent a message
* being sent to Drupal.announce() this should be an empty string.
* @param {string} [options.priority]
* Priority of the message for Drupal.announce().
*
* @return {string}
* ID of message.
*/
add(message, options = {}) {
if (!options.hasOwnProperty('type')) {
options.type = 'status';
}
if (typeof message !== 'string') {
throw new Error('Message must be a string.');
}
// Send message to screen reader.
Drupal.Message.announce(message, options);
/**
* Use the provided index for the message or generate a pseudo-random key
* to allow message deletion.
*/
options.id = options.id
? String(options.id)
: `${options.type}-${Math.random().toFixed(15).replace('0.', '')}`;
// Throw an error if an unexpected message type is used.
if (!Drupal.Message.getMessageTypeLabels().hasOwnProperty(options.type)) {
const { type } = options;
throw new Error(
`The message type, ${type}, is not present in Drupal.Message.getMessageTypeLabels().`,
);
}
this.messageWrapper.appendChild(
Drupal.theme('message', { text: message }, options),
);
return options.id;
}
/**
* Select a message based on id.
*
* @name Drupal.Message~messageDefinition.select
*
* @param {string} id
* The message id to delete from the area.
*
* @return {Element}
* Element found.
*/
select(id) {
return this.messageWrapper.querySelector(
`[data-drupal-message-id^="${id}"]`,
);
}
/**
* Removes messages from the message area.
*
* @name Drupal.Message~messageDefinition.remove
*
* @param {string} id
* Index of the message to remove, as returned by
* {@link Drupal.Message~messageDefinition.add}.
*
* @return {number}
* Number of removed messages.
*/
remove(id) {
return this.messageWrapper.removeChild(this.select(id));
}
/**
* Removes all messages from the message area.
*
* @name Drupal.Message~messageDefinition.clear
*/
clear() {
Array.prototype.forEach.call(
this.messageWrapper.querySelectorAll('[data-drupal-message-id]'),
(message) => {
this.messageWrapper.removeChild(message);
},
);
}
/**
* Helper to call Drupal.announce() with the right parameters.
*
* @param {string} message
* Displayed message.
* @param {object} options
* Additional data.
* @param {string} [options.announce]
* Screen-reader version of the message if necessary. To prevent a message
* being sent to Drupal.announce() this should be `''`.
* @param {string} [options.priority]
* Priority of the message for Drupal.announce().
* @param {string} [options.type]
* Message type, can be either 'status', 'error' or 'warning'.
*/
static announce(message, options) {
if (
!options.priority &&
(options.type === 'warning' || options.type === 'error')
) {
options.priority = 'assertive';
}
/**
* If screen reader message is not disabled announce screen reader
* specific text or fallback to the displayed message.
*/
if (options.announce !== '') {
Drupal.announce(options.announce || message, options.priority);
}
}
/**
* Function for creating the internal message wrapper element.
*
* @param {HTMLElement} messageWrapper
* The message wrapper.
*
* @return {HTMLElement}
* The internal wrapper DOM element.
*/
static messageInternalWrapper(messageWrapper) {
const innerWrapper = document.createElement('div');
innerWrapper.setAttribute('class', 'messages__wrapper');
messageWrapper.insertAdjacentElement('afterbegin', innerWrapper);
return innerWrapper;
}
};
/**
* Theme function for a message.
*
* @param {object} message
* The message object.
* @param {string} message.text
* The message text.
* @param {object} options
* The message context.
* @param {string} options.type
* The message type.
* @param {string} options.id
* ID of the message, for reference.
*
* @return {HTMLElement}
* A DOM Node.
*/
Drupal.theme.message = ({ text }, { type, id }) => {
const messagesTypes = Drupal.Message.getMessageTypeLabels();
const messageWrapper = document.createElement('div');
messageWrapper.setAttribute('class', `messages messages--${type}`);
messageWrapper.setAttribute(
'role',
type === 'error' || type === 'warning' ? 'alert' : 'status',
);
messageWrapper.setAttribute('data-drupal-message-id', id);
messageWrapper.setAttribute('data-drupal-message-type', type);
messageWrapper.setAttribute('aria-label', messagesTypes[type]);
messageWrapper.innerHTML = `${text}`;
return messageWrapper;
};
})(Drupal);