From 95feb071fb1393747df59e2c04b39320160d9b06 Mon Sep 17 00:00:00 2001 From: Dries <dries@buytaert.net> Date: Fri, 16 Nov 2012 18:05:11 -0800 Subject: [PATCH] Issue #1812866 by mkadin, effulgentsia: rebuild the server side AJAX API. --- core/lib/Drupal/Core/Ajax/AddCssCommand.php | 53 +++ core/lib/Drupal/Core/Ajax/AfterCommand.php | 40 +++ core/lib/Drupal/Core/Ajax/AjaxResponse.php | 131 ++++++++ core/lib/Drupal/Core/Ajax/AlertCommand.php | 45 +++ core/lib/Drupal/Core/Ajax/AppendCommand.php | 40 +++ core/lib/Drupal/Core/Ajax/BeforeCommand.php | 40 +++ core/lib/Drupal/Core/Ajax/ChangedCommand.php | 65 ++++ .../lib/Drupal/Core/Ajax/CommandInterface.php | 22 ++ core/lib/Drupal/Core/Ajax/CssCommand.php | 80 +++++ core/lib/Drupal/Core/Ajax/DataCommand.php | 79 +++++ core/lib/Drupal/Core/Ajax/HtmlCommand.php | 40 +++ core/lib/Drupal/Core/Ajax/InsertCommand.php | 78 +++++ core/lib/Drupal/Core/Ajax/InvokeCommand.php | 78 +++++ core/lib/Drupal/Core/Ajax/PrependCommand.php | 40 +++ core/lib/Drupal/Core/Ajax/RemoveCommand.php | 53 +++ core/lib/Drupal/Core/Ajax/ReplaceCommand.php | 40 +++ core/lib/Drupal/Core/Ajax/RestripeCommand.php | 54 +++ core/lib/Drupal/Core/Ajax/SettingsCommand.php | 69 ++++ core/modules/file/file.module | 17 +- .../Tests/Ajax/AjaxCommandsUnitTest.php | 309 ++++++++++++++++++ 20 files changed, 1364 insertions(+), 9 deletions(-) create mode 100644 core/lib/Drupal/Core/Ajax/AddCssCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/AfterCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/AjaxResponse.php create mode 100644 core/lib/Drupal/Core/Ajax/AlertCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/AppendCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/BeforeCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/ChangedCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/CommandInterface.php create mode 100644 core/lib/Drupal/Core/Ajax/CssCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/DataCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/HtmlCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/InsertCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/InvokeCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/PrependCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/RemoveCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/ReplaceCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/RestripeCommand.php create mode 100644 core/lib/Drupal/Core/Ajax/SettingsCommand.php create mode 100644 core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php diff --git a/core/lib/Drupal/Core/Ajax/AddCssCommand.php b/core/lib/Drupal/Core/Ajax/AddCssCommand.php new file mode 100644 index 000000000000..a5dc5bd490dd --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/AddCssCommand.php @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\AddCssCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * An AJAX command for adding css to the page via ajax. + * + * This command is implemented by Drupal.ajax.prototype.commands.add_css() + * defined in misc/ajax.js. + * + * @see misc/ajax.js + */ +class AddCssCommand implements CommandInterface { + + /** + * A string that contains the styles to be added to the page. + * + * It should include the wrapping style tag. + * + * @var string + */ + protected $styles; + + /** + * Constructs an AddCssCommand. + * + * @param string $styles + * A string that contains the styles to be added to the page, including the + * wrapping <style> tag. + */ + public function __construct($styles) { + $this->styles = $styles; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'add_css', + 'data' => $this->styles, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/AfterCommand.php b/core/lib/Drupal/Core/Ajax/AfterCommand.php new file mode 100644 index 000000000000..096d9bc6d65b --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/AfterCommand.php @@ -0,0 +1,40 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\AfterCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\InsertCommand; + +/** + * An AJAX command for calling the jQuery after() method. + * + * The 'insert/after' command instructs the client to use jQuery's after() + * method to insert the given HTML content after each element matched by the + * given selector. + * + * This command is implemented by Drupal.ajax.prototype.commands.insert() + * defined in misc/ajax.js. + * + * @see http://docs.jquery.com/Manipulation/after#content + */ +class AfterCommand extends InsertCommand { + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'insert', + 'method' => 'after', + 'selector' => $this->selector, + 'data' => $this->html, + 'settings' => $this->settings, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php new file mode 100644 index 000000000000..12250b991eb1 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php @@ -0,0 +1,131 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\AjaxResponse. + */ + +namespace Drupal\Core\Ajax; + +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; + +/** + * JSON response object for AJAX requests. + */ +class AjaxResponse extends JsonResponse { + + /** + * The array of ajax commands. + * + * @var array + */ + protected $commands = array(); + + /** + * Add an AJAX command to the response. + * + * @param object $command + * An AJAX command object implementing CommandInterface. + * + * @return AjaxResponse + * The current AjaxResponse. + */ + public function addCommand($command) { + $this->commands[] = $command->render(); + return $this; + } + + /** + * Sets the response's data to be the array of AJAX commands. + * + * @param + * $request A request object. + * + * @return + * Response The current response. + */ + public function prepare(Request $request) { + + parent::setData($this->ajaxRender($request)); + return parent::prepare($request); + } + + /** + * Prepares the AJAX commands for sending back to the client. + * + * @param Request + * The request object that the AJAX is responding to. + * + * @return array + * An array of commands ready to be returned as JSON. + */ + protected function ajaxRender($request) { + // Ajax responses aren't rendered with html.tpl.php, so we have to call + // drupal_get_css() and drupal_get_js() here, in order to have new files + // added during this request to be loaded by the page. We only want to send + // back files that the page hasn't already loaded, so we implement simple + // diffing logic using array_diff_key(). + foreach (array('css', 'js') as $type) { + // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty, + // since the base page ought to have at least one JS file and one CSS file + // loaded. It probably indicates an error, and rather than making the page + // reload all of the files, instead we return no new files. + if (empty($request->parameters['ajax_page_state'][$type])) { + $items[$type] = array(); + } + else { + $function = 'drupal_add_' . $type; + $items[$type] = $function(); + drupal_alter($type, $items[$type]); + // @todo Inline CSS and JS items are indexed numerically. These can't be + // reliably diffed with array_diff_key(), since the number can change + // due to factors unrelated to the inline content, so for now, we + // strip the inline items from Ajax responses, and can add support for + // them when drupal_add_css() and drupal_add_js() are changed to using + // md5() or some other hash of the inline content. + foreach ($items[$type] as $key => $item) { + if (is_numeric($key)) { + unset($items[$type][$key]); + } + } + // Ensure that the page doesn't reload what it already has. + $items[$type] = array_diff_key($items[$type], $request->parameters['ajax_page_state'][$type]); + } + } + + // Render the HTML to load these files, and add AJAX commands to insert this + // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the + // data from being altered again, as we already altered it above. Settings + // are handled separately, afterwards. + if (isset($items['js']['settings'])) { + unset($items['js']['settings']); + } + $styles = drupal_get_css($items['css'], TRUE); + $scripts_footer = drupal_get_js('footer', $items['js'], TRUE); + $scripts_header = drupal_get_js('header', $items['js'], TRUE); + + if (!empty($styles)) { + $this->addCommand(new AddCssCommand($styles)); + } + if (!empty($scripts_header)) { + $this->addCommand(new PrependCommand('head', $scripts_header)); + } + if (!empty($scripts_footer)) { + $this->addCommand(new AppendCommand('body', $scripts_footer)); + } + + // Now add a command to merge changes and additions to Drupal.settings. + $scripts = drupal_add_js(); + if (!empty($scripts['settings'])) { + $settings = $scripts['settings']; + $this->addCommand(new SettingsCommand(call_user_func_array('array_merge_recursive', $settings['data']), TRUE)); + } + + $commands = $this->commands; + drupal_alter('ajax_render', $commands); + + return $commands; + } + +} diff --git a/core/lib/Drupal/Core/Ajax/AlertCommand.php b/core/lib/Drupal/Core/Ajax/AlertCommand.php new file mode 100644 index 000000000000..02a96a39607f --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/AlertCommand.php @@ -0,0 +1,45 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\AlertCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * AJAX command for a javascript alert box. + */ +class AlertCommand implements CommandInterface { + + /** + * The text to be displayed in the alert box. + * + * @var string + */ + protected $text; + + /** + * Constructs an AlertCommand object. + * + * @param string $text + * The text to be displayed in the alert box. + */ + public function __construct($text) { + $this->text = $text; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'alert', + 'text' => $this->text, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/AppendCommand.php b/core/lib/Drupal/Core/Ajax/AppendCommand.php new file mode 100644 index 000000000000..7ba98bd81158 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/AppendCommand.php @@ -0,0 +1,40 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\AppendCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\InsertCommand; + +/** + * An AJAX command for calling the jQuery append() method. + * + * 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.ajax.prototype.commands.insert() + * defined in misc/ajax.js. + * + * @see http://docs.jquery.com/Manipulation/append#content + */ +class AppendCommand extends InsertCommand { + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'insert', + 'method' => 'append', + 'selector' => $this->selector, + 'data' => $this->html, + 'settings' => $this->settings, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/BeforeCommand.php b/core/lib/Drupal/Core/Ajax/BeforeCommand.php new file mode 100644 index 000000000000..0d1dc856ba40 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/BeforeCommand.php @@ -0,0 +1,40 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\InsertCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\InsertCommand; + +/** + * An AJAX command for calling the jQuery before() method. + * + * The 'insert/before' command instructs the client to use jQuery's before() + * method to insert the given HTML content before each of elements matched by + * the given selector. + * + * This command is implemented by Drupal.ajax.prototype.commands.insert() + * defined in misc/ajax.js. + * + * @see http://docs.jquery.com/Manipulation/before#content + */ +class BeforeCommand extends InsertCommand { + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'insert', + 'method' => 'before', + 'selector' => $this->selector, + 'data' => $this->html, + 'settings' => $this->settings, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/ChangedCommand.php b/core/lib/Drupal/Core/Ajax/ChangedCommand.php new file mode 100644 index 000000000000..d3c7cdcd6781 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/ChangedCommand.php @@ -0,0 +1,65 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\ChangedCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * An AJAX command for marking HTML elements as changed. + * + * 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.ajax.prototype.commands.changed() + * defined in misc/ajax.js. + */ +class ChangedCommand implements CommandInterface { + + /** + * A CSS selector string. + * + * If the command is a response to a request from an #ajax form element then + * this value can be NULL. + * + * @var string + */ + protected $selector; + + /** + * An optional CSS selector for elements to which asterisks will be appended. + * + * @var string + */ + protected $asterisk; + + /** + * Constructs a ChangedCommand object. + * + * @param string $selector + * CSS selector for elements to be marked as changed. + * @param string $asterisk + * CSS selector for elements to which an asterisk will be appended. + */ + public function __construct($selector, $asterisk = '') { + $this->selector = $selector; + $this->asterisk = $asterisk; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'changed', + 'selector' => $this->selector, + 'asterisk' => $this->asterisk, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/CommandInterface.php b/core/lib/Drupal/Core/Ajax/CommandInterface.php new file mode 100644 index 000000000000..9de3af57d14b --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/CommandInterface.php @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\CommandInterface. + */ + +namespace Drupal\Core\Ajax; + +/** + * AJAX command interface. + * + * All AJAX commands passed to AjaxResponse objects should implement these + * methods. + */ +interface CommandInterface { + + /** + * Return an array to be run through json_encode and sent to the client. + */ + public function render(); +} diff --git a/core/lib/Drupal/Core/Ajax/CssCommand.php b/core/lib/Drupal/Core/Ajax/CssCommand.php new file mode 100644 index 000000000000..de55e7b8541a --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/CssCommand.php @@ -0,0 +1,80 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\CssCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * An AJAX command for calling the jQuery css() method. + * + * 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.ajax.prototype.commands.css() defined + * in misc/ajax.js. + * + * @see http://docs.jquery.com/CSS/css#properties + */ +class CssCommand implements CommandInterface { + + /** + * A CSS selector string. + * + * If the command is a response to a request from an #ajax form element then + * this value can be NULL. + * + * @var string + */ + protected $selector; + + /** + * An array of property/value pairs to set in the CSS for the selector. + * + * @var array + */ + protected $css = array(); + + /** + * Constructs a CssCommand object. + * + * @param string $selector + * A CSS selector for elements to which the CSS will be applied. + * @param array $css + * An array of CSS property/value pairs to set. + */ + public function __construct($selector, array $css = array()) { + $this->selector = $selector; + $this->css = $css; + } + + /** + * Adds a property/value pair to the CSS to be added to this element. + * + * @param $property + * The CSS property to be changed. + * @param $value + * The new value of the CSS property. + */ + public function setProperty($property, $value) { + $this->css[$property] = $value; + return $this; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'css', + 'selector' => $this->selector, + 'argument' => $this->css, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/DataCommand.php b/core/lib/Drupal/Core/Ajax/DataCommand.php new file mode 100644 index 000000000000..a45d4a9693ae --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/DataCommand.php @@ -0,0 +1,79 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\DataCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * An AJAX command for implementing jQuery's data() method. + * + * This instructs the client to attach the name=value pair of data to the + * selector via jQuery's data cache. + * + * This command is implemented by Drupal.ajax.prototype.commands.data() defined + * in misc/ajax.js. + */ +class DataCommand implements CommandInterface { + + /** + * A CSS selector string for elements to which data will be attached. + * + * If the command is a response to a request from an #ajax form element then + * this value can be NULL. + * + * @var string + */ + protected $selector; + + /** + * The key of the data attached to elements matched by the selector. + * + * @var string + */ + protected $name; + + /** + * The value of the data to be atached to elements matched by the selector. + * + * The data is not limited to strings; it can be any format. + * + * @var mixed + */ + protected $value; + + /** + * Constructs a DataCommand object. + * + * @param string $selector + * A CSS selector for the elements to which the data will be attached. + * @param string $name + * The key of the data to be attached to elements matched by the selector. + * @param type $value + * The value of the data to be attached to elements matched by the selector. + */ + public function __construct($selector, $name, $value) { + $this->selector = $selector; + $this->name = $name; + $this->value = $value; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'data', + 'selector' => $this->selector, + 'name' => $this->name, + 'value' => $this->value, + ); + } + +} + diff --git a/core/lib/Drupal/Core/Ajax/HtmlCommand.php b/core/lib/Drupal/Core/Ajax/HtmlCommand.php new file mode 100644 index 000000000000..d9bb0f6cff34 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/HtmlCommand.php @@ -0,0 +1,40 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\HtmlCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\InsertCommand; + +/** + * AJAX command for calling the jQuery html() method. + * + * The 'insert/html' command instructs the client to use jQuery's html() method + * to set the HTML content of each element matched by the given selector while + * leaving the outer tags intact. + * + * This command is implemented by Drupal.ajax.prototype.commands.insert() + * defined in misc/ajax.js. + * + * @see http://docs.jquery.com/Attributes/html#val + */ +class HtmlCommand extends InsertCommand { + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'insert', + 'method' => 'html', + 'selector' => $this->selector, + 'data' => $this->html, + 'settings' => $this->settings, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/InsertCommand.php b/core/lib/Drupal/Core/Ajax/InsertCommand.php new file mode 100644 index 000000000000..8c0e93815d46 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/InsertCommand.php @@ -0,0 +1,78 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\InsertCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * Generic AJAX command for inserting content. + * + * 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.ajax.prototype.commands.insert() + * defined in misc/ajax.js. + */ +class InsertCommand implements CommandInterface { + + /** + * A CSS selector string. + * + * If the command is a response to a request from an #ajax form element then + * this value can be NULL. + * + * @var string + */ + protected $selector; + + /** + * The HTML content that will replace the matched element(s). + * + * @var string + */ + protected $html; + + /** + * A settings array to be passed to any any attached JavaScript behavior. + * + * @var array + */ + protected $settings; + + /** + * Constructs an InsertCommand object. + * + * @param string $selector + * A CSS selector. + * @param string $html + * String of HTML that will replace the matched element(s). + * @param array $settings + * An array of JavaScript settings to be passed to any attached behaviors. + */ + public function __construct($selector, $html, array $settings = NULL) { + $this->selector = $selector; + $this->html = $html; + $this->settings = $settings; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'insert', + 'method' => NULL, + 'selector' => $this->selector, + 'data' => $this->html, + 'settings' => $this->settings, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/InvokeCommand.php b/core/lib/Drupal/Core/Ajax/InvokeCommand.php new file mode 100644 index 000000000000..ad5e96595438 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/InvokeCommand.php @@ -0,0 +1,78 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\InvokeCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * AJAX command for invoking an arbitrary jQuery method. + * + * The 'invoke' command will instruct the client to invoke the given jQuery + * method with the supplied arguments on the elements matched by the given + * selector. Intended for simple jQuery commands, such as attr(), addClass(), + * removeClass(), toggleClass(), etc. + * + * This command is implemented by Drupal.ajax.prototype.commands.invoke() + * defined in misc/ajax.js. + */ +class InvokeCommand implements CommandInterface { + + /** + * A CSS selector string. + * + * If the command is a response to a request from an #ajax form element then + * this value can be NULL. + * + * @var string + */ + protected $selector; + + /** + * A jQuery method to invoke. + * + * @var string + */ + protected $method; + + /** + * An optional list of arguments to pass to the method. + * + * @var array + */ + protected $arguments; + + /** + * Constructs an InvokeCommand object. + * + * @param string $selector + * A jQuery selector. + * @param string $method + * The name of a jQuery method to invoke. + * @param array $arguments + * An optional array of arguments to pass to the method. + */ + public function __construct($selector, $method, array $arguments = array()) { + $this->selector = $selector; + $this->method = $method; + $this->arguments = $arguments; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'invoke', + 'selector' => $this->selector, + 'method' => $this->method, + 'args' => $this->arguments, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/PrependCommand.php b/core/lib/Drupal/Core/Ajax/PrependCommand.php new file mode 100644 index 000000000000..5557c31a014c --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/PrependCommand.php @@ -0,0 +1,40 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\PrependCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\InsertCommand; + +/** + * AJAX command for calling the jQuery insert() method. + * + * 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.ajax.prototype.commands.insert() + * defined in misc/ajax.js. + * + * @see http://docs.jquery.com/Manipulation/prepend#content + */ +class PrependCommand extends InsertCommand { + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'insert', + 'method' => 'prepend', + 'selector' => $this->selector, + 'data' => $this->html, + 'settings' => $this->settings, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/RemoveCommand.php b/core/lib/Drupal/Core/Ajax/RemoveCommand.php new file mode 100644 index 000000000000..b77db99f2af4 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/RemoveCommand.php @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\RemoveCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * AJAX command for calling the jQuery remove() method. + * + * 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.ajax.prototype.commands.remove() + * defined in misc/ajax.js. + * + * @see http://docs.jquery.com/Manipulation/remove#expr + */ +class RemoveCommand Implements CommandInterface { + + /** + * The CSS selector for the element(s) to be removed. + * + * @var string + */ + protected $selector; + + /** + * Constructs a RemoveCommand object. + * + * @param string $selector + * + */ + public function __construct($selector) { + $this->selector = $selector; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + return array( + 'command' => 'remove', + 'selector' => $this->selector, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/ReplaceCommand.php b/core/lib/Drupal/Core/Ajax/ReplaceCommand.php new file mode 100644 index 000000000000..01c7667dcbbc --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/ReplaceCommand.php @@ -0,0 +1,40 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\ReplaceCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\InsertCommand; + +/** + * AJAX command for calling the jQuery replace() method. + * + * The 'insert/replaceWith' command instructs the client to use jQuery's + * replaceWith() method to replace each element matched matched by the given + * selector with the given HTML. + * + * This command is implemented by Drupal.ajax.prototype.commands.insert() + * defined in misc/ajax.js. + * + * See @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink + */ +class ReplaceCommand extends InsertCommand { + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'insert', + 'method' => 'replaceWith', + 'selector' => $this->selector, + 'data' => $this->html, + 'settings' => $this->settings, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/RestripeCommand.php b/core/lib/Drupal/Core/Ajax/RestripeCommand.php new file mode 100644 index 000000000000..93c61aad7d6c --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/RestripeCommand.php @@ -0,0 +1,54 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\RestripeCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * AJAX command for resetting the striping on a table. + * + * The 'restripe' command instructs the client to restripe a table. This is + * usually used after a table has been modified by a replace or append command. + * + * This command is implemented by Drupal.ajax.prototype.commands.restripe() + * defined in misc/ajax.js. + */ +class RestripeCommand implements CommandInterface { + + /** + * A CSS selector string. + * + * If the command is a response to a request from an #ajax form element then + * this value can be NULL. + * + * @var string + */ + protected $selector; + + /** + * Constructs a RestripeCommand object. + * + * @param string $selector + * A CSS selector for the table to be restriped. + */ + public function __construct($selector) { + $this->selector = $selector; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'restripe', + 'selector' => $this->selector, + ); + } + +} diff --git a/core/lib/Drupal/Core/Ajax/SettingsCommand.php b/core/lib/Drupal/Core/Ajax/SettingsCommand.php new file mode 100644 index 000000000000..7a00f8ac4ea9 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/SettingsCommand.php @@ -0,0 +1,69 @@ +<?php + +/** + * @file + * Definition of Drupal\Core\Ajax\SettingsCommand. + */ + +namespace Drupal\Core\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * AJAX command for adjusting Drupal's JavaScript settings. + * + * The 'settings' command instructs the client either to use the given array as + * the settings for ajax-loaded content or to extend Drupal.settings with the + * given array, depending on the value of the $merge parameter. + * + * This command is implemented by Drupal.ajax.prototype.commands.settings() + * defined in misc/ajax.js. + */ +class SettingsCommand implements CommandInterface { + + /** + * An array of key/value pairs of JavaScript settings. + * + * This will be utilized for all commands after this if they do not include + * their own settings array. + * + * @var array + */ + protected $settings; + + /** + * Whether the settings should be merged into the global Drupal.settings. + * + * By default (FALSE), the settings that are passed to Drupal.attachBehaviors + * will not include the global Drupal.settings. + * + * @var boolean + */ + protected $merge; + + /** + * Constructs a SettingsCommand object. + * + * @param array $settings + * An array of key/value pairs of JavaScript settings. + * @param boolean $merge + * Whether the settings should be merged into the global Drupal.settings. + */ + public function __construct(array $settings, $merge = FALSE) { + $this->settings = $settings; + $this->merge = $merge; + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return array( + 'command' => 'settings', + 'settings' => $this->settings, + 'merge' => $this->merge, + ); + } + +} diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 6694598593f3..445e7ab69a80 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -10,6 +10,8 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Drupal\file\FileUsage\DatabaseFileUsageBackend; use Drupal\file\FileUsage\FileUsageInterface; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\ReplaceCommand; // Load all Field module hooks for File. require_once DRUPAL_ROOT . '/core/modules/file/file.field.inc'; @@ -750,9 +752,8 @@ function file_ajax_upload() { if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) { // Invalid request. drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error'); - $commands = array(); - $commands[] = ajax_command_replace(NULL, theme('status_messages')); - return array('#type' => 'ajax', '#commands' => $commands); + $response = new AjaxResponse(); + return $response->addCommand(new ReplaceCommand(NULL, theme('status_messages'))); } list($form, $form_state) = ajax_get_form(); @@ -760,9 +761,8 @@ function file_ajax_upload() { if (!$form) { // Invalid form_build_id. drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error'); - $commands = array(); - $commands[] = ajax_command_replace(NULL, theme('status_messages')); - return array('#type' => 'ajax', '#commands' => $commands); + $response = new AjaxResponse(); + return $response->addCommand(new ReplaceCommand(NULL, theme('status_messages'))); } // Get the current element and count the number of files. @@ -793,9 +793,8 @@ function file_ajax_upload() { $js = drupal_add_js(); $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']); - $commands = array(); - $commands[] = ajax_command_replace(NULL, $output, $settings); - return array('#type' => 'ajax', '#commands' => $commands); + $response = new AjaxResponse(); + return $response->addCommand(new ReplaceCommand(NULL, $output, $settings)); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php new file mode 100644 index 000000000000..2e6c421f5972 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php @@ -0,0 +1,309 @@ +<?php + +/** + * @file + * Definition of Drupal\system\Tests\Ajax\AjaxCommandsUnitTest. + */ + +namespace Drupal\system\Tests\Ajax; + +use Drupal\simpletest\UnitTestBase; +use Drupal\Core\Ajax\AddCssCommand; +use Drupal\Core\Ajax\AfterCommand; +use Drupal\Core\Ajax\AlertCommand; +use Drupal\Core\Ajax\AppendCommand; +use Drupal\Core\Ajax\BeforeCommand; +use Drupal\Core\Ajax\ChangedCommand; +use Drupal\Core\Ajax\CssCommand; +use Drupal\Core\Ajax\DataCommand; +use Drupal\Core\Ajax\HtmlCommand; +use Drupal\Core\Ajax\InsertCommand; +use Drupal\Core\Ajax\InvokeCommand; +use Drupal\Core\Ajax\PrependCommand; +use Drupal\Core\Ajax\RemoveCommand; +use Drupal\Core\Ajax\ReplaceCommand; +use Drupal\Core\Ajax\RestripeCommand; +use Drupal\Core\Ajax\SettingsCommand; + +/** + * Tests for all AJAX Commands. + */ +class AjaxCommandsUnitTest extends UnitTestBase { + + public static function getInfo() { + return array( + 'name' => 'Ajax Command Objects', + 'description' => 'Test that each AJAX command object can be created and rendered', + 'group' => 'AJAX', + ); + } + + /** + * Tests that AddCssCommand objects can be constructed and rendered. + */ + function testAddCssCommand() { + + $command = new AddCssCommand('p{ text-decoration:blink; }'); + + $expected = array( + 'command' => 'add_css', + 'data' => 'p{ text-decoration:blink; }', + ); + + $this->assertEqual($command->render(), $expected, 'AddCssCommand::render() returns a proper array.'); + } + + /** + * Tests that AfterCommand objecst can be constructed and rendered. + */ + function testAfterCommand() { + + $command = new AfterCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting')); + + $expected = array( + 'command' => 'insert', + 'method' => 'after', + 'selector' => '#page-title', + 'data' => '<p>New Text!</p>', + 'settings' => array('my-setting' => 'setting'), + ); + + $this->assertEqual($command->render(), $expected, 'AfterCommand::render() returns a proper array.'); + } + + /** + * Tests that AlertCommand objects can be constructed and rendered. + */ + function testAlertCommand() { + $command = new AlertCommand('Set condition 1 throughout the ship!'); + $expected = array( + 'command' => 'alert', + 'text' => 'Set condition 1 throughout the ship!', + ); + + $this->assertEqual($command->render(), $expected, 'AlertCommand::render() returns a proper array.'); + } + + /** + * Tests that AppendCommand objects can be constructed and rendered. + */ + function testAppendCommand() { + // Test AppendCommand. + $command = new AppendCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting')); + + $expected = array( + 'command' => 'insert', + 'method' => 'append', + 'selector' => '#page-title', + 'data' => '<p>New Text!</p>', + 'settings' => array('my-setting' => 'setting'), + ); + + $this->assertEqual($command->render(), $expected, 'AppendCommand::render() returns a proper array.'); + } + + /** + * Tests that BeforeCommand objects can be constructed and rendered. + */ + function testBeforeCommand() { + + $command = new BeforeCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting')); + + $expected = array( + 'command' => 'insert', + 'method' => 'before', + 'selector' => '#page-title', + 'data' => '<p>New Text!</p>', + 'settings' => array('my-setting' => 'setting'), + ); + + $this->assertEqual($command->render(), $expected, 'BeforeCommand::render() returns a proper array.'); + } + + /** + * Tests that ChangedCommand objects can be constructed and rendered. + */ + function testChangedCommand() { + $command = new ChangedCommand('#page-title', '#page-title-changed'); + + $expected = array( + 'command' => 'changed', + 'selector' => '#page-title', + 'asterisk' => '#page-title-changed', + ); + + $this->assertEqual($command->render(), $expected, 'ChangedCommand::render() returns a proper array.'); + } + + /** + * Tests that CssCommand objects can be constructed and rendered. + */ + function testCssCommand() { + + $command = new CssCommand('#page-title', array('text-decoration' => 'blink')); + $command->setProperty('font-size', '40px')->setProperty('font-weight', 'bold'); + + $expected = array( + 'command' => 'css', + 'selector' => '#page-title', + 'argument' => array( + 'text-decoration' => 'blink', + 'font-size' => '40px', + 'font-weight' => 'bold', + ), + ); + + $this->assertEqual($command->render(), $expected, 'CssCommand::render() returns a proper array.'); + } + + /** + * Tests that DataCommand objects can be constructed and rendered. + */ + function testDataCommand() { + + $command = new DataCommand('#page-title', 'my-data', array('key' => 'value')); + + $expected = array( + 'command' => 'data', + 'selector' => '#page-title', + 'name' => 'my-data', + 'value' => array('key' => 'value'), + ); + + $this->assertEqual($command->render(), $expected, 'DataCommand::render() returns a proper array.'); + } + + /** + * Tests that HtmlCommand objects can be constructed and rendered. + */ + function testHtmlCommand() { + + $command = new HtmlCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting')); + + $expected = array( + 'command' => 'insert', + 'method' => 'html', + 'selector' => '#page-title', + 'data' => '<p>New Text!</p>', + 'settings' => array('my-setting' => 'setting'), + ); + + $this->assertEqual($command->render(), $expected, 'HtmlCommand::render() returns a proper array.'); + } + + /** + * Tests that InsertCommand objects can be constructed and rendered. + */ + function testInsertCommand() { + + $command = new InsertCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting')); + + $expected = array( + 'command' => 'insert', + 'method' => NULL, + 'selector' => '#page-title', + 'data' => '<p>New Text!</p>', + 'settings' => array('my-setting' => 'setting'), + ); + + $this->assertEqual($command->render(), $expected, 'InsertCommand::render() returns a proper array.'); + } + + /** + * Tests that InvokeCommand objects can be constructed and rendered. + */ + function testInvokeCommand() { + + $command = new InvokeCommand('#page-title', 'myMethod', array('var1', 'var2')); + + $expected = array( + 'command' => 'invoke', + 'selector' => '#page-title', + 'method' => 'myMethod', + 'args' => array('var1', 'var2'), + ); + + $this->assertEqual($command->render(), $expected, 'InvokeCommand::render() returns a proper array.'); + } + + /** + * Tests that PrependCommand objects can be constructed and rendered. + */ + function testPrependCommand() { + + $command = new PrependCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting')); + + $expected = array( + 'command' => 'insert', + 'method' => 'prepend', + 'selector' => '#page-title', + 'data' => '<p>New Text!</p>', + 'settings' => array('my-setting' => 'setting'), + ); + + $this->assertEqual($command->render(), $expected, 'PrependCommand::render() returns a proper array.'); + } + + /** + * Tests that RemoveCommand objects can be constructed and rendered. + */ + function testRemoveCommand() { + + $command = new RemoveCommand('#page-title'); + + $expected = array( + 'command' => 'remove', + 'selector' => '#page-title', + ); + + $this->assertEqual($command->render(), $expected, 'RemoveCommand::render() returns a proper array.'); + } + + /** + * Tests that ReplaceCommand objects can be constructed and rendered. + */ + function testReplaceCommand() { + $command = new ReplaceCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting')); + + $expected = array( + 'command' => 'insert', + 'method' => 'replaceWith', + 'selector' => '#page-title', + 'data' => '<p>New Text!</p>', + 'settings' => array('my-setting' => 'setting'), + ); + + $this->assertEqual($command->render(), $expected, 'ReplaceCommand::render() returns a proper array.'); + } + + /** + * Tests that RestripeCommand objects can be constructed and rendered. + */ + function testRestripeCommand() { + $command = new RestripeCommand('#page-title'); + + $expected = array( + 'command' => 'restripe', + 'selector' => '#page-title', + ); + + $this->assertEqual($command->render(), $expected, 'RestripeCommand::render() returns a proper array.'); + } + + /** + * Tests that SettingsCommand objects can be constructed and rendered. + */ + function testSettingsCommand() { + $command = new SettingsCommand(array('key' => 'value'), TRUE); + + $expected = array( + 'command' => 'settings', + 'settings' => array('key' => 'value'), + 'merge' => TRUE, + ); + + $this->assertEqual($command->render(), $expected, 'SettingsCommand::render() returns a proper array.'); + } + +} + -- GitLab