diff --git a/core/lib/Drupal/Core/Render/Element/StatusMessages.php b/core/lib/Drupal/Core/Render/Element/StatusMessages.php index 530457fd90..2a6e64f35f 100644 --- a/core/lib/Drupal/Core/Render/Element/StatusMessages.php +++ b/core/lib/Drupal/Core/Render/Element/StatusMessages.php @@ -73,21 +73,18 @@ public static function generatePlaceholder(array $element) { * @see drupal_get_messages() */ 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/message.es6.js b/core/misc/message.es6.js new file mode 100644 index 0000000000..a757f1d310 --- /dev/null +++ b/core/misc/message.es6.js @@ -0,0 +1,231 @@ +/** + * @file + * Message API. + */ +(((Drupal) => { + /** + * @typedef {object} Drupal.message~messageDefinition + */ + + /** + * Constructs a new instance of the Drupal.message object. + * + * 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 supplied, the default + * selector is used. + * + * @return {Drupal.message~messageDefinition} + * Object to add and remove messages. + */ + + Drupal.message = class Message { + constructor(messageWrapper = document.querySelector('[data-drupal-messages]')) { + this.messageWrapper = messageWrapper; + if (!this.messageWrapper) { + throw new Error(Drupal.t('There is no @element on the page.', { '@element': '[data-drupal-messages]' })); + } + } + + static getMessageTypes() { + 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 {string} [type=status] + * Message type, can be either 'status', 'error' or 'warning'. + * @param {object} [options] + * The context of the message, used for removing messages again. + * @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 a sort of tag for message deletion. + * @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(). + * + * @return {string} + * ID of message. + */ + add(message, type = 'status', options = {}) { + if (typeof message !== 'string') { + throw new Error('Message must be a string.'); + } + + // Send message to screen reader. + this.announce(message, type, options); + // Use the provided index for the message or generate a unique key to + // allow message deletion. + options.id = `${type} ${options.id || Math.random().toFixed(15).replace('0.', '')}`; + this.messageWrapper.appendChild(Drupal.theme('message', { text: message, type }, options)); + + return options.id; + } + + /** + * Select a set of messages based on type or index. + * + * @name Drupal.message~messageDefinition.select + * + * @param {string|Array.} index + * The message index or type to delete from the area. + * + * @return {NodeList|Array} + * Elements found. + */ + select(index) { + // When there are nothing to select, return an empty list. + if (!index || (Array.isArray(index) && index.length === 0)) { + return []; + } + + const whitespace = /\s+/; + const indexes = Array.isArray(index) ? index : [index]; + // If the index has spaces, add several data matches, the same way + const selectors = indexes.map(currentIndex => + // class names work. + currentIndex.trim() + .split(whitespace) + .map(i => `[data-drupal-message~="${i}"]`) + .join(''), + ); + + return this.messageWrapper.querySelectorAll(selectors.join(',')); + } + + /** + * Helper to remove elements. + * + * @param {NodeList|Array.} elements + * DOM Nodes to be removed. + * + * @return {number} + * Number of removed nodes. + */ + removeElements(elements) { + if (!elements || !elements.length) { + return 0; + } + + const length = elements.length; + for (let i = 0; i < length; i++) { + this.messageWrapper.removeChild(elements[i]); + } + return length; + } + + /** + * Removes messages from the message area. + * + * @name Drupal.message~messageDefinition.remove + * + * @param {string|Array.} options + * Index of the message to remove, as returned by + * {@link Drupal.message~messageDefinition.add}, or an + * array of indexes. + * + * @return {number} + * Number of removed messages. + */ + remove(options) { + const messages = this.select(options); + return this.removeElements(messages); + } + + /** + * Removes all messages from the message area. + * + * @name Drupal.message~messageDefinition.clear + * + * @return {number} + * Number of removed messages. + */ + clear() { + const messages = this.messageWrapper.querySelectorAll('[data-drupal-message]'); + return this.removeElements(messages); + } + + /** + * Helper to call Drupal.announce() with the right parameters. + * + * @param {string} message + * Displayed message. + * @param {string} type + * Message type, can be either 'status', 'error' or 'warning'. + * @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(). + */ + announce(message, type, options) { + if (!options.priority && (type === 'warning' || 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); + } + } + }; + + /** + * 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 {object} options + * The message context. + * @param {string} options.id + * ID of the message, for reference. + * + * @return {HTMLElement} + * A DOM Node. + */ + Drupal.theme.message = (message, options) => { + const messagesTypes = Drupal.message.getMessageTypes(); + const messageWraper = document.createElement('div'); + let messageText = message.text; + messageWraper.setAttribute('class', `messages messages--${message.type}`); + messageWraper.setAttribute('role', 'contentinfo'); + messageWraper.setAttribute('data-drupal-message', options.id); + if (message.type in messagesTypes) { + messageWraper.setAttribute('aria-label', messagesTypes[message.type]); + messageText = `

${messagesTypes[message.type]}

\n${message.text}`; + } + + // Alerts have a different HTML structure + if (message.type === 'error') { + const ariaAlert = document.createElement('div'); + ariaAlert.setAttribute('role', 'alert'); + ariaAlert.innerHTML = messageText; + messageWraper.appendChild(ariaAlert); + } + else { + messageWraper.innerHTML = messageText; + } + + return messageWraper; + }; +})(Drupal)); diff --git a/core/misc/message.js b/core/misc/message.js index 2b3daaed2e..1f57936569 100644 --- a/core/misc/message.js +++ b/core/misc/message.js @@ -1,253 +1,133 @@ /** - * @file - * Message API. - */ +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + (function (Drupal) { - 'use strict'; + Drupal.message = function () { + function Message() { + var messageWrapper = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document.querySelector('[data-drupal-messages]'); - var defaultMessageWrapperSelector = '[data-drupal-messages]'; - var messagesTypes = { - status: Drupal.t('Status message'), - error: Drupal.t('Error message'), - warning: Drupal.t('Warning message') - }; + _classCallCheck(this, Message); - /** - * @typedef {object} Drupal.message~messageDefinition - */ - - /** - * Constructs a new instance of the Drupal.message object. - * - * 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 supplied, the default - * selector is used. - * - * @return {Drupal.message~messageDefinition} - * Object to add and remove messages. - */ - Drupal.message = function (messageWrapper) { - // If an argument is passed, verify that it's a DOM Node of some kind. - if (arguments.length === 1) { - if (!messageWrapper || messageWrapper.nodeType !== 1) { - throw new Error(Drupal.t('Drupal.message() expect an HTMLElement as parameter.')); - } - } - else { - messageWrapper = document.querySelector(defaultMessageWrapperSelector); - if (!messageWrapper) { - throw new Error(Drupal.t('There is no @element on the page.', {'@element': defaultMessageWrapperSelector})); + this.messageWrapper = messageWrapper; + if (!this.messageWrapper) { + throw new Error(Drupal.t('There is no @element on the page.', { '@element': '[data-drupal-messages]' })); } } - /** - * Sequentially adds a message to the message area. - * - * @name Drupal.message~messageDefinition.add - * - * @param {string} message - * The message to display - * @param {string} [type=status] - * Message type, can be either 'status', 'error' or 'warning'. - * @param {object} [options] - * The context of the message, used for removing messages again. - * @param {string} [options.index] - * The message index, it can be a simple value: `'filevalidationerror'` - * or several values separated by a space: `'mymodule formvalidation'` - * which can be used as a sort of tag for message deletion. - * @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(). - * - * @return {string} - * Index of message. - */ - function messageAdd(message, type, options) { - if (typeof message !== 'string') { - throw new Error('Message must be a string.'); - } - type = type || 'status'; - options = options || {}; - // Send message to screen reader. - announce(message, type, options); - // Use the provided index for the message or generate a unique key to - // allow message deletion. - options.index = type + ' ' + (options.index || Math.random().toFixed(15).replace('0.', '')); - messageWrapper.appendChild(Drupal.theme('message', {text: message, type: type}, options)); - - return options.index; - } + _createClass(Message, [{ + key: 'add', + value: function add(message) { + var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'status'; + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - /** - * Select a set of messages based on type or index. - * - * @name Drupal.message~messageDefinition.select - * - * @param {string|Array.} index - * The message index or type to delete from the area. - * - * @return {NodeList|Array} - * Elements found. - */ - function messageSelect(index) { - var selectors; - - // When there are nothing to select, return an empty list. - if (!index || (Array.isArray(index) && index.length === 0)) { - return []; - } + if (typeof message !== 'string') { + throw new Error('Message must be a string.'); + } - var whitespace = /\s+/; - var indexes = Array.isArray(index) ? index : [index]; - selectors = indexes.map(function (currentIndex) { - // If the index has spaces, add several data matches, the same way - // class names work. - return currentIndex.trim() - .split(whitespace) - .map(function (i) { - return '[data-drupal-message~="' + i + '"]'; - }) - .join(''); - }); + this.announce(message, type, options); - return messageWrapper.querySelectorAll(selectors.join(',')); - } + options.id = type + ' ' + (options.id || Math.random().toFixed(15).replace('0.', '')); + this.messageWrapper.appendChild(Drupal.theme('message', { text: message, type: type }, options)); - /** - * Helper to remove elements. - * - * @param {NodeList|Array.} elements - * DOM Nodes to be removed. - * - * @return {number} - * Number of removed nodes. - */ - function removeElements(elements) { - if (!elements || !elements.length) { - return 0; + return options.id; } + }, { + key: 'select', + value: function select(index) { + if (!index || Array.isArray(index) && index.length === 0) { + return []; + } + + var whitespace = /\s+/; + var indexes = Array.isArray(index) ? index : [index]; + + var selectors = indexes.map(function (currentIndex) { + return currentIndex.trim().split(whitespace).map(function (i) { + return '[data-drupal-message~="' + i + '"]'; + }).join(''); + }); - var length = elements.length; - for (var i = 0; i < length; i++) { - messageWrapper.removeChild(elements[i]); + return this.messageWrapper.querySelectorAll(selectors.join(',')); } - return length; - } - - /** - * Removes messages from the message area. - * - * @name Drupal.message~messageDefinition.remove - * - * @param {string|Array.} index - * Index of the message to remove, as returned by - * {@link Drupal.message~messageDefinition.add}, or an - * array of indexes. - * - * @return {number} - * Number of removed messages. - */ - function messageRemove(options) { - var messages = messageSelect(options); - return removeElements(messages); - } - - /** - * Removes all messages from the message area. - * - * @name Drupal.message~messageDefinition.clear - * - * @param {string} [type] - * The type of message to clear, default to all messages. - * - * @return {number} - * Number of removed messages. - */ - function messageClear(type) { - var messages = messageWrapper.querySelectorAll('[data-drupal-message]'); - return removeElements(messages); - } + }, { + key: 'removeElements', + value: function removeElements(elements) { + if (!elements || !elements.length) { + return 0; + } + + var length = elements.length; + for (var i = 0; i < length; i++) { + this.messageWrapper.removeChild(elements[i]); + } + return length; + } + }, { + key: 'remove', + value: function remove(options) { + var messages = this.select(options); + return this.removeElements(messages); + } + }, { + key: 'clear', + value: function clear() { + var messages = this.messageWrapper.querySelectorAll('[data-drupal-message]'); + return this.removeElements(messages); + } + }, { + key: 'announce', + value: function announce(message, type, options) { + if (!options.priority && (type === 'warning' || type === 'error')) { + options.priority = 'assertive'; + } + + if (options.announce !== '') { + Drupal.announce(options.announce || message, options.priority); + } + } + }], [{ + key: 'getMessageTypes', + value: function getMessageTypes() { + return { + status: Drupal.t('Status message'), + error: Drupal.t('Error message'), + warning: Drupal.t('Warning message') + }; + } + }]); - return { - add: messageAdd, - select: messageSelect, - remove: messageRemove, - clear: messageClear - }; - }; + return Message; + }(); - /** - * Helper to call Drupal.announce() with the right parameters. - * - * @param {string} message - * Displayed message. - * @param {string} type - * Message type, can be either 'status', 'error' or 'warning'. - * @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(). - */ - function announce(message, type, options) { - if (!options.priority && (type === 'warning' || 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); - } - } - - /** - * 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 {object} options - * The message context. - * @param {string} options.index - * Index of the message, for reference. - * - * @return {HTMLElement} - * A DOM Node. - */ Drupal.theme.message = function (message, options) { - var messageText = message.text; + var messagesTypes = Drupal.message.getMessageTypes(); var messageWraper = document.createElement('div'); + var messageText = message.text; messageWraper.setAttribute('class', 'messages messages--' + message.type); messageWraper.setAttribute('role', 'contentinfo'); - messageWraper.setAttribute('data-drupal-message', options.index); + messageWraper.setAttribute('data-drupal-message', options.id); if (message.type in messagesTypes) { messageWraper.setAttribute('aria-label', messagesTypes[message.type]); messageText = '

' + messagesTypes[message.type] + '

\n' + message.text; } - // Alerts have a different HTML structure if (message.type === 'error') { var ariaAlert = document.createElement('div'); ariaAlert.setAttribute('role', 'alert'); ariaAlert.innerHTML = messageText; messageWraper.appendChild(ariaAlert); - } - else { + } else { messageWraper.innerHTML = messageText; } return messageWraper; }; - -}(Drupal)); +})(Drupal); \ No newline at end of file diff --git a/core/modules/system/tests/modules/js_message_test/js/js_message_test.js b/core/modules/system/tests/modules/js_message_test/js/js_message_test.js index b4bc8b9bdb..27400dfe61 100644 --- a/core/modules/system/tests/modules/js_message_test/js/js_message_test.js +++ b/core/modules/system/tests/modules/js_message_test/js/js_message_test.js @@ -11,7 +11,7 @@ var messageIndexes = {multiple: []}; drupalSettings.testMessages.selectors.forEach(function (selector) { - messageObjects[selector] = Drupal.message(document.querySelector(selector)); + messageObjects[selector] = new Drupal.message(document.querySelector(selector)); messageIndexes[selector] = {}; drupalSettings.testMessages.types.forEach(function (type) {