Commit f37b8675 authored by webchick's avatar webchick

Issue #1870764 by quicksketch, dawehner, frega, larowlan, nod_: Add an ajax...

Issue #1870764 by quicksketch, dawehner, frega, larowlan, nod_: Add an ajax command which makes it easy to use the dialog API in complex cases.
parent ce27c589
......@@ -473,13 +473,8 @@ function ajax_prepare_response($page_callback_result) {
// manipulation method is used. The method used is specified by
// #ajax['method']. The default method is 'replaceWith', which completely
// replaces the old wrapper element and its content with the new HTML.
// Since this is the primary response content returned to the client, we
// also attach the page title. It is up to client code to determine if and
// how to display that. For example, if the requesting element is configured
// to display the response in a dialog (via #ajax['dialog']), it can use
// this for the dialog title.
$html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result);
$commands[] = ajax_command_insert(NULL, $html) + array('title' => drupal_get_title());
$commands[] = ajax_command_insert(NULL, $html);
// Add the status messages inside the new content's wrapper element, so that
// on subsequent Ajax requests, it is treated as old content.
$commands[] = ajax_command_prepend(NULL, theme('status_messages'));
......@@ -594,9 +589,6 @@ function ajax_pre_render_element($element) {
if (isset($element['#ajax']['event'])) {
$element['#attached']['library'][] = array('system', 'jquery.form');
$element['#attached']['library'][] = array('system', 'drupal.ajax');
if (!empty($element['#ajax']['dialog'])) {
$element['#attached']['library'][] = array('system', 'drupal.dialog');
}
$settings = $element['#ajax'];
......
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\CloseDialogCommand.
*/
namespace Drupal\Core\Ajax;
/**
* Defines an AJAX command that closes the current active dialog.
*/
class CloseDialogCommand implements CommandInterface {
/**
* A CSS selector string of the dialog to close.
*
* @var string
*/
protected $selector;
/**
* Constructs a CloseDialogCommand object.
*
* @param string $selector
* A CSS selector string of the dialog to close.
*/
public function __construct($selector = NULL) {
$this->selector = $selector ? $selector : '#drupal-modal';
}
/**
* Implements \Drupal\Core\Ajax\CommandInterface::render().
*/
public function render() {
return array(
'command' => 'closeDialog',
'selector' => $this->selector,
);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\CloseModalDialogCommand.
*/
namespace Drupal\Core\Ajax;
use Drupal\Core\Ajax\CloseDialogCommand;
/**
* Defines an AJAX command that closes the currently visible modal dialog.
*/
class CloseModalDialogCommand extends CloseDialogCommand {
/**
* Constructs a CloseModalDialogCommand object.
*/
public function __construct() {
$this->selector = '#drupal-modal';
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\OpenDialogCommand.
*/
namespace Drupal\Core\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* Defines an AJAX command to open certain content in a dialog.
*/
class OpenDialogCommand implements CommandInterface {
/**
* The selector of the dialog.
*
* @var string
*/
protected $selector;
/**
* The title of the dialog.
*
* @var string
*/
protected $title;
/**
* HTML content that will placed in the dialog.
*
* @var string
*/
protected $html;
/**
* Stores dialog-specific options passed directly to jQuery UI dialogs. Any
* jQuery UI option can be used. See http://api.jqueryui.com/dialog.
*
* @var array
*/
protected $dialogOptions;
/**
* Custom settings that will be passed to the Drupal behaviors on the content
* of the dialog.
*
* @var array
*/
protected $settings;
/**
* Constructs an OpenDialogCommand object.
*
* @param string $selector
* The selector of the dialog.
* @param string $title
* The title of the dialog.
* @param string $html
* HTML that will be placed in the dialog.
* @param array $dialog_options
* (optional) Options to be passed to the dialog implementation. Any
* jQuery UI option can be used. See http://api.jqueryui.com/dialog.
* @param array|null $settings
* (optional) Custom settings that will be passed to the Drupal behaviors
* on the content of the dialog. If left empty, the settings will be
* populated automatically from the current request.
*/
public function __construct($selector, $title, $html, array $dialog_options = array(), $settings = NULL) {
$dialog_options += array('title' => $title);
$this->selector = $selector;
$this->html = $html;
$this->dialogOptions = $dialog_options;
$this->settings = $settings;
}
/**
* Returns the dialog options.
*
* @return array
*/
public function getDialogOptions() {
return $this->dialogOptions;
}
/**
* Sets the dialog options array.
*
* @param array $dialog_options
* Options to be passed to the dialog implementation. Any jQuery UI option
* can be used. See http://api.jqueryui.com/dialog.
*/
public function setDialogOptions($dialog_options) {
$this->dialogOptions = $dialog_options;
}
/**
* Sets a single dialog option value.
*
* @param string $key
* Key of the dialog option. Any jQuery UI option can be used.
* See http://api.jqueryui.com/dialog.
* @param mixed $value
* Option to be passed to the dialog implementation.
*/
public function setDialogOption($key, $value) {
$this->dialogOptions[$key] = $value;
}
/**
* Sets the dialog title (an alias of setDialogOptions).
*
* @param string $title
* The new title of the dialog.
*/
public function setDialogTitle($title) {
$this->setDialogOptions('title', $title);
}
/**
* Implements \Drupal\Core\Ajax\CommandInterface:render().
*/
public function render() {
// Add the library for handling the dialog in the response.
drupal_add_library('system', 'drupal.dialog.ajax');
// For consistency ensure the modal option is set to TRUE or FALSE.
$this->dialogOptions['modal'] = isset($this->dialogOptions['modal']) && $this->dialogOptions['modal'];
return array(
'command' => 'openDialog',
'selector' => $this->selector,
'settings' => $this->settings,
'data' => $this->html,
'dialogOptions' => $this->dialogOptions,
);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\OpenModalDialogCommand.
*/
namespace Drupal\Core\Ajax;
use Drupal\Core\Ajax\OpenDialogCommand;
/**
* Defines an AJAX command to open certain content in a dialog in a modal dialog.
*/
class OpenModalDialogCommand extends OpenDialogCommand {
/**
* Constructs an OpenModalDialog object.
*
* The modal dialog differs from the normal modal provided by
* OpenDialogCommand in that a modal prevents other interactions on the page
* until the modal has been completed. Drupal provides a built-in modal for
* this purpose, so no selector needs to be provided.
*
* @param string $title
* The title of the dialog.
* @param string $html
* HTML that will be placed in the dialog.
* @param array $dialog_options
* (optional) Settings to be passed to the dialog implementation. Any
* jQuery UI option can be used. See http://api.jqueryui.com/dialog.
* @param array|null $settings
* (optional) Custom settings that will be passed to the Drupal behaviors
* on the content of the dialog. If left empty, the settings will be
* populated automatically from the current request.
*/
public function __construct($title, $html, array $dialog_options = array(), $settings = NULL) {
$dialog_options['modal'] = TRUE;
parent::__construct('#drupal-modal', $title, $html, $dialog_options, $settings);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\RedirectCommand.
*/
namespace Drupal\Core\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* Defines an AJAX command to set the window.location, loading that URL.
*/
class RedirectCommand implements CommandInterface {
/**
* The URL that will be loaded into window.location.
*
* @var string
*/
protected $url;
/**
* Constructs an RedirectCommand object.
*
* @param string $url
* The URL that will be loaded into window.location. This should be a full
* URL, one that has already been run through the url() function.
*/
public function __construct($url) {
$this->url = $url;
}
/**
* Implements \Drupal\Core\Ajax\CommandInterface:render().
*/
public function render() {
return array(
'command' => 'redirect',
'url' => $this->url,
);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\SetDialogOptionCommand.
*/
namespace Drupal\Core\Ajax;
/**
* Defines an AJAX command that sets jQuery UI dialog properties.
*/
class SetDialogOptionCommand implements CommandInterface {
/**
* A CSS selector string.
*
* @var string
*/
protected $selector;
/**
* A jQuery UI dialog option name.
*
* @var string
*/
protected $optionName;
/**
* A jQuery UI dialog option value.
*
* @var mixed
*/
protected $optionValue;
/**
* Constructs a SetDialogOptionCommand object.
*
* @param string $selector
* The selector of the dialog whose title will be set. If set to an empty
* value, the default modal dialog will be selected.
* @param string $option_name
* The name of the option to set. May be any jQuery UI dialog option.
* See http://api.jqueryui.com/dialog.
* @param mixed $option_value
* The value of the option to be passed to the dialog.
*/
public function __construct($selector, $option_name, $option_value) {
$this->selector = $selector ? $selector : '#drupal-modal';
$this->optionName = $option_name;
$this->optionValue = $option_value;
}
/**
* Implements \Drupal\Core\Ajax\CommandInterface::render().
*/
public function render() {
return array(
'command' => 'setDialogOption',
'selector' => $this->selector,
'optionName' => $this->optionName,
'optionValue' => $this->optionValue,
);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\SetDialogTitleCommand.
*/
namespace Drupal\Core\Ajax;
use Drupal\Core\Ajax\SetDialogOptionCommand;
/**
* Defines an AJAX command that sets jQuery UI dialog properties.
*/
class SetDialogTitleCommand extends SetDialogOptionCommand {
/**
* Constructs a SetDialogTitleCommand object.
*
* @param string $selector
* The selector of the dialog whose title will be set. If set to an empty
* value, the default modal dialog will be selected.
* @param string $title
* The title that will be set on the dialog.
*/
public function __construct($selector, $title) {
$this->selector = $selector ? $selector : '#drupal-modal';
$this->optionName = 'title';
$this->optionValue = $title;
}
}
......@@ -126,15 +126,6 @@ Drupal.ajax = function (base, element, element_settings) {
this.wrapper = '#' + this.wrapper;
}
// For Ajax responses that are wanted in a dialog, use the needed method.
// If wanted in a modal dialog, also use the needed wrapper.
if (this.dialog) {
this.method = 'html';
if (this.dialog.modal) {
this.wrapper = '#drupal-modal';
}
}
this.element = element;
this.element_settings = element_settings;
......@@ -561,19 +552,6 @@ Drupal.ajax.prototype.commands = {
// Add the new content to the page.
wrapper[method](new_content);
// If the requesting object wanted the response in a dialog, open that
// dialog. However, a single server response can include multiple insert
// commands (e.g., one for the primary content and another one for status
// messages), but we only want to open the dialog once, so we assume that
// only commands with a title property are dialog eligible.
// @todo Consider whether this is overloading title inappropriately, and
// if so, find another way to determine dialog eligibility.
if (ajax.dialog && ('title' in response)) {
var dialogOptions = $.extend({title: response.title}, ajax.dialog);
var dialog = Drupal.dialog(wrapper, dialogOptions);
ajax.dialog.modal ? dialog.showModal() : dialog.show();
}
// Immediately hide the new content if we're using any effects.
if (effect.showEffect !== 'show') {
new_content.hide();
......@@ -628,6 +606,13 @@ Drupal.ajax.prototype.commands = {
window.alert(response.text, response.title);
},
/**
* Command to set the window.location, redirecting the browser.
*/
redirect: function (ajax, response, status) {
window.location = response.url;
},
/**
* Command to provide the jQuery css() function.
*/
......
/**
* @file
* Extends the Drupal AJAX functionality to integrate the dialog API.
*/
(function ($, Drupal) {
"use strict";
Drupal.behaviors.dialog = {
attach: function () {
// Provide a known 'drupal-modal' DOM element for Drupal-based modal
// dialogs. Non-modal dialogs are responsible for creating their own
// elements, since there can be multiple non-modal dialogs at a time.
if (!$('#drupal-modal').length) {
$('<div id="drupal-modal" />').hide().appendTo('body');
}
}
};
/**
* Command to open a dialog.
*/
Drupal.ajax.prototype.commands.openDialog = function (ajax, response, status) {
if (!response.selector) {
return false;
}
var $dialog = $(response.selector);
if (!$dialog.length) {
// Create the element if needed.
$dialog = $('<div id="' + response.selector.replace(/^#/, '') + '"/>').appendTo('body');
}
// Set up the wrapper, if there isn't one.
if (!ajax.wrapper) {
ajax.wrapper = $dialog.attr('id');
}
// Use the ajax.js insert command to populate the dialog contents.
response.command = 'insert';
response.method = 'html';
ajax.commands.insert(ajax, response, status);
// Open the dialog itself.
response.dialogOptions = response.dialogOptions || {};
var dialog = Drupal.dialog($dialog, response.dialogOptions);
if (response.dialogOptions.modal) {
dialog.showModal();
}
else {
dialog.show();
}
};
/**
* Command to close a dialog.
*
* If no selector is given, it defaults to trying to close the modal.
*/
Drupal.ajax.prototype.commands.closeDialog = function (ajax, response, status) {
var $dialog = $(response.selector);
if ($dialog.length) {
Drupal.dialog($dialog).close();
}
};
/**
* Command to set a dialog property.
*
* jQuery UI specific way of setting dialog options.
*/
Drupal.ajax.prototype.commands.setDialogOption = function (ajax, response, status) {
var $dialog = $(response.selector);
if ($dialog.length) {
$dialog.dialog('option', response.optionName, response.optionValue);
}
};
/**
* Binds a listener on dialog creation to handle the cancel link.
*/
$(window).on('dialog:aftercreate', function (e, dialog, $element, settings) {
$element.on('click.dialog', '.dialog-cancel', function (e) {
dialog.close('cancel');
e.preventDefault();
e.stopPropagation();
});
});
/**
* Removes all 'dialog' listeners.
*/
$(window).on('dialog:beforeclose', function (e, dialog, $element) {
$element.off('.dialog');
});
})(jQuery, Drupal);
......@@ -16,17 +16,6 @@ drupalSettings.dialog = {
}
};
Drupal.behaviors.dialog = {
attach: function () {
// Provide a known 'drupal-modal' dom element for Drupal code to use for
// modal dialogs. Since there can be multiple non-modal dialogs at a time,
// it is the responsibility of calling code to create the elements it needs.
if (!$('#drupal-modal').length) {
$('<div id="drupal-modal" />').hide().appendTo('body');
}
}
};
Drupal.dialog = function (element, options) {
function openDialog (settings) {
......@@ -63,22 +52,4 @@ Drupal.dialog = function (element, options) {
return dialog;
};
/**
* Binds a listener on dialog creation to handle the cancel link.
*/
$(window).on('dialog:aftercreate', function (e, dialog, $element, settings) {
$element.on('click.dialog', '.dialog-cancel', function (e) {
dialog.close('cancel');
e.preventDefault();
e.stopPropagation();
});
});
/**
* Removes all 'dialog' listeners.
*/
$(window).on('dialog:beforeclose', function (e, dialog, $element) {
$element.off('.dialog');
});
})(jQuery, Drupal, drupalSettings);
......@@ -6,6 +6,8 @@
*/
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
/**
* Helper function to construct the storage changes in a configuration synchronization form.
......@@ -37,6 +39,9 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa
return $form;
}
// Add the AJAX library to the form for dialog support.
$form['#attached']['library'][] = array('system', 'drupal.ajax');
foreach ($config_changes as $config_change_type => $config_files) {
if (empty($config_files)) {
continue;
......@@ -70,7 +75,9 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa
$links['view_diff'] = array(
'title' => t('View differences'),
'href' => 'admin/config/development/sync/diff/' . $config_file,
'ajax' => array('dialog' => array('modal' =>TRUE, 'width' => '700px')),
'attributes' => array(
'class' => array('use-ajax'),
),
);
$form[$config_change_type]['list']['#rows'][] = array(
'name' => $config_file,
......@@ -149,12 +156,6 @@ function config_admin_diff_page($config_file) {
// Add the CSS for the inline diff.
$output['#attached']['css'][] = drupal_get_path('module', 'system') . '/system.diff.css';
$output['title'] = array(
'#theme' => 'html_tag',
'#tag' => 'h3',
'#value' => t('View changes of @config_file', array('@config_file' => $config_file)),
);
$diff = config_diff($target_storage, $source_storage, $config_file);
$formatter = new DrupalDiffFormatter();
$formatter->show_header = FALSE;
......@@ -175,10 +176,31 @@ function config_admin_diff_page($config_file) {
'#type' => 'link',
'#title' => "Back to 'Synchronize configuration' page.",
'#href' => 'admin/config/development/sync',
'#attributes' => array(
'class' => array('dialog-cancel'),
),
);
$title = t('View changes of @config_file', array('@config_file' => $config_file));
// Return AJAX requests as a dialog.
// @todo: Set up separate content callbacks for the non-JS and dialog versions
// of this page using the router system. See http://drupal.org/node/1944472.
if (Drupal::service('request')->isXmlHttpRequest()) {
// Add class to the close link.
$output['back']['#attributes']['class'][] = 'dialog-cancel';
$dialog_content = drupal_render($output);
$response = new AjaxResponse();
$response->addCommand(new OpenModalDialogCommand($title, $dialog_content, array('width' => '700')));
return $response;
}
// Otherwise show the page title as an element.
else {
$output['title'] = array(
'#theme' => 'html_tag',
'#tag' => 'h3',
'#value' => $title,
'#weight' => -10,
);
}
return $output;
}
......@@ -7,7 +7,7 @@
namespace Drupal\system\Tests\Ajax;
use Drupal\simpletest\UnitTestBase;
use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\Core\Ajax\AddCssCommand;
use Drupal\Core\Ajax\AfterCommand;
use Drupal\Core\Ajax\AlertCommand;
......@@ -24,11 +24,18 @@
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Ajax\RestripeCommand;
use Drupal\Core\Ajax\SettingsCommand;
use Drupal\Core\Ajax\OpenDialogCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Drupal\Core\Ajax\SetDialogOptionCommand;
use Drupal\Core\Ajax\SetDialogTitleCommand;
use Drupal\Core\Ajax\RedirectCommand;
/**
* Tests for all AJAX Commands.
*/
class AjaxCommandsUnitTest extends UnitTestBase {
class AjaxCommandsUnitTest extends DrupalUnitTestBase {
public static function getInfo() {
return array(
......@@ -305,5 +312,121 @@ function testSettingsCommand() {
$this->assertEqual($command->render(), $expected, 'SettingsCommand::render() returns a proper array.');
}
}
/**
* Tests that OpenDialogCommand objects can be constructed and rendered.
*/
function testOpenDialogCommand() {
$command = new OpenDialogCommand('#some-dialog', 'Title', '<p>Text!</p>', array(
'url' => FALSE,
'width' => 500,
));
$expected = array(
'command' => 'openDialog',
'selector' => '#some-dialog',
'settings' => NULL,
'data' => '<p>Text!</p>',
'dialogOptions' => array(
'url' => FALSE,
'width' => 500,
'title' => 'Title',
'modal' => FALSE,
),