Issue #3086096 by oknate, phenaproxima, tedbow, Wim Leers, andrewmacpherson, droplet, bnjmnm: Add a generic Ajax Message command
parent
f702e8c453
commit
7dde35cfd0
|
@ -7,6 +7,15 @@ use Drupal\Core\Asset\AttachedAssets;
|
|||
/**
|
||||
* AJAX command for a JavaScript Drupal.announce() call.
|
||||
*
|
||||
* Developers should be extra careful if this command and
|
||||
* \Drupal\Core\Ajax\MessageCommand are included in the same response. By
|
||||
* default, MessageCommmand will also call Drupal.announce() and announce the
|
||||
* message to the screen reader (unless the option to suppress announcements is
|
||||
* passed to the constructor). Manual testing with a screen reader is strongly
|
||||
* recommended.
|
||||
*
|
||||
* @see \Drupal\Core\Ajax\MessageCommand
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class AnnounceCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
|
||||
/**
|
||||
* AJAX command for a JavaScript Drupal.message() call.
|
||||
*
|
||||
* Developers should be extra careful if this command and
|
||||
* \Drupal\Core\Ajax\AnnounceCommand are included in the same response. Unless
|
||||
* the `announce` option is set to an empty string (''), this command will
|
||||
* result in the message being announced to screen readers. When combined with
|
||||
* AnnounceCommand, this may result in unexpected behavior. Manual testing with
|
||||
* a screen reader is strongly recommended.
|
||||
*
|
||||
* Here are examples of how to suppress announcements:
|
||||
* @code
|
||||
* $command = new MessageCommand("I won't be announced", NULL, [
|
||||
* 'announce' => '',
|
||||
* ]);
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Ajax\AnnounceCommand
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class MessageCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
|
||||
|
||||
/**
|
||||
* The message text.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* Whether to clear previous messages.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $clearPrevious;
|
||||
|
||||
/**
|
||||
* The query selector for the element the message will appear in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $wrapperQuerySelector;
|
||||
|
||||
/**
|
||||
* The options passed to Drupal.message().add().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Constructs a MessageCommand object.
|
||||
*
|
||||
* @param string $message
|
||||
* The text of the message.
|
||||
* @param string|null $wrapper_query_selector
|
||||
* The query selector of the element to display messages in when they
|
||||
* should be displayed somewhere other than the default.
|
||||
* @see Drupal.Message.defaultWrapper()
|
||||
* @param array $options
|
||||
* The options passed to Drupal.message().add().
|
||||
* @param bool $clear_previous
|
||||
* If TRUE, previous messages will be cleared first.
|
||||
*/
|
||||
public function __construct($message, $wrapper_query_selector = NULL, array $options = [], $clear_previous = TRUE) {
|
||||
$this->message = $message;
|
||||
$this->wrapperQuerySelector = $wrapper_query_selector;
|
||||
$this->options = $options;
|
||||
$this->clearPrevious = $clear_previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'message',
|
||||
'message' => $this->message,
|
||||
'messageWrapperQuerySelector' => $this->wrapperQuerySelector,
|
||||
'messageOptions' => $this->options,
|
||||
'clearPrevious' => $this->clearPrevious,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAttachedAssets() {
|
||||
$assets = new AttachedAssets();
|
||||
$assets->setLibraries(['core/drupal.message']);
|
||||
return $assets;
|
||||
}
|
||||
|
||||
}
|
|
@ -1562,5 +1562,31 @@
|
|||
} while (match);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Command to add a message to the message area.
|
||||
*
|
||||
* @param {Drupal.Ajax} [ajax]
|
||||
* {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
|
||||
* @param {object} response
|
||||
* The response from the Ajax request.
|
||||
* @param {string} response.messageWrapperQuerySelector
|
||||
* The zone where to add the message. If null, the default will be used.
|
||||
* @param {string} response.message
|
||||
* The message text.
|
||||
* @param {string} response.messageOptions
|
||||
* The options argument for Drupal.Message().add().
|
||||
* @param {bool} response.clearPrevious
|
||||
* If true, clear previous messages.
|
||||
*/
|
||||
message(ajax, response) {
|
||||
const messages = new Drupal.Message(
|
||||
document.querySelector(response.messageWrapperQuerySelector),
|
||||
);
|
||||
if (response.clearPrevious) {
|
||||
messages.clear();
|
||||
}
|
||||
messages.add(response.message, response.messageOptions);
|
||||
},
|
||||
};
|
||||
})(jQuery, window, Drupal, drupalSettings);
|
||||
|
|
|
@ -639,6 +639,13 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
|
|||
document.styleSheets[0].addImport(match[1]);
|
||||
} while (match);
|
||||
}
|
||||
},
|
||||
message: function message(ajax, response) {
|
||||
var messages = new Drupal.Message(document.querySelector(response.messageWrapperQuerySelector));
|
||||
if (response.clearPrevious) {
|
||||
messages.clear();
|
||||
}
|
||||
messages.add(response.message, response.messageOptions);
|
||||
}
|
||||
};
|
||||
})(jQuery, window, Drupal, drupalSettings);
|
|
@ -77,3 +77,11 @@ ajax_test.render_error:
|
|||
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::renderError'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
ajax_test.message_form:
|
||||
path: '/ajax-test/message'
|
||||
defaults:
|
||||
_title: 'Ajax Message Form'
|
||||
_form: '\Drupal\ajax_test\Form\AjaxTestMessageCommandForm'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ajax_test\Form;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\MessageCommand;
|
||||
use Drupal\Core\Form\FormInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Form for testing AJAX MessageCommand.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AjaxTestMessageCommandForm implements FormInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'ajax_test_message_command_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['alternate-message-container'] = [
|
||||
'#type' => 'container',
|
||||
'#id' => 'alternate-message-container',
|
||||
];
|
||||
$form['button_default'] = [
|
||||
'#type' => 'submit',
|
||||
'#name' => 'makedefaultmessage',
|
||||
'#value' => 'Make Message In Default Location',
|
||||
'#ajax' => [
|
||||
'callback' => '::makeMessageDefault',
|
||||
],
|
||||
];
|
||||
$form['button_alternate'] = [
|
||||
'#type' => 'submit',
|
||||
'#name' => 'makealternatemessage',
|
||||
'#value' => 'Make Message In Alternate Location',
|
||||
'#ajax' => [
|
||||
'callback' => '::makeMessageAlternate',
|
||||
],
|
||||
];
|
||||
$form['button_warning'] = [
|
||||
'#type' => 'submit',
|
||||
'#name' => 'makewarningmessage',
|
||||
'#value' => 'Make Warning Message',
|
||||
'#ajax' => [
|
||||
'callback' => '::makeMessageWarning',
|
||||
],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for testing MessageCommand with default settings.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* The AJAX response.
|
||||
*/
|
||||
public function makeMessageDefault() {
|
||||
$response = new AjaxResponse();
|
||||
return $response->addCommand(new MessageCommand('I am a message in the default location.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for testing MessageCommand using an alternate message location.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* The AJAX response.
|
||||
*/
|
||||
public function makeMessageAlternate() {
|
||||
$response = new AjaxResponse();
|
||||
return $response->addCommand(new MessageCommand('I am a message in an alternate location.', '#alternate-message-container', [], FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for testing MessageCommand with warning status.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* The AJAX response.
|
||||
*/
|
||||
public function makeMessageWarning() {
|
||||
$response = new AjaxResponse();
|
||||
return $response->addCommand(new MessageCommand('I am a warning message in the default location.', NULL, ['type' => 'warning', 'announce' => '']));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalJavascriptTests\Ajax;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests adding messages via AJAX command.
|
||||
*
|
||||
* @group Ajax
|
||||
*/
|
||||
class MessageCommandTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['ajax_test'];
|
||||
|
||||
/**
|
||||
* Test AJAX MessageCommand use in a form.
|
||||
*/
|
||||
public function testMessageCommand() {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$this->drupalGet('ajax-test/message');
|
||||
$page->pressButton('Make Message In Default Location');
|
||||
$this->waitForMessageVisible('I am a message in the default location.');
|
||||
$this->assertAnnounceContains('I am a message in the default location.');
|
||||
$assert_session->elementsCount('css', '.messages__wrapper .messages', 1);
|
||||
|
||||
$page->pressButton('Make Message In Alternate Location');
|
||||
$this->waitForMessageVisible('I am a message in an alternate location.', '#alternate-message-container');
|
||||
$assert_session->pageTextContains('I am a message in the default location.');
|
||||
$this->assertAnnounceContains('I am a message in an alternate location.');
|
||||
$assert_session->elementsCount('css', '.messages__wrapper .messages', 1);
|
||||
$assert_session->elementsCount('css', '#alternate-message-container .messages', 1);
|
||||
|
||||
$page->pressButton('Make Warning Message');
|
||||
$this->waitForMessageVisible('I am a warning message in the default location.', NULL, 'warning');
|
||||
$assert_session->pageTextNotContains('I am a message in the default location.');
|
||||
$assert_session->elementsCount('css', '.messages__wrapper .messages', 1);
|
||||
$assert_session->elementsCount('css', '#alternate-message-container .messages', 1);
|
||||
|
||||
$this->drupalGet('ajax-test/message');
|
||||
// Test that by default, previous messages in a location are removed.
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$page->pressButton('Make Message In Default Location');
|
||||
$this->waitForMessageVisible('I am a message in the default location.');
|
||||
$assert_session->elementsCount('css', '.messages__wrapper .messages', 1);
|
||||
|
||||
$page->pressButton('Make Warning Message');
|
||||
$this->waitForMessageVisible('I am a warning message in the default location.', NULL, 'warning');
|
||||
// Test that setting MessageCommand::$option['announce'] => '' supresses
|
||||
// screen reader announcement.
|
||||
$this->assertAnnounceNotContains('I am a warning message in the default location.');
|
||||
$this->waitForMessageRemoved('I am a message in the default location.');
|
||||
$assert_session->elementsCount('css', '.messages__wrapper .messages', 1);
|
||||
}
|
||||
|
||||
// Test that if MessageCommand::clearPrevious is FALSE, messages will not
|
||||
// be cleared.
|
||||
$this->drupalGet('ajax-test/message');
|
||||
for ($i = 1; $i < 7; $i++) {
|
||||
$page->pressButton('Make Message In Alternate Location');
|
||||
$expected_count = $page->waitFor(10, function () use ($i, $page) {
|
||||
return count($page->findAll('css', '#alternate-message-container .messages')) === $i;
|
||||
});
|
||||
$this->assertTrue($expected_count);
|
||||
$this->assertAnnounceContains('I am a message in an alternate location.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a message of the expected type appears.
|
||||
*
|
||||
* @param string $message
|
||||
* The expected message.
|
||||
* @param string $selector
|
||||
* The selector for the element in which to check for the expected message.
|
||||
* @param string $type
|
||||
* The expected type.
|
||||
*/
|
||||
protected function waitForMessageVisible($message, $selector = '[data-drupal-messages]', $type = 'status') {
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', $selector . ' .messages--' . $type . ':contains("' . $message . '")'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a message of the expected type is removed.
|
||||
*
|
||||
* @param string $message
|
||||
* The expected message.
|
||||
* @param string $selector
|
||||
* The selector for the element in which to check for the expected message.
|
||||
* @param string $type
|
||||
* The expected type.
|
||||
*/
|
||||
protected function waitForMessageRemoved($message, $selector = '[data-drupal-messages]', $type = 'status') {
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElementRemoved('css', $selector . ' .messages--' . $type . ':contains("' . $message . '")'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for inclusion of text in #drupal-live-announce.
|
||||
*
|
||||
* @param string $expected_message
|
||||
* The text expected to be present in #drupal-live-announce.
|
||||
*/
|
||||
protected function assertAnnounceContains($expected_message) {
|
||||
$assert_session = $this->assertSession();
|
||||
$this->assertNotEmpty($assert_session->waitForElement('css', "#drupal-live-announce:contains('$expected_message')"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for absence of the given text from #drupal-live-announce.
|
||||
*
|
||||
* @param string $expected_message
|
||||
* The text expected to be absent from #drupal-live-announce.
|
||||
*/
|
||||
protected function assertAnnounceNotContains($expected_message) {
|
||||
$assert_session = $this->assertSession();
|
||||
$this->assertEmpty($assert_session->waitForElement('css', "#drupal-live-announce:contains('$expected_message')", 1000));
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,7 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
|||
use Drupal\js_message_test\Controller\JSMessageTestController;
|
||||
|
||||
/**
|
||||
* Tests core/drupal.messages library.
|
||||
* Tests core/drupal.message library.
|
||||
*
|
||||
* @group Javascript
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue