Unverified Commit 53f19807 authored by larowlan's avatar larowlan

Issue #2992410 by tim.plunkett, scottsawyer, sugaroverflow, phenaproxima:...

Issue #2992410 by tim.plunkett, scottsawyer, sugaroverflow, phenaproxima: Provide placeholders for empty blocks (for example, an empty Views listing)
parent 530f7ac5
......@@ -11,6 +11,7 @@
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface;
use Drupal\Core\Plugin\PluginWithFormsTrait;
use Drupal\Core\Render\PlaceholderInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Component\Transliteration\TransliterationInterface;
......@@ -23,7 +24,7 @@
*
* @ingroup block_api
*/
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface {
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface, PlaceholderInterface {
use ContextAwarePluginAssignmentTrait;
use MessengerTrait;
......@@ -252,6 +253,13 @@ public function getMachineNameSuggestion() {
return $transliterated;
}
/**
* {@inheritdoc}
*/
public function getPlaceholderString() {
return $this->t('Placeholder for the "@block" block', ['@block' => $this->label()]);
}
/**
* Wraps the transliteration service.
*
......
......@@ -8,6 +8,7 @@
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\EntityDisplayBase;
use Drupal\Core\Render\Element;
use Drupal\Core\TypedData\TranslatableInterface as TranslatableDataInterface;
/**
......@@ -269,7 +270,7 @@ public function buildMultiple(array $entities) {
foreach ($entities as $id => $entity) {
// Assign the configured weights.
foreach ($this->getComponents() as $name => $options) {
if (isset($build_list[$id][$name])) {
if (isset($build_list[$id][$name]) && !Element::isEmpty($build_list[$id][$name])) {
$build_list[$id][$name]['#weight'] = $options['weight'];
}
}
......
<?php
namespace Drupal\Core\Render;
/**
* Allows an element to provide a placeholder representation of itself.
*/
interface PlaceholderInterface {
/**
* Returns a string to be used as a placeholder.
*
* This is typically used when an element has no output and must be displayed,
* for example during configuration.
*
* @return string|\Drupal\Core\StringTranslation\TranslatableMarkup
* A placeholder string for this element.
*/
public function getPlaceholderString();
}
......@@ -5,6 +5,8 @@
use Drupal\block_content\Access\RefinableDependentAccessInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\PlaceholderInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\layout_builder\Access\LayoutPreviewAccessAllowed;
use Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent;
......@@ -98,6 +100,9 @@ public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) {
'#weight' => $event->getComponent()->getWeight(),
'content' => $block->build(),
];
if ($event->inPreview() && Element::isEmpty($build['content']) && $block instanceof PlaceholderInterface) {
$build['content']['#markup'] = $block->getPlaceholderString();
}
$event->setBuild($build);
}
}
......
......@@ -130,13 +130,22 @@ public function build() {
// render array. If the hook is invoked the placeholder will be
// replaced.
// @see ::replaceFieldPlaceholder()
'#markup' => new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]),
'#markup' => $this->getPlaceholderString(),
];
}
CacheableMetadata::createFromObject($this)->applyTo($build);
return $build;
}
/**
* {@inheritdoc}
*/
public function getPlaceholderString() {
$entity = $this->getEntity();
$extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
return new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]);
}
/**
* Replaces all placeholders for a given field.
*
......
......@@ -17,7 +17,6 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Psr\Log\LoggerInterface;
......@@ -160,13 +159,17 @@ public function build() {
$build = [];
$this->logger->warning('The field "%field" failed to render with the error of "%error".', ['%field' => $this->fieldName, '%error' => $e->getMessage()]);
}
if (!empty($entity->in_preview) && !Element::getVisibleChildren($build)) {
$build['content']['#markup'] = new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $this->getFieldDefinition()->getLabel()]);
}
CacheableMetadata::createFromObject($this)->applyTo($build);
return $build;
}
/**
* {@inheritdoc}
*/
public function getPlaceholderString() {
return new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $this->getFieldDefinition()->getLabel()]);
}
/**
* {@inheritdoc}
*/
......
......@@ -22,6 +22,7 @@ class LayoutBuilderTest extends BrowserTestBase {
'layout_builder_views_test',
'layout_test',
'block',
'block_test',
'node',
'layout_builder_test',
];
......@@ -482,6 +483,49 @@ public function testDeletedView() {
$assert_session->pageTextNotContains('Test Block View');
}
/**
* Tests the usage of placeholders for empty blocks.
*
* @see \Drupal\Core\Block\BlockPluginInterface::getPlaceholderString()
* @see \Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray::onBuildRender()
*/
public function testBlockPlaceholder() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalLogin($this->drupalCreateUser([
'configure any layout',
'administer node display',
]));
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
// Customize the default view mode.
$this->drupalGet("$field_ui_prefix/display-layout/default");
// Add a block whose content is controlled by state and is empty by default.
$this->clickLink('Add Block');
$this->clickLink('Test block caching');
$page->fillField('settings[label]', 'The block label');
$page->pressButton('Add Block');
$block_content = 'I am content';
$placeholder_content = 'Placeholder for the "The block label" block';
// The block placeholder is displayed and there is no content.
$assert_session->pageTextContains($placeholder_content);
$assert_session->pageTextNotContains($block_content);
// Set block content and reload the page.
\Drupal::state()->set('block_test.content', $block_content);
$this->getSession()->reload();
// The block placeholder is no longer displayed and the content is visible.
$assert_session->pageTextNotContains($placeholder_content);
$assert_session->pageTextContains($block_content);
}
/**
* Asserts that a text string only appears once on the page.
*
......
......@@ -230,11 +230,10 @@ protected function getTestBlock(ProphecyInterface $entity_prophecy, array $confi
* @covers ::build
* @dataProvider providerTestBuild
*/
public function testBuild(PromiseInterface $promise, $in_preview, $expected_markup, $log_message = '', $log_arguments = []) {
public function testBuild(PromiseInterface $promise, $expected_markup, $log_message = '', $log_arguments = []) {
$entity = $this->prophesize(FieldableEntityInterface::class);
$field = $this->prophesize(FieldItemListInterface::class);
$entity->get('the_field_name')->willReturn($field->reveal());
$entity->in_preview = $in_preview;
$field->view(Argument::type('array'))->will($promise);
$field_definition = $this->prophesize(FieldDefinitionInterface::class);
......@@ -269,40 +268,20 @@ public function testBuild(PromiseInterface $promise, $in_preview, $expected_mark
*/
public function providerTestBuild() {
$data = [];
$data['array, no preview'] = [
$data['array'] = [
new ReturnPromise([['content' => ['#markup' => 'The field value']]]),
FALSE,
'The field value',
];
$data['array, preview'] = [
new ReturnPromise([['content' => ['#markup' => 'The field value']]]),
TRUE,
'The field value',
];
$data['empty array, no preview'] = [
$data['empty array'] = [
new ReturnPromise([[]]),
FALSE,
'',
];
$data['empty array, preview'] = [
new ReturnPromise([[]]),
TRUE,
'Placeholder for the "The Field Label" field',
];
$data['exception, no preview'] = [
$data['exception'] = [
new ThrowPromise(new \Exception('The exception message')),
FALSE,
'',
'The field "%field" failed to render with the error of "%error".',
['%field' => 'the_field_name', '%error' => 'The exception message'],
];
$data['exception, preview'] = [
new ThrowPromise(new \Exception('The exception message')),
TRUE,
'Placeholder for the "The Field Label" field',
'The field "%field" failed to render with the error of "%error".',
['%field' => 'the_field_name', '%error' => 'The exception message'],
];
return $data;
}
......
......@@ -105,6 +105,13 @@ public function defaultConfiguration() {
return ['views_label' => ''];
}
/**
* {@inheritdoc}
*/
public function getPlaceholderString() {
return $this->t('Placeholder for the "@view" views block', ['@view' => $this->view->storage->label()]);
}
/**
* {@inheritdoc}
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment