diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
index a40b56a..7f4d393 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
@@ -2,13 +2,17 @@
namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
+use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -54,6 +58,13 @@ class EntityReferenceEntityFormatter extends EntityReferenceFormatterBase implem
protected $entityDisplayRepository;
/**
+ * The entity type bundle info.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+ */
+ protected $entityTypeBundleInfo;
+
+ /**
* An array of counters for the recursive rendering protection.
*
* Each counter takes into account all the relevant information about the
@@ -88,12 +99,15 @@ class EntityReferenceEntityFormatter extends EntityReferenceFormatterBase implem
* The entity type manager.
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
* The entity display repository.
+ * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
+ * The entity type bundle info.
*/
- public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, LoggerChannelFactoryInterface $logger_factory, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository) {
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, LoggerChannelFactoryInterface $logger_factory, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
$this->loggerFactory = $logger_factory;
$this->entityTypeManager = $entity_type_manager;
$this->entityDisplayRepository = $entity_display_repository;
+ $this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
@@ -110,7 +124,8 @@ public static function create(ContainerInterface $container, array $configuratio
$configuration['third_party_settings'],
$container->get('logger.factory'),
$container->get('entity_type.manager'),
- $container->get('entity_display.repository')
+ $container->get('entity_display.repository'),
+ $container->get('entity_type.bundle.info')
);
}
@@ -125,15 +140,158 @@ public static function defaultSettings() {
}
/**
+ * Wrapper around ::getSetting() to reflect current values from Form State.
+ *
+ * @param string $key
+ * The setting name.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * (optional) The form state object. If absent, this method is equivalent to
+ * parent::getSetting().
+ *
+ * @return mixed|null
+ * The value of the setting, or NULL if absent.
+ */
+ public function getSetting($key, FormStateInterface $form_state = NULL) {
+ if ($form_state) {
+ $field_name = $this->fieldDefinition->getName();
+ $form_state_key = [
+ 'fields',
+ $field_name,
+ 'settings_edit_form',
+ 'settings',
+ $key,
+ ];
+ if ($form_state->hasValue($form_state_key)) {
+ return $form_state->getValue($form_state_key);
+ }
+ }
+
+ return parent::getSetting($key);
+ }
+
+ /**
+ * Ajax callback for view mode selection change.
+ */
+ public static function onViewModeChange(array &$form, FormStateInterface $form_state) {
+ return $form['fields'][$form_state->get('plugin_settings_edit')]['plugin']['settings_edit_form']['settings'];
+ }
+
+ /**
+ * Rebuilds the form on select submit.
+ */
+ public static function rebuildOnSubmit(array &$form, FormStateInterface $form_state) {
+ $form_state->setRebuild(TRUE);
+ }
+
+ /**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
+ $entity_type_id = $this->getFieldSetting('target_type');
+ $entity_type_label = $this->entityTypeManager->getDefinition($entity_type_id)->getLabel();
+ $bundle_entity_type_id = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType();
+ $target_bundles_setting = $this->getFieldSetting('handler_settings')['target_bundles'];
+ if ($target_bundles_setting === NULL) {
+ // If no bundle is configured in the field settings, all are selectable.
+ $bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
+ }
+ else {
+ $bundles = array_keys($target_bundles_setting);
+ }
+
+ $currently_selected_view_mode_name = $this->getSetting('view_mode', $form_state);
+ $currently_selected_view_mode = EntityViewMode::load("$entity_type_id.$currently_selected_view_mode_name");
+ // @todo Simplify this in https://www.drupal.org/node/2844203.
+ $currently_selected_view_mode_label = $currently_selected_view_mode ? $currently_selected_view_mode->label() : $this->t('Default');
+
+ $elements['#prefix'] = '
';
+ $elements['#suffix'] = '
';
+ $description = [
+ '#theme' => 'item_list',
+ '#context' => ['list_style' => 'comma-list'],
+ '#items' => [],
+ ];
+ // If targeting entities of the same type this field is attached to, skip
+ // the viewmode for the bundle we are currently editing, once it wouldn't
+ // make sense to open in the modal the same form we have on the main page.
+ if (count($bundles) === 1 && !($entity_type_id === $this->fieldDefinition->getTargetEntityTypeId()
+ && $bundles[0] === $form_state->getFormObject()->getEntity()->getTargetBundle()
+ && $currently_selected_view_mode_name === $this->viewMode)) {
+ $description['#items'][] = [
+ '#type' => 'link',
+ '#title' => $this->t('Configure %view-mode-label view mode', ['%view-mode-label' => $currently_selected_view_mode_label]),
+ '#url' => $currently_selected_view_mode_name === 'default'
+ ? Url::fromRoute(sprintf('entity.entity_view_display.%s.default', $entity_type_id), [$bundle_entity_type_id => $bundles[0]])
+ : Url::fromRoute(sprintf('entity.entity_view_display.%s.view_mode', $entity_type_id), [$bundle_entity_type_id => $bundles[0], 'view_mode_name' => $currently_selected_view_mode_name]),
+ '#attributes' => [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => json_encode([
+ 'width' => 800,
+ ]),
+ ],
+ ];
+ }
+ else {
+ $bundle_info = entity_get_bundles($entity_type_id);
+ for ($i = 0; $i < count($bundles); $i++) {
+ $skip_bundle = $entity_type_id === $this->fieldDefinition->getTargetEntityTypeId()
+ && $bundles[$i] === $form_state->getFormObject()->getEntity()->getTargetBundle()
+ && $currently_selected_view_mode_name === $this->viewMode;
+ if ($skip_bundle) {
+ continue;
+ }
+ $description['#items'][] = [
+ '#type' => 'link',
+ '#title' => $this->t('%bundle-label %entity-type-label', ['%bundle-label' => $bundle_info[$bundles[$i]]['label'], '%entity-type-label' => $entity_type_label]),
+ '#url' => $currently_selected_view_mode_name === 'default'
+ ? Url::fromRoute(sprintf('entity.entity_view_display.%s.default', $entity_type_id), [$bundle_entity_type_id => $bundles[$i]])
+ : Url::fromRoute(sprintf('entity.entity_view_display.%s.view_mode', $entity_type_id), [$bundle_entity_type_id => $bundles[$i], 'view_mode_name' => $currently_selected_view_mode_name]),
+ '#attributes' => [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => json_encode([
+ 'width' => 800,
+ ]),
+ ],
+ ];
+ }
+ if (!empty($description['#items'][0])) {
+ $description['#items'][0]['#prefix'] = $this->t('Configure %view-mode-label view mode for', [
+ '%view-mode-label' => $currently_selected_view_mode_label,
+ ]) . ' ';
+ }
+ }
+
+ $description['#items'][] = [
+ '#type' => 'link',
+ '#title' => !empty($description['#items']) ? $this->t('add new view mode') : $this->t('Add new view mode'),
+ '#prefix' => !empty($description['#items']) ? Markup::create(t('or') . ' ') : '',
+ '#url' => Url::fromRoute('entity.entity_view_mode.add_form', ['entity_type_id' => $entity_type_id]),
+ '#attributes' => [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => json_encode([
+ 'width' => 800,
+ ]),
+ ],
+ ];
$elements['view_mode'] = [
'#type' => 'select',
- '#options' => $this->entityDisplayRepository->getViewModeOptions($this->getFieldSetting('target_type')),
+ '#options' => $this->entityDisplayRepository->getViewModeOptions($entity_type_id),
'#title' => t('View mode'),
- '#default_value' => $this->getSetting('view_mode'),
+ '#default_value' => $currently_selected_view_mode_name,
+ '#ajax' => [
+ 'callback' => [static::class, 'onViewModeChange'],
+ 'wrapper' => 'entity-reference-entity-formatter-settings-ajax',
+ 'method' => 'replace',
+ ],
+ '#submit' => [[static::class, 'rebuildOnSubmit']],
+ '#executes_submit_callback' => TRUE,
'#required' => TRUE,
+ '#description' => $description + [
+ '#access' => \Drupal::currentUser()->hasPermission('administer ' . $entity_type_id . ' display'),
+ ],
];
return $elements;
diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js
index 095154a..1bfcb80 100644
--- a/core/misc/ajax.es6.js
+++ b/core/misc/ajax.es6.js
@@ -380,6 +380,12 @@
* @type {string}
*/
this.wrapper = `#${this.wrapper}`;
+ // To avoid problems when the same form exists multiple times on the same page, allow its
+ // wrapper to be scoped to the form it lives in. This can f.e. happen when the same base form
+ // is both in the parent page and in a modal.
+ if (this.scope && this.scope === 'form') {
+ this.wrapper = `#${element.form.id} ${this.wrapper}`;
+ }
}
/**
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 8ec175f..b806378 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -168,6 +168,10 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
if (this.wrapper) {
this.wrapper = '#' + this.wrapper;
+
+ if (this.scope && this.scope === 'form') {
+ this.wrapper = '#' + element.form.id + ' ' + this.wrapper;
+ }
}
this.element = element;
diff --git a/core/modules/field_ui/src/Controller/EntityDisplayModeController.php b/core/modules/field_ui/src/Controller/EntityDisplayModeController.php
index b4a608a..763a025 100644
--- a/core/modules/field_ui/src/Controller/EntityDisplayModeController.php
+++ b/core/modules/field_ui/src/Controller/EntityDisplayModeController.php
@@ -3,6 +3,7 @@
namespace Drupal\field_ui\Controller;
use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\Core\Url;
/**
@@ -10,6 +11,26 @@
*/
class EntityDisplayModeController extends ControllerBase {
+ public function editViewDisplayTitle($entity_type_id, $bundle, $view_mode_name) {
+ $entity_type = $this->entityTypeManager()->getDefinition($entity_type_id);
+ if ($entity_type->hasKey('bundle')) {
+ return $this->t('Edit %view-mode-label display of %bundle-label %entity-type-label', [
+ '%view-mode-label' => $view_mode_name === 'default'
+ ? t('Default')
+ : EntityViewMode::load("$entity_type_id.$view_mode_name")->label(),
+ '%bundle-label' => $this->entityManager()->getBundleInfo($entity_type_id)[$bundle]['label'],
+ '%entity-type-label' => $this->entityTypeManager()->getDefinition($entity_type_id)->getPluralLabel(),
+ ]);
+ }
+ else {
+ return $this->t('Edit %view-mode-label display of %entity-type-label', [
+ '%view-mode-label' => $view_mode_name === 'default'
+ ? t('Default')
+ : EntityViewMode::load("$entity_type_id.$view_mode_name")->label(),
+ '%entity-type-label' => $this->entityTypeManager()->getDefinition($entity_type_id)->getPluralLabel(),
+ ]);
+ }
+ }
/**
* Provides a list of eligible entity types for adding view modes.
*
diff --git a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
index 06eed1f..f524687 100644
--- a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
+++ b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
@@ -4,6 +4,9 @@
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
@@ -235,6 +238,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#submit' => ['::multistepSubmit'],
'#ajax' => [
'callback' => '::multistepAjax',
+ 'scope' => 'form',
'wrapper' => 'field-display-overview-wrapper',
'effect' => 'fade',
// The button stays hidden, so we hide the Ajax spinner too. Ad-hoc
@@ -244,19 +248,47 @@ public function form(array $form, FormStateInterface $form_state) {
'#attributes' => ['class' => ['visually-hidden']]
];
- $form['actions'] = ['#type' => 'actions'];
- $form['actions']['submit'] = [
- '#type' => 'submit',
- '#button_type' => 'primary',
- '#value' => $this->t('Save'),
- ];
-
$form['#attached']['library'][] = 'field_ui/drupal.field_ui';
return $form;
}
/**
+ * {@inheritdoc}
+ */
+ protected function actions(array $form, FormStateInterface $form_state) {
+ $actions = parent::actions($form, $form_state);
+ $actions['submit']['#ajax'] = [
+ 'callback' => '::ajaxCallback',
+ 'event' => 'click',
+ ];
+ return $actions;
+ }
+
+ /**
+ * Ajax callback to close the modal and update the selected text.
+ *
+ * @return \Drupal\Core\Ajax\AjaxResponse
+ * An ajax response object.
+ */
+ public function ajaxCallback(array $form, FormStateInterface $form_state) {
+ $response = new AjaxResponse();
+ if ($form_state->getErrors()) {
+ unset($form['#prefix'], $form['#suffix']);
+ $form['status_messages'] = [
+ '#type' => 'status_messages',
+ '#weight' => -10,
+ ];
+ $response->addCommand(new HtmlCommand('#drupal-modal', $form));
+ }
+ else {
+ // @todo show message for successful saving of settings once https://www.drupal.org/node/77245 lands. This message is set by \Drupal\field_ui\Form\EntityDisplayFormBase::submitForm().
+ $response->addCommand(new CloseModalDialogCommand());
+ }
+ return $response;
+ }
+
+ /**
* Builds the table row structure for a single field.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
@@ -347,6 +379,7 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, arr
'#submit' => ['::multistepSubmit'],
'#ajax' => [
'callback' => '::multistepAjax',
+ 'scope' => 'form',
'wrapper' => 'field-display-overview-wrapper',
'effect' => 'fade',
],
@@ -375,7 +408,6 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, arr
'settings' => $settings_form,
'third_party_settings' => $third_party_settings_form,
'actions' => [
- '#type' => 'actions',
'save_settings' => $base_button + [
'#type' => 'submit',
'#button_type' => 'primary',
diff --git a/core/modules/field_ui/src/Form/EntityDisplayModeFormBase.php b/core/modules/field_ui/src/Form/EntityDisplayModeFormBase.php
index 72a8a19..ae5f381 100644
--- a/core/modules/field_ui/src/Form/EntityDisplayModeFormBase.php
+++ b/core/modules/field_ui/src/Form/EntityDisplayModeFormBase.php
@@ -2,6 +2,9 @@
namespace Drupal\field_ui\Form;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
@@ -84,4 +87,41 @@ public function save(array $form, FormStateInterface $form_state) {
$form_state->setRedirectUrl($this->entity->urlInfo('collection'));
}
+ /**
+ * {@inheritdoc}
+ */
+ public function actions(array $form, FormStateInterface $form_state) {
+ $actions = parent::actions($form, $form_state);
+ $actions['submit']['#ajax'] = [
+ 'callback' => '::ajaxCallback',
+ 'event' => 'click',
+ ];
+ return $actions;
+ }
+
+ /**
+ * Ajax callback to close the modal and update the selected text.
+ *
+ * @return \Drupal\Core\Ajax\AjaxResponse
+ * An ajax response object.
+ */
+ public function ajaxCallback(array $form, FormStateInterface $form_state) {
+ $response = new AjaxResponse();
+ if ($form_state->getErrors()) {
+ unset($form['#prefix'], $form['#suffix']);
+ $form['status_messages'] = [
+ '#type' => 'status_messages',
+ '#weight' => -10,
+ ];
+ $response->addCommand(new HtmlCommand('#drupal-modal', $form));
+ }
+ else {
+ // @TODO Show message for successful saving of settings once
+ // https://www.drupal.org/node/77245 lands. This message is set by
+ // \Drupal\field_ui\Form\EntityDisplayFormBase::submitForm().
+ $response->addCommand(new CloseModalDialogCommand());
+ }
+ return $response;
+ }
+
}
diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php
index b08e7f8..495f14a 100644
--- a/core/modules/field_ui/src/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php
@@ -5,6 +5,7 @@
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
+use Drupal\field_ui\Controller\EntityDisplayModeController;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
@@ -136,7 +137,7 @@ protected function alterRoutes(RouteCollection $collection) {
"$path/display",
[
'_entity_form' => 'entity_view_display.edit',
- '_title' => 'Manage display',
+ '_title_callback' => EntityDisplayModeController::class . '::editViewDisplayTitle',
'view_mode_name' => 'default',
] + $defaults,
['_field_ui_view_mode_access' => 'administer ' . $entity_type_id . ' display'],
@@ -148,7 +149,7 @@ protected function alterRoutes(RouteCollection $collection) {
"$path/display/{view_mode_name}",
[
'_entity_form' => 'entity_view_display.edit',
- '_title' => 'Manage display',
+ '_title_callback' => EntityDisplayModeController::class . '::editViewDisplayTitle',
] + $defaults,
['_field_ui_view_mode_access' => 'administer ' . $entity_type_id . ' display'],
$options
diff --git a/core/modules/field_ui/tests/src/Functional/FieldUIRouteTest.php b/core/modules/field_ui/tests/src/Functional/FieldUIRouteTest.php
index fe7a270..1b27d27 100644
--- a/core/modules/field_ui/tests/src/Functional/FieldUIRouteTest.php
+++ b/core/modules/field_ui/tests/src/Functional/FieldUIRouteTest.php
@@ -46,13 +46,13 @@ public function testFieldUIRoutes() {
$this->assertResponse(403);
$this->drupalGet('admin/config/people/accounts/display');
- $this->assertTitle('Manage display | Drupal');
+ $this->assertTitle('Edit Default display of user entities | Drupal');
$this->assertLocalTasks();
$edit = ['display_modes_custom[compact]' => TRUE];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalGet('admin/config/people/accounts/display/compact');
- $this->assertTitle('Manage display | Drupal');
+ $this->assertTitle('Edit Compact display of user entities | Drupal');
$this->assertLocalTasks();
// Test manage form display tabs and titles.
diff --git a/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php b/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php
index 0e493e5..2e7a4a2 100644
--- a/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php
+++ b/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php
@@ -115,10 +115,12 @@ public function testBreadCrumbs() {
];
$this->assertBreadcrumb("admin/structure/types/manage/$type/fields", $trail);
$this->assertBreadcrumb("admin/structure/types/manage/$type/display", $trail);
+ $title_html = t('Edit %view-mode-label display of %bundle-label %entity-type-label', ['%view-mode-label' => 'Teaser', '%bundle-label' => NodeType::load($type)->label(), '%entity-type-label' => 'content items']);
$trail_teaser = $trail + [
- "admin/structure/types/manage/$type/display" => t('Manage display'),
+ // @todo Improve ::assertBreadcrumbParts()
+ "admin/structure/types/manage/$type/display" => 'Edit display of ',
];
- $this->assertBreadcrumb("admin/structure/types/manage/$type/display/teaser", $trail_teaser);
+ $this->assertBreadcrumb("admin/structure/types/manage/$type/display/teaser", $trail_teaser, strip_tags($title_html));
$this->assertBreadcrumb("admin/structure/types/manage/$type/delete", $trail);
$trail += [
"admin/structure/types/manage/$type/fields" => t('Manage fields'),