diff -u b/core/misc/message.js b/core/misc/message.js --- b/core/misc/message.js +++ b/core/misc/message.js @@ -26,11 +26,13 @@ * Object to add and remove messages. */ Drupal.message = function (messageWrapper) { - if ((messageWrapper === null || typeof messageWrapper === 'string') - || (arguments.length === 1 && typeof messageWrapper === 'undefined')) { - throw new Error(Drupal.t('Drupal.message() expect an HTMLElement as parameter.')); + // If an argument is passed, verify that it's a DOM Node of some kind. + if (arguments.length === 1) { + if (!messageWrapper || (messageWrapper && messageWrapper.nodeType !== 1)) { + throw new Error(Drupal.t('Drupal.message() expect an HTMLElement as parameter.')); + } } - if (!messageWrapper) { + else { messageWrapper = document.querySelector(defaultMessageWrapperSelector); if (!messageWrapper) { throw new Error(Drupal.t('There is no @element on the page.', {'@element': defaultMessageWrapperSelector})); @@ -38,7 +40,7 @@ } /** - * Sequentially adds a message to the defined location in the page. + * Sequentially adds a message to the message area. * * @name Drupal.message~messageDefinition.add * @@ -68,22 +70,23 @@ } /** - * Removes messages from the page. + * Removes messages from the message area. * * @name Drupal.message~messageDefinition.remove * - * @param {Number|String|Array.} [messages] + * @param {string|Array.} messages * Index of the message to remove, as returned by - * {@link Drupal.message~messageDefinition.add}, a number - * corresponding to the CSS index of the element, or an - * array containing a combination of the previous two types. + * {@link Drupal.message~messageDefinition.add}, or an + * array of indexes. * * @return {number} * Number of removed messages. */ function messageRemove(messages) { - if (!messages) { - throw new Error(Drupal.t('Object.message() expect a message to remove.')); + // If there is no argument or if the argument is an empty array, + // no message can be removed. + if (!messages || (messages && Array.isArray(messages) && messages.length === 0)) { + return 0; } var removeSelectors = (Array.isArray(messages) ? messages : [messages]) @@ -91,18 +94,43 @@ return '[data-drupal-message="' + messageIndex + '"]'; }); - var remove = messageWrapper.querySelectorAll(removeSelectors.join(', ')); - var length = remove.length; - for (var i = 0; i < length; i += 1) { - messageWrapper.removeChild(remove[i]); + var toRemove = Array.from(messageWrapper.querySelectorAll(removeSelectors.join(','))); + toRemove.forEach(function (element) { + messageWrapper.removeChild(element); + }); + + return toRemove.length; + } + + /** + * 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 messageSelector = '[data-drupal-message]'; + if (type) { + messageSelector += '[class~="messages--' + type + '"]'; } - return length; + var toRemove = Array.from(messageWrapper.querySelectorAll(messageSelector)); + toRemove.forEach(function (element) { + messageWrapper.removeChild(element); + }); + + return toRemove.length; } return { add: messageAdd, - remove: messageRemove + remove: messageRemove, + clear: messageClear }; }; diff -u b/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 --- b/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 @@ -3,10 +3,24 @@ * Testing behavior for JSMessageTest. */ -(function ($) { +(function ($, Drupal, drupalSettings) { 'use strict'; + var messageObjects = {}; + var messageIndexes = {multiple: []}; + + drupalSettings.testMessages.selectors.forEach(function (selector) { + messageObjects[selector] = Drupal.message(document.querySelector(selector)); + + messageIndexes[selector] = {}; + drupalSettings.testMessages.types.forEach(function (type) { + messageIndexes[selector][type] = []; + }); + }); + + var defaultMessageArea = messageObjects[drupalSettings.testMessages.selectors[0]]; + /** * @type {Drupal~behavior} * @@ -18,47 +32,51 @@ - var messageObjects = {}; - var messageIndexes = {}; - $('.show-link').once('show-msg').on('click', function (e) { + $('[data-drupal-messages-area]').once('messages-details').on('click', '[data-action]', function (e) { + var $target = $(e.currentTarget); + var type = $target.attr('data-type'); + var area = $target.closest('[data-drupal-messages-area]').attr('data-drupal-messages-area'); + var message = messageObjects[area]; + var action = $target.attr('data-action'); - e.preventDefault(); - var type = e.currentTarget.getAttribute('data-type'); - messageIndexes[type] = getMessageObject(e).add('Msg-' + type, type); - }); - $('.remove-link').once('remove-msg').on('click', function (e) { - e.preventDefault(); - var type = e.currentTarget.getAttribute('data-type'); - getMessageObject(e).remove(messageIndexes[type]); - }); - $('.show-multiple').once('show-msg').on('click', function (e) { - e.preventDefault(); - for (var i = 0; i < 10; i++) { - messageIndexes[i] = getMessageObject(e).add('Msg-' + i, 'status'); + if (action === 'add') { + messageIndexes[area][type].push(message.add('Msg-' + type, type)); + } + else if (action === 'remove') { + message.remove(messageIndexes[area][type].pop()); } - }); - $('.remove-multiple').once('remove-msg').on('click', function (e) { - e.preventDefault(); - for (var i = 0; i < 10; i++) { - getMessageObject(e).remove(messageIndexes[i]); + + $('[data-action="add-multiple"]').once('add-multiple').on('click', function (e) { + var types = drupalSettings.testMessages.types; + // Add several of different types to make sure message type doesn't + // cause issues in the API. + for (var i = 0; i < types.length * 2; i++) { + messageIndexes.multiple.push(defaultMessageArea.add('Msg-' + i, types[i % types.length])); } }); - /** - * Gets message object for the click event. - * - * @param {jQuery.Event} e - * The click event. - * @return {Drupal.message~messageDefinition} - * The message object for correct div. - */ - function getMessageObject(e) { - var divSelector = e.currentTarget.getAttribute('data-selector'); - if (!messageObjects.hasOwnProperty(divSelector)) { - messageObjects[divSelector] = Drupal.message(document.querySelector(divSelector)); + $('[data-action="remove-multiple"]').once('remove-multiple').on('click', function (e) { + defaultMessageArea.remove(messageIndexes.multiple); + messageIndexes.multiple = []; + }); + + $('[data-action="add-multiple-error"]').once('add-multiple-error').on('click', function (e) { + // Use the same number of elements to facilitate things on the PHP side. + var total = drupalSettings.testMessages.types.length * 2; + for (var i = 0; i < total; i++) { + defaultMessageArea.add('Msg-' + i, 'error'); } - return messageObjects[divSelector]; - } + defaultMessageArea.add('Msg-' + total, 'status'); + }); + + $('[data-action="clear-type"]').once('clear-type').on('click', function (e) { + defaultMessageArea.clear('error'); + }); + + $('[data-action="clear-all"]').once('clear-all').on('click', function (e) { + defaultMessageArea.clear(); + }); + } }; -})(jQuery); +})(jQuery, Drupal, drupalSettings); diff -u b/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml b/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml --- b/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml +++ b/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml @@ -4,4 +4,5 @@ js/js_message_test.js: {} dependencies: + - core/drupalSettings - core/drupal.message - core/jquery.once diff -u b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php --- b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php +++ b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php @@ -17,56 +17,112 @@ * Render array for links. */ public function messageLinks() { - $links = []; + $buttons = []; foreach (JsMessageTestCases::getMessagesSelectors() as $messagesSelector) { + $buttons[$messagesSelector] = [ + '#type' => 'details', + '#open' => TRUE, + '#title' => "Message area: $messagesSelector", + '#attributes' => [ + 'data-drupal-messages-area' => $messagesSelector, + ] + ]; foreach (JsMessageTestCases::getTypes() as $type) { - $links["show-$messagesSelector-$type"] = [ - 'title' => "Show-$messagesSelector-$type", - 'url' => Url::fromRoute('js_message_test.links'), - 'attributes' => [ - 'id' => "show-$messagesSelector-$type", + $buttons[$messagesSelector]["add-$type"] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Add $type", + '#attributes' => [ + 'type' => 'button', + 'id' => "add-$messagesSelector-$type", 'data-type' => $type, - 'data-selector' => $messagesSelector, - 'class' => ['show-link'], + 'data-action' => 'add', ], ]; - $links["remove-$messagesSelector-$type"] = [ - 'title' => "Remove-$messagesSelector-$type", - 'url' => Url::fromRoute('js_message_test.links'), - 'attributes' => [ + $buttons[$messagesSelector]["remove-$type"] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Remove $type", + '#attributes' => [ + 'type' => 'button', 'id' => "remove-$messagesSelector-$type", 'data-type' => $type, - 'data-selector' => $messagesSelector, - 'class' => ['remove-link'], + 'data-action' => 'remove', ], ]; } } + // Add alternative message area. + $buttons[JsMessageTestCases::getMessagesSelectors()[1]]['messages-other-area'] = [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => [ + 'data-drupal-messages-other' => TRUE, + ], + ]; + $buttons['add-multiple'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Add multiple", + '#attributes' => [ + 'type' => 'button', + 'id' => 'add-multiple', + 'data-action' => 'add-multiple', + ], + ]; + $buttons['remove-multiple'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Remove multiple", + '#attributes' => [ + 'type' => 'button', + 'id' => 'remove-multiple', + 'data-action' => 'remove-multiple', + ], + ]; + $buttons['add-multiple-error'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Add multiple 'error' and one 'status'", + '#attributes' => [ + 'type' => 'button', + 'id' => 'add-multiple-error', + 'data-action' => 'add-multiple-error', + ], + ]; + $buttons['clear-type'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Clear 'error' type", + '#attributes' => [ + 'type' => 'button', + 'id' => 'clear-type', + 'data-action' => 'clear-type', + ], + ]; + $buttons['clear-all'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Clear all", + '#attributes' => [ + 'type' => 'button', + 'id' => 'clear-all', + 'data-action' => 'clear-all', + ], + ]; - $links['show-multi'] = [ - 'title' => "Show Multiple", - 'url' => Url::fromRoute('js_message_test.links'), - 'attributes' => [ - 'class' => ['show-multiple'], - 'data-selector' => JsMessageTestCases::getMessagesSelectors()[0], - ], - ]; - $links['remove-multi'] = [ - 'title' => "Remove Multiple", - 'url' => Url::fromRoute('js_message_test.links'), - 'attributes' => [ - 'class' => ['remove-multiple'], - 'data-selector' => JsMessageTestCases::getMessagesSelectors()[0], - ], - ]; - return [ - '#theme' => 'links', - '#links' => $links, + return $buttons + [ '#attached' => [ 'library' => [ 'js_message_test/show_message', ], - ], + 'drupalSettings' => [ + 'testMessages' => [ + 'selectors' => JsMessageTestCases::getMessagesSelectors(), + 'types' => JsMessageTestCases::getTypes(), + ], + ], + ] ]; } diff -u b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php --- b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php @@ -41,7 +41,7 @@ foreach (JsMessageTestCases::getMessagesSelectors() as $messagesSelector) { $web_assert->elementExists('css', $messagesSelector); foreach (JsMessageTestCases::getTypes() as $type) { - $this->clickLink("Show-$messagesSelector-$type"); + $this->click("#add-$messagesSelector-$type"); $selector = "$messagesSelector .messages.messages--$type"; $msg_element = $web_assert->waitForElementVisible('css', $selector); $this->assertNotEmpty($msg_element, "Message element visible: $selector"); @@ -51,7 +51,7 @@ } // Remove messages 1 by 1 and confirm the messages are expected. foreach (JsMessageTestCases::getTypes() as $type) { - $this->clickLink("Remove-$messagesSelector-$type"); + $this->click("#remove-$messagesSelector-$type"); $selector = "$messagesSelector .messages.messages--$type"; // The message for this selector should not be on the page. unset($current_messages[$selector]); @@ -59,16 +59,26 @@ } } - // Test adding multiple messages at once. - // @see processMessages() - $this->clickLink('Show Multiple'); - $current_messages = []; - for ($i = 0; $i < 10; $i++) { + $nb_messages = count(JsMessageTestCases::getTypes()) * 2; + for ($i = 0; $i < $nb_messages; $i++) { $current_messages[] = "Msg-$i"; } + // Test adding multiple messages at once. + // @see processMessages() + $this->click('#add-multiple'); + $this->assertCurrentMessages($current_messages); + $this->click('#remove-multiple'); + $this->assertCurrentMessages([]); + + // The last message is of a different type and shouldn't get cleared. + $last_message = 'Msg-' . count($current_messages); + $current_messages[] = $last_message; + $this->click('#add-multiple-error'); $this->assertCurrentMessages($current_messages); - $this->clickLink('Remove Multiple'); + $this->click('#clear-type'); + $this->assertCurrentMessages($last_message); + $this->click('#clear-all'); $this->assertCurrentMessages([]); }