255 lines
7.6 KiB
JavaScript
255 lines
7.6 KiB
JavaScript
/**
|
|
* @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);
|