diff -u b/core/misc/ajax.js b/core/misc/ajax.js --- b/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -1015,10 +1015,6 @@ * The XMLHttpRequest status. */ insert: function (ajax, response, status) { - if (!response.data) { - return; - } - // Get information from the response. If it is not there, default to // our presets. var $wrapper = response.selector ? $(response.selector) : $(ajax.wrapper); @@ -1036,25 +1032,37 @@ // will be wrapped with a element. This also gives themers the // possibility to style the response. var $new_content; + var onlyElementNodes; + var trimedData = response.data.trim(); - // If only NODE_ELEMENT or COMMENT_NODE elements are returned skip wrap processing. - var responseDataWrappedContents = $.parseHTML(response.data.trim(), true); - var isAllElementNode = responseDataWrappedContents.every(function (element) { - return element.nodeType === Node.ELEMENT_NODE || element.nodeType === Node.COMMENT_NODE; - }); + // Tranform the response data in an array of DOM Nodes. + var responseDataNodes = $.parseHTML(trimedData, true); + // When response.data is empty (to empty an element for exemple) + // parseHTML returns null, we need to cheat a little. + if (responseDataNodes === null && trimedData === '') { + onlyElementNodes = false; + responseDataNodes = ['']; + } + else { + onlyElementNodes = responseDataNodes.every(function (element) { + return element.nodeType === Node.ELEMENT_NODE || element.nodeType === Node.COMMENT_NODE; + }); + } - if (!isAllElementNode) { - // If response.data contains TEXT_ELEMENT type only or target element - // is not a block level element, response.data will be wrapped with SPAN. - if (responseDataWrappedContents.length === 1 || ($wrapper.css('display') !== 'block')) { - $new_content = $('').html(response.data); + // If only NODE_ELEMENT or COMMENT_NODE elements are returned skip wrap processing. + if (!onlyElementNodes) { + // If response.data contains only one TEXT_ELEMENT if the target element + // is not styled as a block, response.data will be wrapped with SPAN. + if (responseDataNodes.length === 1 || ($wrapper.css('display') !== 'block')) { + $new_content = $(''); } else { - $new_content = $('
').html(response.data); + $new_content = $('
'); } + $new_content.html(responseDataNodes); } else { - $new_content = $(response.data); + $new_content = $(responseDataNodes); } // If removing content from the wrapper, detach behaviors first. @@ -1065,10 +1073,7 @@ case 'empty': case 'remove': settings = response.settings || ajax.settings || drupalSettings; - // Detach behaviors of all the to be removed element nodes. - $wrapper.children().each(function () { - Drupal.detachBehaviors(this, settings); - }); + Drupal.detachBehaviors($wrapper.get(0), settings); } // Add the new content to the page. diff -u b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php --- b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php +++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php @@ -74,7 +74,7 @@ $build['links'] = [ 'ajax_target' => [ - '#markup' => '
Target
Inline Test:
TEXT TEXT', + '#markup' => '
Target
Target inline', ], 'links' => [ '#theme' => 'links', diff -u b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php --- b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php @@ -17,16 +17,16 @@ public static $modules = ['ajax_test']; /** - * Wrap HTML with an AJAX target div. + * Wrap HTML with an AJAX target element. * * @param string $html * The HTML to wrap. * * @return string - * The HTML wrapped in the an AJAX target div. + * The HTML wrapped in the an AJAX target element. */ protected function wrapAjaxTarget($html) { - return '
' . $html . '
'; + return 'data-drupal-ajax-target="">' . $html . 'assertSession(); - $test_cases = [ // Test that no additional wrapper is added when inserting already wrapped // response data and all top-level node elements (context) are processed @@ -118,13 +117,13 @@ // processed correctly. [ 'render_type' => 'pre-wrapped-whitespace', - 'expected' => '
pre-wrapped-leading-whitespace
', + 'expected' => '
pre-wrapped-whitespace
', ], // Test that not wrapped response data (text node) is inserted wrapped and // all top-level node elements (context) are processed correctly. [ 'render_type' => 'not-wrapped', - 'expected' => 'not-wrapped', + 'expected' => 'not-wrapped', ], // Test that top-level comments (which are not lead by text nodes) are // inserted without wrapper. @@ -137,36 +136,34 @@ [ 'method' => 'html', 'render_type' => 'mixed', - 'expected' => '
foo foo bar

some string

additional not wrapped strings,

final string

', + 'expected' => '
foo foo bar

some string

additional not wrapped strings,

final string

', ], // Test that inline response data. [ 'render_type' => 'inline', - 'expected' => 'inline
BLOCK LEVEL
', + 'expected' => 'inline
BLOCK LEVEL
', ], // Test that empty response data. [ 'render_type' => 'empty', - 'expected' => '', + 'expected' => '', ], ]; - foreach (['html', 'replaceWith'] as $method) { - foreach ($test_cases as $test_case) { - $this->drupalGet('ajax-test/insert'); - $this->clickLink("Link $method {$test_case['render_type']}"); - $assert->assertWaitOnAjaxRequest(); - switch ($method) { - case 'html': - $assert->responseContains($this->wrapAjaxTarget($test_case['expected'])); - break; - - case 'replaceWith': - $assert->responseContains($test_case['expected']); - $assert->responseNotContains($this->wrapAjaxTarget($test_case['expected'])); - break; - } - } + $this->drupalGet('ajax-test/insert'); + foreach ($test_cases as $test_case) { + $this->clickLink("Link html {$test_case['render_type']}"); + $assert->assertWaitOnAjaxRequest(); + // Extra span added by a second prepend command on the ajax requests. + $assert->responseContains($this->wrapAjaxTarget('' . $test_case['expected'])); + } + + foreach ($test_cases as $test_case) { + $this->drupalGet('ajax-test/insert'); + $this->clickLink("Link replaceWith {$test_case['render_type']}"); + $assert->assertWaitOnAjaxRequest(); + $assert->responseContains($test_case['expected']); + $assert->responseNotContains($this->wrapAjaxTarget($test_case['expected'])); } }