diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js index a438fbc60c7e8314a1a637f66d05d73a0e40b35a..739afa19747bc2fadafab2228c36d6b55e897706 100644 --- a/core/misc/ajax.es6.js +++ b/core/misc/ajax.es6.js @@ -811,6 +811,40 @@ } }; + /** + * An animated progress throbber and container element for AJAX operations. + * + * @param {string} [message] + * (optional) The message shown on the UI. + * @return {string} + * The HTML markup for the throbber. + */ + Drupal.theme.ajaxProgressThrobber = (message) => { + // Build markup without adding extra white space since it affects rendering. + const messageMarkup = typeof message === 'string' ? Drupal.theme('ajaxProgressMessage', message) : ''; + const throbber = '<div class="throbber"> </div>'; + + return `<div class="ajax-progress ajax-progress-throbber">${throbber}${messageMarkup}</div>`; + }; + + /** + * An animated progress throbber and container element for AJAX operations. + * + * @return {string} + * The HTML markup for the throbber. + */ + Drupal.theme.ajaxProgressIndicatorFullscreen = () => '<div class="ajax-progress ajax-progress-fullscreen"> </div>'; + + /** + * Formats text accompanying the AJAX progress throbber. + * + * @param {string} message + * The message shown on the UI. + * @return {string} + * The HTML markup for the throbber. + */ + Drupal.theme.ajaxProgressMessage = message => `<div class="message">${message}</div>`; + /** * Sets the progress bar progress indicator. */ @@ -831,10 +865,7 @@ * Sets the throbber progress indicator. */ Drupal.Ajax.prototype.setProgressIndicatorThrobber = function () { - this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>'); - if (this.progress.message) { - this.progress.element.find('.throbber').after(`<div class="message">${this.progress.message}</div>`); - } + this.progress.element = $(Drupal.theme('ajaxProgressThrobber', this.progress.message)); $(this.element).after(this.progress.element); }; @@ -842,7 +873,7 @@ * Sets the fullscreen progress indicator. */ Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function () { - this.progress.element = $('<div class="ajax-progress ajax-progress-fullscreen"> </div>'); + this.progress.element = $(Drupal.theme('ajaxProgressIndicatorFullscreen')); $('body').after(this.progress.element); }; diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 814d82f8f2348f85aeda9c2c1faa8adce5062859..abe0ec2928df7cb497910d6be9b3d1cbf38e345f 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -368,6 +368,21 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr } }; + Drupal.theme.ajaxProgressThrobber = function (message) { + var messageMarkup = typeof message === 'string' ? Drupal.theme('ajaxProgressMessage', message) : ''; + var throbber = '<div class="throbber"> </div>'; + + return '<div class="ajax-progress ajax-progress-throbber">' + throbber + messageMarkup + '</div>'; + }; + + Drupal.theme.ajaxProgressIndicatorFullscreen = function () { + return '<div class="ajax-progress ajax-progress-fullscreen"> </div>'; + }; + + Drupal.theme.ajaxProgressMessage = function (message) { + return '<div class="message">' + message + '</div>'; + }; + Drupal.Ajax.prototype.setProgressIndicatorBar = function () { var progressBar = new Drupal.ProgressBar('ajax-progress-' + this.element.id, $.noop, this.progress.method, $.noop); if (this.progress.message) { @@ -382,15 +397,12 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr }; Drupal.Ajax.prototype.setProgressIndicatorThrobber = function () { - this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>'); - if (this.progress.message) { - this.progress.element.find('.throbber').after('<div class="message">' + this.progress.message + '</div>'); - } + this.progress.element = $(Drupal.theme('ajaxProgressThrobber', this.progress.message)); $(this.element).after(this.progress.element); }; Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function () { - this.progress.element = $('<div class="ajax-progress ajax-progress-fullscreen"> </div>'); + this.progress.element = $(Drupal.theme('ajaxProgressIndicatorFullscreen')); $('body').after(this.progress.element); }; diff --git a/core/modules/field_ui/field_ui.es6.js b/core/modules/field_ui/field_ui.es6.js index 965a2e4c36b07f461140244656588e4f6971847e..cf1284171186bdfddf5a955988941144f8c0aa4f 100644 --- a/core/modules/field_ui/field_ui.es6.js +++ b/core/modules/field_ui/field_ui.es6.js @@ -219,7 +219,7 @@ if (rowNames.length) { // Add a throbber next each of the ajaxElements. - $(ajaxElements).after('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>'); + $(ajaxElements).after(Drupal.theme.ajaxProgressThrobber()); // Fire the Ajax update. $('input[name=refresh_rows]').val(rowNames.join(' ')); diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js index d0e8a6d7126f0574577be9d7c84aa184f8d22087..5cbeb6458b35c366b4d2da6b97f5424a45599492 100644 --- a/core/modules/field_ui/field_ui.js +++ b/core/modules/field_ui/field_ui.js @@ -126,7 +126,7 @@ }); if (rowNames.length) { - $(ajaxElements).after('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>'); + $(ajaxElements).after(Drupal.theme.ajaxProgressThrobber()); $('input[name=refresh_rows]').val(rowNames.join(' ')); $('input[data-drupal-selector="edit-refresh"]').trigger('mousedown'); diff --git a/core/modules/system/tests/modules/hold_test/hold_test.info.yml b/core/modules/system/tests/modules/hold_test/hold_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..f7677514228914e6967ff1e9764f933e36ffb6b7 --- /dev/null +++ b/core/modules/system/tests/modules/hold_test/hold_test.info.yml @@ -0,0 +1,6 @@ +name: Hold test +type: module +description: 'Support testing with request/response hold.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/hold_test/hold_test.install b/core/modules/system/tests/modules/hold_test/hold_test.install new file mode 100644 index 0000000000000000000000000000000000000000..183463deb3b05a22e782e711d0cd454f05a385f1 --- /dev/null +++ b/core/modules/system/tests/modules/hold_test/hold_test.install @@ -0,0 +1,14 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the hold_test module. + */ + +/** + * Implements hook_install(). + */ +function hold_test_install() { + hold_test_request(FALSE); + hold_test_response(FALSE); +} diff --git a/core/modules/system/tests/modules/hold_test/hold_test.module b/core/modules/system/tests/modules/hold_test/hold_test.module new file mode 100644 index 0000000000000000000000000000000000000000..867eacf90612a40c8dd0b240fd337c85b0b2aa68 --- /dev/null +++ b/core/modules/system/tests/modules/hold_test/hold_test.module @@ -0,0 +1,26 @@ +<?php + +/** + * @file + * Contains functions for testing hold request/response. + */ + +/** + * Request hold. + * + * @param bool $status + * TRUE - enable hold, FALSE - disable hold. + */ +function hold_test_request($status) { + file_put_contents(\Drupal::root() . '/sites/default/files/simpletest/hold_test_request.txt', $status); +} + +/** + * Response hold. + * + * @param bool $status + * TRUE - enable hold, FALSE - disable hold. + */ +function hold_test_response($status) { + file_put_contents(\Drupal::root() . '/sites/default/files/simpletest/hold_test_response.txt', $status); +} diff --git a/core/modules/system/tests/modules/hold_test/hold_test.services.yml b/core/modules/system/tests/modules/hold_test/hold_test.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..88e7babdff32adcf91dd5d4a1018572a4afe45fb --- /dev/null +++ b/core/modules/system/tests/modules/hold_test/hold_test.services.yml @@ -0,0 +1,5 @@ +services: + hold_test.response: + class: Drupal\hold_test\EventSubscriber\HoldTestSubscriber + tags: + - { name: event_subscriber } diff --git a/core/modules/system/tests/modules/hold_test/src/EventSubscriber/HoldTestSubscriber.php b/core/modules/system/tests/modules/hold_test/src/EventSubscriber/HoldTestSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..332f4c00b9a8f01b5409f44c90ca287cc564d819 --- /dev/null +++ b/core/modules/system/tests/modules/hold_test/src/EventSubscriber/HoldTestSubscriber.php @@ -0,0 +1,52 @@ +<?php + +namespace Drupal\hold_test\EventSubscriber; + +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Response subscriber to test hold. + */ +class HoldTestSubscriber implements EventSubscriberInterface { + + const HOLD_REQUEST = 'request'; + const HOLD_RESPONSE = 'response'; + + /** + * Request hold. + */ + public function onRequest() { + $this->hold(static::HOLD_REQUEST); + } + + /** + * Response hold. + */ + public function onRespond() { + $this->hold(static::HOLD_RESPONSE); + } + + /** + * Hold process by type. + * + * @param string $type + * Type of hold. + */ + protected function hold($type) { + $path = \Drupal::root() . "/sites/default/files/simpletest/hold_test_$type.txt"; + do { + $status = (bool) file_get_contents($path); + } while ($status && (NULL === usleep(100000))); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = ['onRequest']; + $events[KernelEvents::RESPONSE][] = ['onRespond']; + return $events; + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e2ecaea7a468d9aeb9f1fa5fc86d4b6372216f90 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php @@ -0,0 +1,112 @@ +<?php + +namespace Drupal\FunctionalJavascriptTests\Ajax; + +use Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver; +use Drupal\FunctionalJavascriptTests\JavascriptTestBase; + +/** + * Tests the throbber. + * + * @group Ajax + */ +class ThrobberTest extends JavascriptTestBase { + + /** + * {@inheritdoc} + */ + protected $minkDefaultDriverClass = DrupalSelenium2Driver::class; + + /** + * {@inheritdoc} + */ + public static $modules = [ + 'node', + 'views', + 'views_ui', + 'views_ui_test_field', + 'hold_test', + ]; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $admin_user = $this->drupalCreateUser([ + 'administer views', + ]); + $this->drupalLogin($admin_user); + } + + /** + * Tests theming throbber element. + */ + public function testThemingThrobberElement() { + $session = $this->getSession(); + $web_assert = $this->assertSession(); + $page = $session->getPage(); + + $custom_ajax_progress_indicator_fullscreen = <<<JS + Drupal.theme.ajaxProgressIndicatorFullscreen = function () { + return '<div class="custom-ajax-progress-fullscreen"></div>'; + }; +JS; + $custom_ajax_progress_throbber = <<<JS + Drupal.theme.ajaxProgressThrobber = function (message) { + return '<div class="custom-ajax-progress-throbber"></div>'; + }; +JS; + $custom_ajax_progress_message = <<<JS + Drupal.theme.ajaxProgressMessage = function (message) { + return '<div class="custom-ajax-progress-message">Hold door!</div>'; + }; +JS; + + $this->drupalGet('admin/structure/views/view/content'); + $this->waitForNoElement('.ajax-progress-fullscreen'); + + // Test theming fullscreen throbber. + $session->executeScript($custom_ajax_progress_indicator_fullscreen); + hold_test_response(TRUE); + $page->clickLink('Content: Published (grouped)'); + $this->assertNotNull($web_assert->waitForElement('css', '.custom-ajax-progress-fullscreen'), 'Custom ajaxProgressIndicatorFullscreen.'); + hold_test_response(FALSE); + $this->waitForNoElement('.custom-ajax-progress-fullscreen'); + + // Test theming throbber message. + $web_assert->waitForElementVisible('css', '[data-drupal-selector="edit-options-group-info-add-group"]'); + $session->executeScript($custom_ajax_progress_message); + hold_test_response(TRUE); + $page->pressButton('Add another item'); + $this->assertNotNull($web_assert->waitForElement('css', '.ajax-progress-throbber .custom-ajax-progress-message'), 'Custom ajaxProgressMessage.'); + hold_test_response(FALSE); + $this->waitForNoElement('.ajax-progress-throbber'); + + // Test theming throbber. + $web_assert->waitForElementVisible('css', '[data-drupal-selector="edit-options-group-info-group-items-3-title"]'); + $session->executeScript($custom_ajax_progress_throbber); + hold_test_response(TRUE); + $page->pressButton('Add another item'); + $this->assertNotNull($web_assert->waitForElement('css', '.custom-ajax-progress-throbber'), 'Custom ajaxProgressThrobber.'); + hold_test_response(FALSE); + $this->waitForNoElement('.custom-ajax-progress-throbber'); + } + + /** + * Waits for an element to be removed from the page. + * + * @param string $selector + * CSS selector. + * @param int $timeout + * (optional) Timeout in milliseconds, defaults to 10000. + * + * @todo Remove in https://www.drupal.org/node/2892440. + */ + protected function waitForNoElement($selector, $timeout = 10000) { + $condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)"; + $this->assertJsCondition($condition, $timeout); + } + +}