diff --git a/core/lib/Drupal/Core/Ajax/AnnounceCommand.php b/core/lib/Drupal/Core/Ajax/AnnounceCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..aa58bdbfee03172809fbd0fff0bcdbaf298f5005 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/AnnounceCommand.php @@ -0,0 +1,81 @@ +<?php + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Asset\AttachedAssets; + +/** + * AJAX command for a JavaScript Drupal.announce() call. + * + * @ingroup ajax + */ +class AnnounceCommand implements CommandInterface, CommandWithAttachedAssetsInterface { + + /** + * The assertive priority attribute value. + * + * @var string + */ + const PRIORITY_ASSERTIVE = 'assertive'; + + /** + * The polite priority attribute value. + * + * @var string + */ + const PRIORITY_POLITE = 'polite'; + + /** + * The text to be announced. + * + * @var string + */ + protected $text; + + /** + * The priority that will be used for the announcement. + * + * @var string + */ + protected $priority; + + /** + * Constructs an AnnounceCommand object. + * + * @param string $text + * The text to be announced. + * @param string|null $priority + * (optional) The priority that will be used for the announcement. Defaults + * to NULL which will not set a 'priority' in the response sent to the + * client and therefore the JavaScript Drupal.announce() default of 'polite' + * will be used for the message. + */ + public function __construct($text, $priority = NULL) { + $this->text = $text; + $this->priority = $priority; + } + + /** + * {@inheritdoc} + */ + public function render() { + $render = [ + 'command' => 'announce', + 'text' => $this->text, + ]; + if ($this->priority !== NULL) { + $render['priority'] = $this->priority; + } + return $render; + } + + /** + * {@inheritdoc} + */ + public function getAttachedAssets() { + $assets = new AttachedAssets(); + $assets->setLibraries(['core/drupal.announce']); + return $assets; + } + +} diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js index 39784c403c2a9b2d362e9efb93e67851b4901d60..ca3c0a5e5db9db645fc5526ae1df7926848573a1 100644 --- a/core/misc/ajax.es6.js +++ b/core/misc/ajax.es6.js @@ -1336,6 +1336,26 @@ window.alert(response.text, response.title); }, + /** + * Command to provide triggers audio UAs to read the supplied text. + * + * @param {Drupal.Ajax} [ajax] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The JSON response from the Ajax request. + * @param {string} [response.text] + * The text that will be read. + * @param {string} [response.priority] + * An optional priority that will be used for the announcement. + */ + announce(ajax, response) { + if (response.priority) { + Drupal.announce(response.text, response.priority); + } else { + Drupal.announce(response.text); + } + }, + /** * Command to set the window.location, redirecting the browser. * diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 79aac05e8520f2e672dd2953cf8aba9a594cccec..cceffe4762463ba6768e9c301138df62a43a378b 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -576,6 +576,13 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr alert: function alert(ajax, response, status) { window.alert(response.text, response.title); }, + announce: function announce(ajax, response) { + if (response.priority) { + Drupal.announce(response.text, response.priority); + } else { + Drupal.announce(response.text); + } + }, redirect: function redirect(ajax, response, status) { window.location = response.url; }, diff --git a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module index a93372d06d10585b07b6f2c68bac72375579504c..7adeffef9cec7c01b5ed7cc29712e0017d7a55f6 100644 --- a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module +++ b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module @@ -9,6 +9,7 @@ use Drupal\Core\Ajax\AfterCommand; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\AlertCommand; +use Drupal\Core\Ajax\AnnounceCommand; use Drupal\Core\Ajax\AppendCommand; use Drupal\Core\Ajax\BeforeCommand; use Drupal\Core\Ajax\ChangedCommand; @@ -43,6 +44,35 @@ function ajax_forms_test_advanced_commands_alert_callback($form, FormStateInterf return $response; } +/** + * Ajax form callback: Selects 'announce' with no priority specified. + */ +function ajax_forms_test_advanced_commands_announce_callback($form, FormStateInterface $form_state) { + return (new AjaxResponse())->addCommand(new AnnounceCommand('Default announcement.')); +} + +/** + * Ajax form callback: Selects 'announce' with 'polite' priority. + */ +function ajax_forms_test_advanced_commands_announce_polite_callback($form, FormStateInterface $form_state) { + return (new AjaxResponse())->addCommand(new AnnounceCommand('Polite announcement.', 'polite')); +} + +/** + * Ajax form callback: Selects 'announce' with 'assertive' priority. + */ +function ajax_forms_test_advanced_commands_announce_assertive_callback($form, FormStateInterface $form_state) { + return (new AjaxResponse())->addCommand(new AnnounceCommand('Assertive announcement.', 'assertive')); +} + +/** + * Ajax form callback: Selects 'announce' with two announce commands returned. + */ +function ajax_forms_test_advanced_commands_double_announce_callback($form, FormStateInterface $form_state) { + return (new AjaxResponse())->addCommand(new AnnounceCommand('Assertive announcement.', 'assertive')) + ->addCommand(new AnnounceCommand('Another announcement.')); +} + /** * Ajax form callback: Selects 'append'. */ diff --git a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCommandsForm.php b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCommandsForm.php index 924a9fda61ceaf652944000423cd3c22e15d2e4e..8804b1bd3788e2540c8b07391cdf04d92968a675 100644 --- a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCommandsForm.php +++ b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCommandsForm.php @@ -44,6 +44,42 @@ public function buildForm(array $form, FormStateInterface $form_state) { ], ]; + // Shows the 'announce' command with default priority. + $form['announce_command_example'] = [ + '#value' => $this->t("AJAX 'Announce': Click to announce"), + '#type' => 'submit', + '#ajax' => [ + 'callback' => 'ajax_forms_test_advanced_commands_announce_callback', + ], + ]; + + // Shows the 'announce' command with 'polite' priority. + $form['announce_command_polite_example'] = [ + '#value' => $this->t("AJAX 'Announce': Click to announce with 'polite' priority"), + '#type' => 'submit', + '#ajax' => [ + 'callback' => 'ajax_forms_test_advanced_commands_announce_polite_callback', + ], + ]; + + // Shows the 'announce' command with 'assertive' priority. + $form['announce_command_assertive_example'] = [ + '#value' => $this->t("AJAX 'Announce': Click to announce with 'assertive' priority"), + '#type' => 'submit', + '#ajax' => [ + 'callback' => 'ajax_forms_test_advanced_commands_announce_assertive_callback', + ], + ]; + + // Shows the 'announce' command used twice in one AjaxResponse. + $form['announce_command_double_example'] = [ + '#value' => $this->t("AJAX 'Announce': Click to announce twice"), + '#type' => 'submit', + '#ajax' => [ + 'callback' => 'ajax_forms_test_advanced_commands_double_announce_callback', + ], + ]; + // Shows the 'append' command. $form['append_command_example'] = [ '#value' => $this->t("AJAX 'Append': Click to append something"), diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php index f2e95770482380e242cee605b05a08aabea022c6..eca511d82239b4b63127f7bfe483b84b59341ade 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/CommandsTest.php @@ -52,6 +52,22 @@ public function testAjaxCommands() { $this->assertEquals('Alert', $alert_text); $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); + $this->drupalGet($form_path); + $page->pressButton("AJAX 'Announce': Click to announce"); + $this->assertWaitPageContains('<div id="drupal-live-announce" class="visually-hidden" aria-live="polite" aria-busy="false">Default announcement.</div>'); + + $this->drupalGet($form_path); + $page->pressButton("AJAX 'Announce': Click to announce with 'polite' priority"); + $this->assertWaitPageContains('<div id="drupal-live-announce" class="visually-hidden" aria-live="polite" aria-busy="false">Polite announcement.</div>'); + + $this->drupalGet($form_path); + $page->pressButton("AJAX 'Announce': Click to announce with 'assertive' priority"); + $this->assertWaitPageContains('<div id="drupal-live-announce" class="visually-hidden" aria-live="assertive" aria-busy="false">Assertive announcement.</div>'); + + $this->drupalGet($form_path); + $page->pressButton("AJAX 'Announce': Click to announce twice"); + $this->assertWaitPageContains('<div id="drupal-live-announce" class="visually-hidden" aria-live="assertive" aria-busy="false">Assertive announcement.' . "\nAnother announcement.</div>"); + // Tests the 'append' command. $page->pressButton("AJAX 'Append': Click to append something"); $this->assertWaitPageContains('<div id="append_div">Append inside this divAppended text</div>'); diff --git a/core/tests/Drupal/Tests/Core/Ajax/AjaxCommandsTest.php b/core/tests/Drupal/Tests/Core/Ajax/AjaxCommandsTest.php index 1dd66b83283793753d56e1c28107a17aeb5413ad..2a5cc4faf3877ead91177c7ba916cf916cb5aa7f 100644 --- a/core/tests/Drupal/Tests/Core/Ajax/AjaxCommandsTest.php +++ b/core/tests/Drupal/Tests/Core/Ajax/AjaxCommandsTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\Core\Ajax; +use Drupal\Core\Ajax\AnnounceCommand; +use Drupal\Core\Asset\AttachedAssets; use Drupal\Tests\UnitTestCase; use Drupal\Core\Ajax\AddCssCommand; use Drupal\Core\Ajax\AfterCommand; @@ -78,6 +80,61 @@ public function testAlertCommand() { $this->assertEquals($expected, $command->render()); } + /** + * @covers \Drupal\Core\Ajax\AnnounceCommand + * + * @dataProvider announceCommandProvider + */ + public function testAnnounceCommand($message, $priority, array $expected) { + if ($priority === NULL) { + $command = new AnnounceCommand($message); + } + else { + $command = new AnnounceCommand($message, $priority); + } + + $expected_assets = new AttachedAssets(); + $expected_assets->setLibraries(['core/drupal.announce']); + + $this->assertEquals($expected_assets, $command->getAttachedAssets()); + $this->assertSame($expected, $command->render()); + } + + /** + * Data provider for testAnnounceCommand(). + */ + public function announceCommandProvider() { + return [ + 'no priority' => [ + 'Things are going to change!', + NULL, + [ + 'command' => 'announce', + 'text' => 'Things are going to change!', + ], + ], + 'polite priority' => [ + 'Things are going to change!', + 'polite', + [ + 'command' => 'announce', + 'text' => 'Things are going to change!', + 'priority' => AnnounceCommand::PRIORITY_POLITE, + ], + + ], + 'assertive priority' => [ + 'Important!', + 'assertive', + [ + 'command' => 'announce', + 'text' => 'Important!', + 'priority' => AnnounceCommand::PRIORITY_ASSERTIVE, + ], + ], + ]; + } + /** * @covers \Drupal\Core\Ajax\AppendCommand */