diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js index a438fbc60c..739afa1974 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 = '
 
'; + + return `
${throbber}${messageMarkup}
`; + }; + + /** + * An animated progress throbber and container element for AJAX operations. + * + * @return {string} + * The HTML markup for the throbber. + */ + Drupal.theme.ajaxProgressIndicatorFullscreen = () => '
 
'; + + /** + * 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 => `
${message}
`; + /** * Sets the progress bar progress indicator. */ @@ -831,10 +865,7 @@ * Sets the throbber progress indicator. */ Drupal.Ajax.prototype.setProgressIndicatorThrobber = function () { - this.progress.element = $('
 
'); - if (this.progress.message) { - this.progress.element.find('.throbber').after(`
${this.progress.message}
`); - } + 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 = $('
 
'); + this.progress.element = $(Drupal.theme('ajaxProgressIndicatorFullscreen')); $('body').after(this.progress.element); }; diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 814d82f8f2..abe0ec2928 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 = '
 
'; + + return '
' + throbber + messageMarkup + '
'; + }; + + Drupal.theme.ajaxProgressIndicatorFullscreen = function () { + return '
 
'; + }; + + Drupal.theme.ajaxProgressMessage = function (message) { + return '
' + message + '
'; + }; + 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 = $('
 
'); - if (this.progress.message) { - this.progress.element.find('.throbber').after('
' + this.progress.message + '
'); - } + this.progress.element = $(Drupal.theme('ajaxProgressThrobber', this.progress.message)); $(this.element).after(this.progress.element); }; Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function () { - this.progress.element = $('
 
'); + 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 965a2e4c36..cf12841711 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('
 
'); + $(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 d0e8a6d712..5cbeb6458b 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('
 
'); + $(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 0000000000..f767751422 --- /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 0000000000..183463deb3 --- /dev/null +++ b/core/modules/system/tests/modules/hold_test/hold_test.install @@ -0,0 +1,14 @@ +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 0000000000..e2ecaea7a4 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php @@ -0,0 +1,112 @@ +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; + $custom_ajax_progress_throbber = <<'; + }; +JS; + $custom_ajax_progress_message = <<Hold door!'; + }; +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); + } + +}