Commit e4e74fcf authored by alexpott's avatar alexpott

Issue #1959574 by dawehner, mkadin, Wim Leers, jessebeach: Remove the deprecated Drupal 7 Ajax API.

parent c3b12738
......@@ -410,13 +410,15 @@ services:
arguments: ['@controller_resolver', '@string_translation', '@title_resolver']
controller.ajax:
class: Drupal\Core\Controller\AjaxController
arguments: ['@controller_resolver']
arguments: ['@controller_resolver', '@ajax_response_renderer']
controller.entityform:
class: Drupal\Core\Entity\HtmlEntityFormController
arguments: ['@controller_resolver', '@service_container', '@entity.manager']
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:
......@@ -428,7 +430,7 @@ services:
class: Drupal\Core\EventSubscriber\ViewSubscriber
tags:
- { name: event_subscriber }
arguments: ['@content_negotiation', '@title_resolver']
arguments: ['@content_negotiation', '@title_resolver', '@ajax_response_renderer']
html_view_subscriber:
class: Drupal\Core\EventSubscriber\HtmlViewSubscriber
tags:
......
......@@ -5,8 +5,6 @@
* Functions for use with Drupal's Ajax framework.
*/
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* @defgroup ajax Ajax framework
* @{
......@@ -50,7 +48,8 @@
* #ajax['callback'], which returns the form element that has been updated
* and needs to be returned to the browser, or alternatively, an array of
* custom Ajax commands.
* - The array is serialized using ajax_render() and sent to the browser.
* - The array is serialized using
* \Drupal\Core\Ajax\AjaxResponse::ajaxRender() and sent to the browser.
* - The browser unserializes the returned JSON string into an array of
* command objects and executes each command, resulting in the old page
* content within and including the HTML element specified by
......@@ -79,7 +78,7 @@
* 'wrapper' => 'replace_textfield_div',
* ),
* );
*
* // This entire form element will be replaced with an updated value.
* $form['replace_textfield'] = array(
* '#type' => 'textfield',
......@@ -125,11 +124,12 @@
* executed by the calling code.
* - #ajax['path']: The menu path to use for the request. This is often omitted
* and the default is used. This path should map
* to a menu page callback that returns data using ajax_render(). Defaults to
* 'system/ajax', which invokes \Drupal\system\FormAjaxController::content(),
* eventually calling the function named in #ajax['callback']. If you use a
* custom path, you must set up the menu entry and handle the entire callback
* in your own code.
* to a controller that returns data using
* \Drupal\Core\Ajax\AjaxResponse::ajaxRender(). Defaults to 'system/ajax',
* which invokes \Drupal\system\FormAjaxController::content(), eventually
* calling the function named in #ajax['callback']. If you use a custom path,
* you must set up the menu entry and handle the entire callback in your own
* code.
* - #ajax['wrapper']: The CSS ID of the area to be replaced by the content
* returned by the #ajax['callback'] function. The content returned from
* the callback will replace the entire element named by #ajax['wrapper'].
......@@ -185,9 +185,10 @@
* '/ajax/' so that the submit handler can tell if the form was submitted
* in a degraded state or not.
*
* When responding to Ajax requests, the server should do what it needs to do
* for that request, then create a commands array. This commands array will
* be converted to a JSON object and returned to the client, which will then
* As a developer you basically create a \Drupal\Core\Ajax\AjaxResponse and add
* a couple of \Drupal\Core\Ajax\CommandInterface onto it, which will be
* converted to a commands array automatically. This commands array will be
* converted to a JSON object and returned to the client, which will then
* iterate over the array and process it like a macro language.
*
* Each command item is an associative array which will be converted to a
......@@ -559,266 +560,3 @@ function ajax_pre_render_element($element) {
/**
* @} End of "defgroup ajax".
*/
/**
* @defgroup ajax_commands Ajax framework commands
* @{
* Functions to create various Ajax commands.
*
* These functions can be used to create arrays for use with the
* ajax_render() function.
*/
/**
* Creates a Drupal Ajax 'alert' command.
*
* The 'alert' command instructs the client to display a JavaScript alert
* dialog box.
*
* This command is implemented by Drupal.AjaxCommands.prototype.alert()
* defined in misc/ajax.js.
*
* @param $text
* The message string to display to the user.
*
* @return
* An array suitable for use with the ajax_render() function.
*/
function ajax_command_alert($text) {
return array(
'command' => 'alert',
'text' => $text,
);
}
/**
* Creates a Drupal Ajax 'insert' command using the method in #ajax['method'].
*
* This command instructs the client to insert the given HTML using whichever
* jQuery DOM manipulation method has been specified in the #ajax['method']
* variable of the element that triggered the request.
*
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
* defined in misc/ajax.js.
*
* @param $selector
* A jQuery selector string. If the command is a response to a request from
* an #ajax form element then this value can be NULL.
* @param $html
* The data to use with the jQuery method.
* @param $settings
* An optional array of settings that will be used for this command only.
*
* @return
* An array suitable for use with the ajax_render() function.
*/
function ajax_command_insert($selector, $html, $settings = NULL) {
return array(
'command' => 'insert',
'method' => NULL,
'selector' => $selector,
'data' => $html,
'settings' => $settings,
);
}
/**
* Creates a Drupal Ajax 'insert/prepend' command.
*
* The 'insert/prepend' command instructs the client to use jQuery's prepend()
* method to prepend the given HTML content to the inside each element matched
* by the given selector.
*
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
* defined in misc/ajax.js.
*
* @param $selector
* A jQuery selector string. If the command is a response to a request from
* an #ajax form element then this value can be NULL.
* @param $html
* The data to use with the jQuery prepend() method.
* @param $settings
* An optional array of settings that will be used for this command only.
*
* @return
* An array suitable for use with the ajax_render() function.
*
* @see http://docs.jquery.com/Manipulation/prepend#content
*/
function ajax_command_prepend($selector, $html, $settings = NULL) {
return array(
'command' => 'insert',
'method' => 'prepend',
'selector' => $selector,
'data' => $html,
'settings' => $settings,
);
}
/**
* Creates a Drupal Ajax 'insert/append' command.
*
* The 'insert/append' command instructs the client to use jQuery's append()
* method to append the given HTML content to the inside of each element matched
* by the given selector.
*
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
* defined in misc/ajax.js.
*
* @param $selector
* A jQuery selector string. If the command is a response to a request from
* an #ajax form element then this value can be NULL.
* @param $html
* The data to use with the jQuery append() method.
* @param $settings
* An optional array of settings that will be used for this command only.
*
* @return
* An array suitable for use with the ajax_render() function.
*
* @see http://docs.jquery.com/Manipulation/append#content
*/
function ajax_command_append($selector, $html, $settings = NULL) {
return array(
'command' => 'insert',
'method' => 'append',
'selector' => $selector,
'data' => $html,
'settings' => $settings,
);
}
/**
* Creates a Drupal Ajax 'remove' command.
*
* The 'remove' command instructs the client to use jQuery's remove() method
* to remove each of elements matched by the given selector, and everything
* within them.
*
* This command is implemented by Drupal.AjaxCommands.prototype.remove()
* defined in misc/ajax.js.
*
* @param $selector
* A jQuery selector string. If the command is a response to a request from
* an #ajax form element then this value can be NULL.
*
* @return
* An array suitable for use with the ajax_render() function.
*
* @see http://docs.jquery.com/Manipulation/remove#expr
*/
function ajax_command_remove($selector) {
return array(
'command' => 'remove',
'selector' => $selector,
);
}
/**
* Creates a Drupal Ajax 'changed' command.
*
* This command instructs the client to mark each of the elements matched by the
* given selector as 'ajax-changed'.
*
* This command is implemented by Drupal.AjaxCommands.prototype.changed()
* defined in misc/ajax.js.
*
* @param $selector
* A jQuery selector string. If the command is a response to a request from
* an #ajax form element then this value can be NULL.
* @param $asterisk
* An optional CSS selector which must be inside $selector. If specified,
* an asterisk will be appended to the HTML inside the $asterisk selector.
*
* @return
* An array suitable for use with the ajax_render() function.
*/
function ajax_command_changed($selector, $asterisk = '') {
return array(
'command' => 'changed',
'selector' => $selector,
'asterisk' => $asterisk,
);
}
/**
* Creates a Drupal Ajax 'css' command.
*
* The 'css' command will instruct the client to use the jQuery css() method
* to apply the CSS arguments to elements matched by the given selector.
*
* This command is implemented by Drupal.AjaxCommands.prototype.css()
* defined in misc/ajax.js.
*
* @param $selector
* A jQuery selector string. If the command is a response to a request from
* an #ajax form element then this value can be NULL.
* @param $argument
* An array of key/value pairs to set in the CSS for the selector.
*
* @return
* An array suitable for use with the ajax_render() function.
*
* @see http://docs.jquery.com/CSS/css#properties
*/
function ajax_command_css($selector, $argument) {
return array(
'command' => 'css',
'selector' => $selector,
'argument' => $argument,
);
}
/**
* Creates a Drupal Ajax 'settings' command.
*
* The 'settings' command instructs the client either to use the given array as
* the settings for ajax-loaded content or to extend drupalSettings with the
* given array, depending on the value of the $merge parameter.
*
* This command is implemented by Drupal.AjaxCommands.prototype.settings()
* defined in misc/ajax.js.
*
* @param $argument
* An array of key/value pairs to add to the settings. This will be utilized
* for all commands after this if they do not include their own settings
* array.
* @param $merge
* Whether or not the passed settings in $argument should be merged into the
* global drupalSettings on the page. By default (FALSE), the settings that
* are passed to Drupal.attachBehaviors will not include the global
* drupalSettings.
*
* @return
* An array suitable for use with the ajax_render() function.
*/
function ajax_command_settings($argument, $merge = FALSE) {
return array(
'command' => 'settings',
'settings' => $argument,
'merge' => $merge,
);
}
/**
* Creates a Drupal Ajax 'add_css' command.
*
* This method will add css via ajax in a cross-browser compatible way.
*
* This command is implemented by Drupal.AjaxCommands.prototype.add_css()
* defined in misc/ajax.js.
*
* @param $styles
* A string that contains the styles to be added.
*
* @return
* An array suitable for use with the ajax_render() function.
*
* @see misc/ajax.js
*/
function ajax_command_add_css($styles) {
return array(
'command' => 'add_css',
'data' => $styles,
);
}
<?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 an AjaxResponse, then return it without manipulation.
if ($content instanceof AjaxResponse && $content->isOk()) {
return $content;
}
// Allow controllers to return a HtmlFragment or a Response object directly.
if ($content instanceof HtmlFragment) {
$content = $content->getContent();
}
elseif ($content instanceof Response) {
$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 += element_info('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 = drupal_render($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 = drupal_render($status_messages);
if (!empty($output)) {
$response->addCommand(new PrependCommand(NULL, $output));
}
return $response;
}
}
......@@ -7,17 +7,12 @@
namespace Drupal\Core\Controller;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Ajax\PrependCommand;
use Drupal\Core\Page\HtmlFragment;
use Drupal\Core\Page\HtmlPage;
use Drupal\Core\Ajax\AjaxResponseRenderer;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Default controller for ajax requests.
* Default controller for Ajax requests.
*/
class AjaxController extends ContainerAware {
......@@ -28,63 +23,40 @@ class AjaxController extends ContainerAware {
*/
protected $controllerResolver;
/**
* The Ajax response renderer.
*
* @var \Drupal\Core\Ajax\AjaxResponseRenderer
*/
protected $ajaxRenderer;
/**
* 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.
*/
public function __construct(ControllerResolverInterface $controller_resolver) {
public function __construct(ControllerResolverInterface $controller_resolver, AjaxResponseRenderer $ajax_renderer) {
$this->controllerResolver = $controller_resolver;
$this->ajaxRenderer = $ajax_renderer;
}
/**
* Controller method for AJAX content.
* Controller method for Ajax content.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param callable $_content
* The callable that returns the content of the ajax response.
* The callable that returns the content of the Ajax response.
*
* @return \Symfony\Component\HttpFoundation\Response
* @return \Drupal\Core\Ajax\AjaxResponse
* A response object.
*/
public function content(Request $request, $_content) {
$content = $this->getContentResult($request, $_content);
// If there is already an AjaxResponse, then return it without
// manipulation.
if ($content instanceof AjaxResponse && $content->isOk()) {
return $content;
}
// Allow controllers to return a HtmlFragment or a Response object directly.
if ($content instanceof HtmlFragment) {
$content = $content->getContent();
}
if ($content instanceof Response) {
$content = $content->getContent();
}
// Most controllers return a render array, but some return a string.
if (!is_array($content)) {
$content = array(
'#markup' => $content,
);
}
$html = drupal_render($content);
$response = new AjaxResponse();
// 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 = drupal_render($status_messages);
if (!empty($output)) {
$response->addCommand(new PrependCommand(NULL, $output));
}
return $response;
return $this->ajaxRenderer->render($content);
}
/**
......
......@@ -7,6 +7,7 @@
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Ajax\AjaxResponseRenderer;
use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Page\HtmlPage;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
......@@ -42,6 +43,13 @@ class ViewSubscriber implements EventSubscriberInterface {
*/
protected $titleResolver;
/**
* The Ajax response renderer.
*
* @var \Drupal\Core\Ajax\AjaxResponseRenderer
*/
protected $ajaxRenderer;
/**
* Constructs a new ViewSubscriber.
*
......@@ -49,10 +57,13 @@ class ViewSubscriber implements EventSubscriberInterface {
* The content negotiation.
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver.
* @param \Drupal\Core\Ajax\AjaxResponseRenderer $ajax_renderer
* The ajax response renderer.
*/
public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver) {
public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver, AjaxResponseRenderer $ajax_renderer) {
$this->negotiation = $negotiation;
$this->titleResolver = $title_resolver;
$this->ajaxRenderer = $ajax_renderer;
}
/**
......@@ -118,26 +129,8 @@ public function onJson(GetResponseForControllerResultEvent $event) {
return $response;
}
public function onAjax(GetResponseForControllerResultEvent $event) {
$page_callback_result = $event->getControllerResult();
// Construct the response content from the page callback result.
$commands = ajax_prepare_response($page_callback_result);
$json = ajax_render($commands);
// Build the actual response object.
$response = new JsonResponse();
$response->setContent($json);
return $response;
}
public function onIframeUpload(GetResponseForControllerResultEvent $event) {
$page_callback_result = $event->getControllerResult();
// Construct the response content from the page callback result.
$commands = ajax_prepare_response($page_callback_result);
$json = ajax_render($commands);
$response = $event->getResponse();
// Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
// and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into
......@@ -145,7 +138,7 @@ public function onIframeUpload(GetResponseForControllerResultEvent $event) {
// JSON data by making it the value of a textarea.
// @see http://malsup.com/jquery/form/#file-upload
// @see http://drupal.org/node/1009382
$html = '<textarea>' . $json . '</textarea>';
$html = '<textarea>' . $response->getContent() . '</textarea>';
return new Response($html);
}
......
......@@ -37,7 +37,10 @@ public function __construct(ContentNegotiation $negotiation) {
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request) {
if (empty($defaults['_content']) && $defaults['_controller'] != 'controller.ajax:content' && $this->negotiation->getContentType($request) == 'drupal_ajax') {
// A request can have the 'ajax' content type when the controller supports
// basically both simple HTML and Ajax routes by returning a render array.
// In those cases we want to convert it to a proper ajax response as well.
if (empty($defaults['_content']) && $defaults['_controller'] != 'controller.ajax:content' && in_array($this->negotiation->getContentType($request), array('drupal_ajax', 'ajax', 'iframeupload'))) {
$defaults['_content'] = isset($defaults['_controller']) ? $defaults['_controller'] : NULL;
$defaults['_controller'] = 'controller.ajax:content';
}
......
......@@ -1667,7 +1667,8 @@ protected function drupalProcessAjaxResponse($content, array $ajax_response, arr
}
// @todo Ajax commands can target any jQuery selector, but these are
// hard to fully emulate with XPath. For now, just handle 'head'
// and 'body', since these are used by ajax_render().
// and 'body', since these are used by
// \Drupal\Core\Ajax\AjaxResponse::ajaxRender().
elseif (in_array($command['selector'], array('head', 'body'))) {
$wrapperNode = $xpath->query('//' . $command['selector'])->item(0);
}
......
......@@ -24,22 +24,22 @@ abstract class AjaxTestBase extends WebTestBase {
/**
* Asserts the array of Ajax commands contains the searched command.
*
* The Ajax framework, via the ajax_render() function, returns an array of
* commands. This array sometimes includes commands automatically provided by
* the framework in addition to commands returned by a particular page
* callback. During testing, we're usually interested that a particular
* command is present, and don't care whether other commands precede or
* follow the one we're interested in. Additionally, the command we're
* interested in may include additional data that we're not interested in.
* Therefore, this function simply asserts that one of the commands in
* $haystack contains all of the keys and values in $needle. Furthermore, if
* $needle contains a 'settings' key with an array value, we simply assert
* that all keys and values within that array are present in the command we're
* checking, and do not consider it a failure if the actual command contains
* additional settings that aren't part of $needle.
* An AjaxResponse object stores an array of Ajax commands. This array
* sometimes includes commands automatically provided by the framework in
* addition to commands returned by a particular controller. During testing,
* we're usually interested that a particular command is present, and don't
* care whether other commands precede or follow the one we're interested in.
* Additionally, the command we're interested in may include additional data
* that we're not interested in. Therefore, this function simply asserts that
* one of the commands in $haystack contains all of the keys and values in