Problem/Motivation
Site Settings provides 2 blocks, one is a rendered entity, the other is just plain field values, which is great, but limits the flexibility for site builders to control what is displayed and how.
Use case: I have a Site Setting type, Locations. In this type, I have an address field ( rendered as an address ) and a Geolocation field rendered as a Google Map. I have 3 view modes, Default, Address, and Map.
- Default displays the address and map.
- Address just displays the address.
- Map just displays the map.
I want site builders to be able to place a map block in some contexts, and just an address block in other contexts. However, the Site Settings block only ever renders the default view_mode.
https://git.drupalcode.org/project/site_settings/-/blob/8.x-1.x/src/Plug...
$pre_render = $view_builder->view($entity, 'default');
Proposed resolution
Add a setting to the RenderedSiteSettingsBlock that allows for selecting any enabled view_mode for the selected bundle.
My thoughts are, add an AJAX callback on the BlockForm such that when a Site Setting Type is selected, a second select box is displayed with enabled view_modes for that Site Setting Type.
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
// Inject this service.
// ...
public function defaultConfiguration() {
return [
'setting' => NULL,
'label_display' => FALSE,
'view_mode' => NULL,
] + parent::defaultConfiguration();
}
public function blockForm($form, FormStateInterface $form_state) {
// Allow selection of a site settings entity type.
$form['setting'] = [
'#type' => 'entity_autocomplete',
'#target_type' => 'site_setting_entity_type',
'#title' => $this->t('Site setting type'),
'#weight' => '20',
'#required' => TRUE,
// Add ajax callback
'#ajax' => [
'callback' => '::viewModeCallback',
'event' => 'autocompleteclose change',
'wrapper' => 'edit-view-mode',
],
];
if (isset($this->configuration['setting']) && !empty($this->configuration['setting'])) {
$setting_entity_type = $this->entityTypeManager
->getStorage('site_setting_entity_type')
->load($this->configuration['setting']);
$form['setting']['#default_value'] = $setting_entity_type;
// Load the view_mode widget if configuration 'view_mode' is set.
$view_mode_default = NULL;
if (isset($this->configuration['view_mode']) && !empty($this->configuration['view_mode'])) {
$view_mode_default = $this->configuration['view_mode'];
}
array_push($form, $this->viewModeElement($setting_entity_type, $view_mode_default);
}
else {
// Return empty div so we have a target container.
$form['view_mode'] = [
'#markup' => '<div class="view-mode"></div>',
'#allowed_tags' => ['div'],
];
}
return $form;
}
public function getViewModes($entity_type) {
$view_modes = $this->repository->getViewModeOptionsByBundle('site_setting_entity_type', $entity_type);
return $view_modes;
}
public function viewModeCallback(array &$form, FormStateInterface $form_state) {
$entity_type = $form_state->getValue('setting');
$element = $this->viewModeElement($entity_type);
return $element;
}
public function viewModeElement($entity_type = NULL, $default = NULL) {
$view_modes = $this->getViewModes($entity_type);
$options = $this->getViewModes($entity_type);
if (is_array($options) && count($options) > 1) {
$element['view_mode'] = [
'#type' => 'select',
'#title' => $this->t('Select View Mode'),
'#options' => $options,
];
if ($default != NULL) {
$element['view_mode']['#default_value'] = $default;
}
}
else {
$element['view_mode'] = [
'#type' => 'hidden',
'#value' => ($default) ? $default : $options,
];
}
return $element;
}
I haven't tested any of this, but I feel like it's the correct approach.
Remaining tasks
Build / test.
User interface changes
New field to select view mode.
API changes
Data model changes
Add a new setting for view mode.
Issue fork site_settings-3171895
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
scott_euser commentedInteresting issue and makes sense to add! I don't think we actually need to do the ajax as anyways there is a risk that the view mode is subsequently deleted for the entity. You could use https://git.drupalcode.org/project/entity_block/-/blob/8.x-1.x/src/Plugi... as an example - there they just get all view modes available for the entity type. We can fallback to the default view mode on render if the particular entity does not have that view mode available (it may even be that core automatically falls back to default for us).
Next step for this would be to provide this as a patch file.
Thanks!
Comment #3
scottsawyerI am working on a patch now. I have the ajax-y bit working. Basically, if there is only one view mode, I set it a hidden field with a value of default, or if the setting field is either empty or invalid, hidden field with no value.
This works great in my environment where I have on Setting Type "Locations" which has fields like: phone, address, map, with corresponding view modes: Phone, Address, Map. Each view mode displays only one field. However, I have another Setting Type "Memberships", which has two different view modes, Organization, and Image. Being able to switch the available view modes every time the Setting Type is changed I think is a real win.
I will look at the link you shared. I am going to upload a WIP. It's working great on my dev site, so try it out.
Comment #4
scottsawyerGah, I noticed a glitch, when the new field is brought in, it's missing the name attribute. Unfortunately ( or fortunately ), I am unavailable for a week or so. Unless someone else is willing to jump on this, it'll have to wait a bit.
Comment #5
scottsawyerSorry for the delay, reworked the patch. Previously, it was not saving the settings when creating the block. I tested creating a block with a new view mode, placing the block, then deleting the view mode and it falls back to default. Please test it out.
I only built this with the Rendered Site Setting, not the Simple Site Setting.
Comment #6
scott_euser commentedThanks for the progress on this! I've tested it out and it works well.
On further examination of the code I think we need to have the view mode required and validated in case ajax is slow, to avoid inconsistency on saved data. So I think its adding this: https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Block%21B...
I've also added in an update hook that installs the view mode.
Then once that is in place, I am currently getting some tests into https://www.drupal.org/project/site_settings/issues/3138260 for block - once completed, we can add an appropriate test here.
Comment #7
scottsawyerThank you for the review. Should we then mark this as postponed on tests?
Comment #8
scott_euser commentedSorry for the delayed reply. Yes, once #3138260 is completed we can finish this off and get it in.
Comment #9
rgeerolfI have a project that needs updating to D10, depending on this functionality (my bad). Rerolled the patch for the current 8.x-1.x (1.20) and sharing it in case anyone needs it.
Comment #10
scott_euser commentedThanks, this is open to an MR if someone wants to contribute it. Would also need test coverage adding please. Work should be done to the 2x branch. Thanks!
Comment #12
scott_euser commentedComment #14
scott_euser commentedThanks everyone for the work on this! Managed to get around to it, so its merged and in now + has test coverage.