diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index fefe9f3031..46d65bdafd 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -1027,8 +1027,6 @@
// $(response.data) as new HTML rather than a CSS selector. Also, if
// response.data contains top-level text nodes, they get lost with either
// $(response.data) or $('
created
// above when it doesn't. For more information, please see
// https://www.drupal.org/node/736066.
- if ($new_content.length !== 1 || $new_content.get(0).nodeType !== 1) {
- $new_content = $new_content_wrapped;
+
+ var $responseDataWrapped = $('
').html(response.data);
+ var $new_content = $('
');
+ var elementsReturned = $responseDataWrapped.contents().length;
+
+ var createWrapper = function () {
+ return $('
');
+ };
+
+ var intermediateWrapper = null;
+ $responseDataWrapped.contents().each(function (index, value) {
+ if (intermediateWrapper && value.nodeType !== 1) {
+ intermediateWrapper.append(value);
+ }
+ if (!intermediateWrapper && value.nodeType !== 1) {
+ if ($.trim(value.nodeValue).length > 0) {
+ intermediateWrapper = createWrapper();
+ intermediateWrapper.append(value);
+ }
+ }
+ if (!intermediateWrapper && value.nodeType === 1) {
+ $new_content.append(value);
+ intermediateWrapper = null;
+ }
+ if (intermediateWrapper && value.nodeType === 1) {
+ $new_content.append(intermediateWrapper, value);
+ intermediateWrapper = null;
+ }
+ });
+
+ // If we only had one element returned, and not an Node.ELEMENT_NODE
+ // we need to add it to $new_content
+ if (elementsReturned === 1) {
+ $new_content.append(intermediateWrapper);
}
+ $new_content = $new_content.children();
+
// If removing content from the wrapper, detach behaviors first.
switch (method) {
case 'html':
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.libraries.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.libraries.yml
index f1c73064bd..772a05f734 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.libraries.yml
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.libraries.yml
@@ -1,3 +1,8 @@
+ajax_insert:
+ js:
+ js/insert-ajax.js: {}
+ dependencies:
+ - core/drupal.ajax
order:
drupalSettings:
ajax: test
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
index e8d06c0a9f..89191a1c63 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
@@ -6,6 +6,14 @@ ajax_test.dialog_contents:
requirements:
_access: 'TRUE'
+ajax_test.ajax_render_types:
+ path: '/ajax-test/dialog-contents-types/{type}'
+ defaults:
+ _title: 'AJAX Dialog contents routing'
+ _controller: '\Drupal\ajax_test\Controller\AjaxTestController::renderTypes'
+ requirements:
+ _access: 'TRUE'
+
ajax_test.dialog_form:
path: '/ajax-test/dialog-form'
defaults:
@@ -21,6 +29,13 @@ ajax_test.dialog:
requirements:
_access: 'TRUE'
+ajax_test.insert_links:
+ path: '/ajax-test/insert'
+ defaults:
+ _controller: '\Drupal\ajax_test\Controller\AjaxTestController::insertLinks'
+ requirements:
+ _access: 'TRUE'
+
ajax_test.dialog_close:
path: '/ajax-test/dialog-close'
defaults:
diff --git a/core/modules/system/tests/modules/ajax_test/js/insert-ajax.js b/core/modules/system/tests/modules/ajax_test/js/insert-ajax.js
new file mode 100644
index 0000000000..b2b9277393
--- /dev/null
+++ b/core/modules/system/tests/modules/ajax_test/js/insert-ajax.js
@@ -0,0 +1,25 @@
+/**
+ * @file
+ * Provides method to test ajax requests.
+ */
+
+(function ($, window, Drupal, drupalSettings) {
+ 'use strict';
+
+ Drupal.behaviors.insertTest = {
+ attach: function (context, settings) {
+ $('.ajax-insert').once('ajax-insert').on('click', function (event) {
+ event.preventDefault();
+ var ajaxSettings = {
+ url: event.currentTarget.getAttribute('href'),
+ wrapper: 'ajax-target',
+ base: false,
+ element: false,
+ method: 'html'
+ };
+ var myAjaxObject = Drupal.ajax(ajaxSettings);
+ myAjaxObject.execute();
+ });
+ }
+ };
+})(jQuery, window, Drupal, drupalSettings);
diff --git a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
index 63cc0abf16..6eb44410b6 100644
--- a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
+++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
@@ -43,6 +43,79 @@ public static function dialogContents() {
}
/**
+ * Example content for testing whether response should be wrapped in div.
+ *
+ * @param string $type
+ * Type of response.
+ *
+ * @return array
+ * Renderable array of AJAX response contents.
+ */
+ public static function renderTypes($type) {
+ switch ($type) {
+ case 'pre-wrapped':
+ $markup = '
' . $type . '
';
+ break;
+
+ case 'pre-wrapped-leading-whitespace':
+ $markup = '
' . $type . '
';
+ break;
+
+ case 'not-wrapped':
+ $markup = $type;
+ break;
+
+ case 'mixed':
+ $markup = ' foo foo bar
additional wrapped strings,
final string
';
+ break;
+ }
+
+ $content = [
+ '#title' => '
AJAX Dialog & contents',
+ 'content' => [
+ '#type' => 'inline_template',
+ '#template' => !empty($markup) ? $markup : '',
+ ],
+ ];
+
+ return $content;
+ }
+
+ /**
+ * Returns a render array of links that directly Drupal.ajax().
+ */
+ public function insertLinks() {
+ $types = [
+ 'pre-wrapped',
+ 'pre-wrapped-leading-whitespace',
+ 'not-wrapped',
+ 'mixed',
+ ];
+
+ $build['links'] = [
+ 'ajax_target' => [
+ '#markup' => '
Target
',
+ ],
+ 'links' => [
+ '#theme' => 'links',
+ '#attached' => ['library' => ['ajax_test/ajax_insert']],
+ ],
+ ];
+
+ foreach ($types as $type) {
+ $build['links']['links']['#links'][$type] = [
+ 'title' => "Link $type",
+ 'url' => Url::fromRoute('ajax_test.ajax_render_types', ['type' => $type]),
+ 'attributes' => [
+ 'class' => ['ajax-insert'],
+ ],
+ ];
+ }
+
+ return $build;
+ }
+
+ /**
* Returns a render array that will be rendered by AjaxRenderer.
*
* Verifies that the response incorporates JavaScript settings generated
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
index e05940537c..386133cf34 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php
@@ -82,4 +82,35 @@ public function testDrupalSettingsCachingRegression() {
$this->assertNotContains($fake_library, $libraries);
}
+ /**
+ * Tests that various AJAX responses are correctly wrapped.
+ */
+ public function testWrap() {
+ $assert = $this->assertSession();
+ $this->drupalGet('ajax-test/insert');
+
+ // Test that no additional wrapper is added when inserting already wrapped
+ // response data.
+ $this->clickLink('Link pre-wrapped');
+ $assert->assertWaitOnAjaxRequest();
+ $assert->responseContains('
pre-wrapped
');
+
+ // Test that no additional empty leading div is added when the return
+ // value had a leading space.
+ $this->clickLink('Link pre-wrapped-leading-whitespace');
+ $assert->assertWaitOnAjaxRequest();
+ $assert->responseContains('
pre-wrapped-leading-whitespace
');
+
+ // Test that unwrapped response data (text node) is inserted wrapped.
+ $this->clickLink('Link not-wrapped');
+ $assert->assertWaitOnAjaxRequest();
+ $assert->responseContains('
not-wrapped
');
+
+ // Test that wrappend and unwrapped response data is inserted correctly.
+ $this->clickLink('Link mixed');
+ $assert->assertWaitOnAjaxRequest();
+ $this->createScreenshot('/tmp/drupal/na4.jpg');
+ $assert->responseContains('
foo foo bar
additional wrapped strings,
final string
');
+ }
+
}