Commit 40db9b98 authored by alexpott's avatar alexpott

Issue #2364127 by Wim Leers: Merge AjaxResponseRenderer into AjaxController.

parent 62da091f
......@@ -655,12 +655,10 @@ services:
arguments: ['@controller_resolver', '@title_resolver', '@render_html_renderer']
controller.ajax:
class: Drupal\Core\Controller\AjaxController
arguments: ['@controller_resolver', '@ajax_response_renderer']
arguments: ['@controller_resolver', '@element_info']
controller.dialog:
class: Drupal\Core\Controller\DialogController
arguments: ['@controller_resolver', '@title_resolver']
ajax_response_renderer:
class: Drupal\Core\Ajax\AjaxResponseRenderer
router_listener:
class: Symfony\Component\HttpKernel\EventListener\RouterListener
tags:
......@@ -672,7 +670,7 @@ services:
class: Drupal\Core\EventSubscriber\ViewSubscriber
tags:
- { name: event_subscriber }
arguments: ['@content_negotiation', '@title_resolver', '@ajax_response_renderer']
arguments: ['@content_negotiation', '@title_resolver']
html_view_subscriber:
class: Drupal\Core\EventSubscriber\HtmlViewSubscriber
tags:
......
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\AjaxResponseRenderer.
*/
namespace Drupal\Core\Ajax;
use Drupal\Core\Page\HtmlFragment;
use Symfony\Component\HttpFoundation\Response;
/**
* Converts a controller result into an Ajax response object.
*/
class AjaxResponseRenderer {
/**
* Converts the output of a controller into an Ajax response object.
*
* @var mixed $content
* The return value of a controller, for example a string, a render array, a
* HtmlFragment object, a Response object or even an AjaxResponse itself.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An Ajax response containing the controller result.
*/
public function render($content) {
// If there is already a Response object, return it without manipulation.
if ($content instanceof Response && $content->isOk()) {
return $content;
}
// Allow controllers to return an HtmlFragment directly.
if ($content instanceof HtmlFragment) {
$content = $content->getContent();
}
// Most controllers return a render array, but some return a string.
if (!is_array($content)) {
$content = array(
'#markup' => $content,
);
}
$response = new AjaxResponse();
if (isset($content['#type']) && ($content['#type'] == 'ajax')) {
// Complex Ajax callbacks can return a result that contains an error
// message or a specific set of commands to send to the browser.
$content += $this->elementInfo('ajax');
$error = $content['#error'];
if (!empty($error)) {
// Fall back to some default message otherwise use the specific one.
if (!is_string($error)) {
$error = 'An error occurred while handling the request: The server received invalid input.';
}
$response->addCommand(new AlertCommand($error));
}
}
$html = $this->drupalRenderRoot($content);
// The selector for the insert command is NULL as the new content will
// replace the element making the Ajax call. The default 'replaceWith'
// behavior can be changed with #ajax['method'].
$response->addCommand(new InsertCommand(NULL, $html));
$status_messages = array('#theme' => 'status_messages');
$output = $this->drupalRenderRoot($status_messages);
if (!empty($output)) {
$response->addCommand(new PrependCommand(NULL, $output));
}
return $response;
}
/**
* Wraps drupal_render_root().
*
* @todo: Remove as part of https://drupal.org/node/2182149
*/
protected function drupalRenderRoot(&$elements) {
$output = drupal_render_root($elements);
drupal_process_attached($elements);
return $output;
}
/**
* Wraps element_info().
*/
protected function elementInfo($type) {
return element_info($type);
}
}
......@@ -7,10 +7,16 @@
namespace Drupal\Core\Controller;
use Drupal\Core\Ajax\AjaxResponseRenderer;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Ajax\PrependCommand;
use Drupal\Core\Page\HtmlFragment;
use Drupal\Core\Render\ElementInfoManagerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Default controller for Ajax requests.
......@@ -27,23 +33,23 @@ class AjaxController implements ContainerAwareInterface {
protected $controllerResolver;
/**
* The Ajax response renderer.
* The element info manager.
*
* @var \Drupal\Core\Ajax\AjaxResponseRenderer
* @var \Drupal\Core\Render\ElementInfoManagerInterface
*/
protected $ajaxRenderer;
protected $elementInfoManager;
/**
* Constructs a new AjaxController instance.
*
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
* The controller resolver.
* @param \Drupal\Core\Ajax\AjaxResponseRenderer $ajax_renderer
* The Ajax response renderer.
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info_manager
* The element info manager.
*/
public function __construct(ControllerResolverInterface $controller_resolver, AjaxResponseRenderer $ajax_renderer) {
public function __construct(ControllerResolverInterface $controller_resolver, ElementInfoManagerInterface $element_info_manager) {
$this->controllerResolver = $controller_resolver;
$this->ajaxRenderer = $ajax_renderer;
$this->elementInfoManager = $element_info_manager;
}
/**
......@@ -59,7 +65,51 @@ public function __construct(ControllerResolverInterface $controller_resolver, Aj
*/
public function content(Request $request, $_content) {
$content = $this->getContentResult($request, $_content);
return $this->ajaxRenderer->render($content);
// If there is already a Response object, return it without manipulation.
if ($content instanceof Response && $content->isOk()) {
return $content;
}
// Allow controllers to return an HtmlFragment directly.
if ($content instanceof HtmlFragment) {
$content = $content->getContent();
}
// Most controllers return a render array, but some return a string.
if (!is_array($content)) {
$content = array(
'#markup' => $content,
);
}
$response = new AjaxResponse();
if (isset($content['#type']) && ($content['#type'] == 'ajax')) {
// Complex Ajax callbacks can return a result that contains an error
// message or a specific set of commands to send to the browser.
$content += $this->elementInfoManager->getInfo('ajax');
$error = $content['#error'];
if (!empty($error)) {
// Fall back to some default message otherwise use the specific one.
if (!is_string($error)) {
$error = 'An error occurred while handling the request: The server received invalid input.';
}
$response->addCommand(new AlertCommand($error));
}
}
$html = $this->drupalRenderRoot($content);
// The selector for the insert command is NULL as the new content will
// replace the element making the Ajax call. The default 'replaceWith'
// behavior can be changed with #ajax['method'].
$response->addCommand(new InsertCommand(NULL, $html));
$status_messages = array('#theme' => 'status_messages');
$output = $this->drupalRenderRoot($status_messages);
if (!empty($output)) {
$response->addCommand(new PrependCommand(NULL, $output));
}
return $response;
}
/**
......@@ -87,4 +137,15 @@ public function getContentResult(Request $request, $controller_definition) {
return $page_content;
}
/**
* Wraps drupal_render_root().
*
* @todo: Remove as part of https://drupal.org/node/2182149
*/
protected function drupalRenderRoot(&$elements) {
$output = drupal_render_root($elements);
drupal_process_attached($elements);
return $output;
}
}
......@@ -2,66 +2,63 @@
/**
* @file
* Contains \Drupal\Tests\Core\Ajax\AjaxResponseRendererTest.
* Contains \Drupal\Tests\Core\Controller\AjaxControllerTest.
*/
namespace Drupal\Tests\Core\Ajax;
namespace Drupal\Tests\Core\Controller;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AjaxResponseRenderer;
use Drupal\Core\Page\HtmlFragment;
use Drupal\Core\Controller\AjaxController;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* @coversDefaultClass \Drupal\Core\Ajax\AjaxResponseRenderer
* @coversDefaultClass \Drupal\Core\Controller\AjaxControllerTest
* @group Ajax
*/
class AjaxResponseRendererTest extends UnitTestCase {
class AjaxControllerTest extends UnitTestCase {
/**
* The tested ajax response renderer.
* The tested ajax controller.
*
* @var \Drupal\Tests\Core\Ajax\TestAjaxResponseRenderer
* @var \Drupal\Tests\Core\Controller\TestAjaxController
*/
protected $ajaxResponseRenderer;
protected $ajaxController;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->ajaxResponseRenderer = new TestAjaxResponseRenderer();
$controller_resolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface');
$controller_resolver->expects($this->any())
->method('getArguments')
->willReturn([]);
$element_info_manager = $this->getMock('Drupal\Core\Render\ElementInfoManagerInterface');
$element_info_manager->expects($this->any())
->method('getInfo')
->with('ajax')
->willReturn([
'#header' => TRUE,
'#commands' => array(),
'#error' => NULL,
]);
$this->ajaxController = new TestAjaxController($controller_resolver, $element_info_manager);
}
/**
* Tests the render method with an HtmlFragment object.
* Tests the renderMainContent method.
*
* @covers \Drupal\Core\Ajax\AjaxResponseRenderer::render
* @covers \Drupal\Core\Controller\AjaxController::renderContentIntoResponse
*/
public function testRenderWithFragmentObject() {
$html_fragment = new HtmlFragment('example content');
/** @var \Drupal\Core\Ajax\AjaxResponse $result */
$result = $this->ajaxResponseRenderer->render($html_fragment);
$this->assertInstanceOf('Drupal\Core\Ajax\AjaxResponse', $result);
$commands = $result->getCommands();
$this->assertEquals('insert', $commands[0]['command']);
$this->assertEquals('example content', $commands[0]['data']);
$this->assertEquals('insert', $commands[1]['command']);
$this->assertEquals('status_messages', $commands[1]['data']);
}
/**
* Tests the render method with an HtmlFragment object.
*
* @covers \Drupal\Core\Ajax\AjaxResponseRenderer::render
*/
public function testRenderWithString() {
$html_fragment = 'example content';
$main_content = ['#markup' => 'example content'];
$request = new Request();
$_content = function() use ($main_content) {
return $main_content;
};
/** @var \Drupal\Core\Ajax\AjaxResponse $result */
$result = $this->ajaxResponseRenderer->render($html_fragment);
$result = $this->ajaxController->content($request, $_content);
$this->assertInstanceOf('Drupal\Core\Ajax\AjaxResponse', $result);
......@@ -73,35 +70,42 @@ public function testRenderWithString() {
$this->assertEquals('status_messages', $commands[1]['data']);
}
/**
* Tests the render method with a response object.
* Tests the handle method with a Json response object.
*
* @covers \Drupal\Core\Ajax\AjaxResponseRenderer::render
* @covers \Drupal\Core\Controller\AjaxController::handle
*/
public function testRenderWithResponseObject() {
$json_response = new JsonResponse(array('foo' => 'bar'));
$this->assertSame($json_response, $this->ajaxResponseRenderer->render($json_response));
$request = new Request();
$_content = function() use ($json_response) {
return $json_response;
};
$this->assertSame($json_response, $this->ajaxController->content($request, $_content));
}
/**
* Tests the render method with an Ajax response object.
* Tests the handle method with an Ajax response object.
*
* @covers \Drupal\Core\Ajax\AjaxResponseRenderer::render
* @covers \Drupal\Core\Controller\AjaxController::handle
*/
public function testRenderWithAjaxResponseObject() {
$ajax_response = new AjaxResponse(array('foo' => 'bar'));
$this->assertSame($ajax_response, $this->ajaxResponseRenderer->render($ajax_response));
$request = new Request();
$_content = function() use ($ajax_response) {
return $ajax_response;
};
$this->assertSame($ajax_response, $this->ajaxController->content($request, $_content));
}
}
class TestAjaxResponseRenderer extends AjaxResponseRenderer {
class TestAjaxController extends AjaxController {
/**
* {@inheritdoc}
*/
protected function drupalRenderRoot(&$elements) {
protected function drupalRenderRoot(&$elements, $is_root_call = FALSE) {
if (isset($elements['#markup'])) {
return $elements['#markup'];
}
......@@ -113,20 +117,4 @@ protected function drupalRenderRoot(&$elements) {
}
}
/**
* {@inheritdoc}
*/
protected function elementInfo($type) {
if ($type == 'ajax') {
return array(
'#header' => TRUE,
'#commands' => array(),
'#error' => NULL,
);
}
else {
return array();
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment