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 af994361fa..393858ce52 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; +use Drupal\Core\Entity\Entity\EntityViewMode; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; @@ -9,6 +10,8 @@ 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; /** @@ -124,16 +127,141 @@ public static function defaultSettings() { ] + parent::defaultSettings(); } + protected function getSelectedViewModeName(FormStateInterface $form_state) { + return $this->getSetting('view_mode', $form_state); + } + + /** + * 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'); + $bundle_entity_type_id = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType(); + $bundles = array_keys($this->getFieldSetting('handler_settings')['target_bundles']); + + $currently_selected_view_mode_name = $this->getSelectedViewModeName($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 (count($bundles) === 1) { + $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++) { + $description['#items'][] = [ + '#type' => 'link', + '#prefix' => $i == 0 + ? $this->t('Configure %view-mode-label view mode for', ['%view-mode-label' => $currently_selected_view_mode_label]) . ' ' + : '', + '#title' => $this->t('%bundle-label %entity-type-label', ['%bundle-label' => $bundle_info[$bundles[$i]]['label'], '%entity-type-label' => 'Media items']), + '#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, + ]), + ], + ]; + } + } + $description['#items'][] = [ + '#type' => 'link', + '#title' => $this->t('add new view mode'), + '#prefix' => 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 6248e46efe..b4fa3f25ca 100644 --- a/core/misc/ajax.es6.js +++ b/core/misc/ajax.es6.js @@ -368,6 +368,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 5ea52425be..d9b89a56e2 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -164,6 +164,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 b4a608acd5..763a025c62 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 0a91862b35..8dfbd78412 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,18 +248,46 @@ 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. * @@ -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/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php index b08e7f8fe5..495f14a426 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 fe7a27011c..1b27d2742a 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 e9142dc853..09e448e330 100644 --- a/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php +++ b/core/modules/system/tests/src/Functional/Menu/BreadcrumbTest.php @@ -113,10 +113,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'),