Problem/Motivation

#2992410: Provide placeholders for empty blocks (for example, an empty Views listing) added the ability for blocks to specify a preview fallback string; however, this is only triggered when the block is empty.

There is currently no way for block or layout plugins to know if they are being rendered in preview mode.

Proposed resolution

Use setter injection to inform block and layout plugins when they're being rendered in preview mode. The plugins can then track this information and use it as needed.

Since block.module does not have a preview mode, this change is only needed in Layout Builder at the moment.

Remaining tasks

None.

User interface changes

None.

API changes

A new interface is needed to detect if block and layout plugins support preview mode injection. This is a backward compatible change since the new functionality is opt-in (albeit provided out of the box if extending BlockBase or LayoutDefault).

Data model changes

None.

Issue fork drupal-3027653

Command icon 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

tim.plunkett created an issue. See original summary.

tim.plunkett’s picture

Status: Active » Needs review
Issue tags: +Needs tests
StatusFileSize
new1.26 KB
mpotter’s picture

Tested this with the Facet Block and it works. Good way to determine if rendering in the Preview.

Other suggestion I made to Tim is potentially adding an isEmpty() method to the PreviewFallbackInterface that would use the Element::isEmpty($content) by default but allow custom blocks to have their own logic for “empty”.

Status: Needs review » Needs work

The last submitted patch, 2: 3027653-preview-2.patch, failed testing. View results

tim.plunkett’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new3.28 KB
new9.5 KB
new9.06 KB

Here it is with tests.

The last submitted patch, 5: 3027653-preview-5-FAIL.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 5: 3027653-preview-5-PASS.patch, failed testing. View results

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new10.92 KB
new1.42 KB

Rerolled and fixed the kernel tests

Status: Needs review » Needs work

The last submitted patch, 8: 3027653-preview-8.patch, failed testing. View results

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new10.92 KB
new1.52 KB

Reroll for upstream changes

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

phenaproxima’s picture

+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php
@@ -106,7 +108,9 @@ public function removeSection($delta) {
+    $contexts = $this->getContexts();
+    $contexts['in_preview'] = new Context(new ContextDefinition('boolean'), TRUE);
+    return $contexts;

Would it make sense to have getContexts() return a FALSE version of 'in_preview', then make getContextsDuringPreview() overwrite that with a TRUE one? That way, external code calling either of these methods doesn't need to check isset($contexts['in_preview']), and there's a consistent way to check if we're in preview mode.

tim.plunkett’s picture

As it is an optional context and a bool, that is already the behavior for any plugin. I don't much care about the contents of the array, it should only exist to be passed along to applyContextMapping or the like.

But, it doesn't matter much either way.

tim.plunkett’s picture

Issue tags: +sprint
cboyden’s picture

Status: Needs review » Needs work
StatusFileSize
new616.94 KB

This is working as expected. I've created this custom block type:

namespace Drupal\mymodule\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'TestBlock' block.
 *
 * @Block(
 *  id = "test_block",
 *  admin_label = @Translation("Test block"),
 *  context_definitions = {
 *    "in_preview" = @ContextDefinition("boolean", required = FALSE),
 *   },
 * )
 */
class TestBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
  	$in_preview = $this->getContextValue('in_preview');
  	if ($in_preview) {
  	  $markup = 'Preview of test block.';
  	}
    else {
      $markup = 'This is a fancy test block with lots of context.';
    }

    return [
      '#markup' => $markup,
      '#cache' => [
        'max-age' => 0,
      ],
    ];
  }

And it shows the regular text when viewing, and the preview text when previewing in layout builder.

The problem is, using an annotation to control whether the block cares about being in preview is not easily discoverable by a developer creating block plugins. Would it be possible to implement this with a property on the block base class?

Also, there is a bug in the context setup. With the code as above, where the annotation says required = FALSE, I get a select option in the block config that asks me to "Select a in_preview value" where the options are "- None -" or a blank string. See screenshot with with code inspection:

Context select element with blank string option.

And if I remove the required = FALSE, the block cannot be placed using the regular block layout, it's only available in layout builder.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

pavel ruban’s picture

StatusFileSize
new9.65 KB

Rerolled patch for 8.8.1

pavel ruban’s picture

StatusFileSize
new9.58 KB

fixing some test errors

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

raman.b’s picture

StatusFileSize
new10.73 KB
new1.7 KB

TestInPreviewContext.php got removed during re-roll in #18

#16 still needs to be addressed

clayfreeman’s picture

In response to #16: we're going to need a separate issue to fix this.

My recommendation for blocks that need to support both Layout Builder & the classic block layout page is to have a required context definition with a default value. Unfortunately, this isn't currently possible using the boolean data type; we first need to make sure that FALSE is considered a non-empty value for contexts, whether default or explicitly assigned (see this method for more information). (Currently, a default value of FALSE causes \Drupal\Component\Plugin\Context\Context::hasContextValue() to return FALSE.)

clayfreeman’s picture

Status: Needs work » Needs review
StatusFileSize
new12.71 KB
new5.62 KB

General review + updated patch notes:

  1. I think it's best that we set the context value in four separate locations:
    1. \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::getContextsForEntity()
    2. \Drupal\layout_builder\Plugin\SectionStorage\SectionStorageBase::getContextsDuringPreview()
    3. \Drupal\layout_builder\Section::toRenderArray()
    4. \Drupal\layout_builder\SectionComponent::toRenderArray()

    At a minimum, I believe it's most appropriate to unconditionally set the context value according to $in_preview in Section::toRenderArray() and SectionComponent::toRenderArray().

    The rationale behind this decision is to ensure that this context value is always available when using layout builder. Since neither Section::toRenderArray() or SectionComponent::toRenderArray() are internal APIs, they could ostensibly be called from contrib; because of this, I think it's most safe to ensure that a fresh context value is set in these methods. The other two locations that set the context value ensure that the context is available, for example, in the block selection form (since it's filtered by context availability).

  2. I've also added some extra test assertions for robustness.
clayfreeman’s picture

StatusFileSize
new12.71 KB

Fix spell check error.

clayfreeman’s picture

clayfreeman’s picture

Issue summary: View changes

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bhumikavarshney’s picture

StatusFileSize
new95.23 KB

Hi @clayfreeman,
Patch #25, works good for me.
Thanks

clayfreeman’s picture

Adding tag to request subsystem maintainer review.

Please let me know if the direction taken in the issues referenced in #26 is appropriate. If not, please suggest a recommended approach to finding a solution to the problem identified in #16.

(Slack thread for reference)

clayfreeman’s picture

Issue tags: -Needs subsystem maintainer review +Needs framework manager review

Escalating as per What timeframes are given for sign-offs before decisions are escalated?

The primary concerns that needs to be addressed are from #16:

[W]here the annotation says required = FALSE, I get a select option in the block config that asks me to "Select a in_preview value" where the options are " - None - " or a blank string.

[I]f I remove the required = FALSE, the block cannot be placed using the regular block layout, it's only available in layout builder.

It may also be prudent to review the discussions on MR !932 after reading this comment.

I've taken steps to address the concerns in #16 by submitting two additional issues; however, I'm not sure if the direction I've taken is acceptable for inclusion in Drupal core, and if so, I've not been given clear direction regarding the management of this issue.

Since the select box for the in_preview context value only appears when the context definition is not required (which is currently a prerequisite for being able to use the block plugin outside of Layout Builder), I wanted to find a way to be able to make the context definition required so that the select box does not appear, but also still allow the block to be placed outside of Layout Builder. The approach that I ended up taking was a combination of two supplementary changes to Drupal core:

The first of these changes, in #3194767: Required contexts with default values should not fail the requirements check in the context handler, ensures that required context definitions with default values are capable of passing the requirements check. Presently, if you have a required context definition with a default value, it will be ignored when checking requirements which will prevent the block from appearing in the Block layout UI.

The next change, in #3194768: The plugin system should only consider NULL context values to be empty to facilitate the use of otherwise FALSE values, ensures that a default value of FALSE for context definitions is not considered "empty" by Drupal. This is required since by default, in_preview should be considered FALSE unless explicitly set to TRUE by Layout Builder's preview functionality.

I want to clearly state that I'm not married to the approach that I've taken in MR !932 and the two referenced issues. While I think it'd be nice to fix up the requirements check & default value logic, I can live without it if there is a better alternative to getting this issue committed in a way that addresses the concerns in #16. I just need to be given some direction on how this issue should be handled.

In summary, this issue has the following outstanding questions:

  1. Is the approach taken in MR !932 for this issue acceptable for addition to Drupal core?
  2. If the answer to the previous question is yes, then should the referenced issues be made blockers for this issue?
  3. What additional steps should be taken to get this issue ready to be committed?

(Here's a Slack thread concerning this issue & the referenced issues which may provide useful supplementary information.)

ankithashetty made their first commit to this issue’s fork.

ankithashetty’s picture

Just rebased the existing MR, thanks!

larowlan’s picture

larowlan’s picture

Took a look at this at the request of @clayfreeman - left some comments.

I think that perhaps postponing this in favour of the other two bugs may be the best approach

clayfreeman’s picture

Status: Needs review » Postponed

Postponing this as per @larowlan's recommendation until the two child issues are committed.

In the interim, I'll try to accommodate the review criteria that can be addressed now, and save the remaining criteria for when the other issues are committed.

Thanks for your review!

clayfreeman’s picture

Version: 9.3.x-dev » 9.4.x-dev
larowlan’s picture

Thinking of another way to solve this - what if there are more than two states for previewing? e.g. think of how we use view-modes.

If we go with boolean are we locking ourselves into a binary setting if in the future we need a third, fourth or n-th option?

Should we instead have a context definition that has string values, which could be backed by constants in D9 and Enums in D10?

Default
Preview
...Some future state.

Would that also render #3194767: Required contexts with default values should not fail the requirements check in the context handler redundant and solve the impasse there?

clayfreeman’s picture

At the very least, I think YAGNI comes into play when trying to envision other view modes for Layout Builder. I doubt that we'd want to even consider view modes in Layout Builder other than "preview" and "regular" since view modes are a concept that exist outside of Layout Builder before it is ever invoked for rendering, and this could cause user & developer confusion.

The problem with using the context system is that #3194767: Required contexts with default values should not fail the requirements check in the context handler is ultimately going to be required because of how ContextAwarePluginAssignmentTrait:: addContextAssignmentElement() works. If a context definition is optional, then a context mapping configuration element will be exposed to the end user, allowing them to make blocks unaware of the in_preview context. If a context definition is required, the block will work as expected in Layout Builder, but it cannot be placed outside of Layout Builder unless the referenced issue is committed.

Ultimately, we can't use contexts at all if we consider the referenced issue to be a dead end and also want blocks to be compatible with both Layout Builder and Structure > Block layout. I still think the goal of that issue is valid, and facilitates more flexible control over context definitions.

One alternative I've been able to come up with is sort of a "caveman" approach to the problem, which would be creating a centralized Layout Builder service to track whether preview mode has been entered for the current request. Blocks can then use this service during rendering to check if they're being rendered in preview mode. The problem with this approach is that modules which provide blocks that are sensitive to preview mode will either need to establish an artificial dependency on Layout Builder, or conditionally inject the service dependency into blocks based on whether Layout Builder is installed for the current site. Neither of these side effects are ideal in my opinion. This theoretical service may also need to be written with the assumption that only a single Layout Builder element is used in each response, which isn't currently a documented restriction for Layout Builder.

Another alternative is refactoring the Block API to add support for injection of preview information directly into every block plugin. I'm imagining something like PreviewAwareBlockPluginInterface::setBlockInPreview(bool $in_preview = TRUE): void. The block would then track this value internally as needed. We could even update BlockBase to enable preview tracking for all blocks if desired. The only downside to this approach is scope creep outside of Layout Builder. We can even extend this in the future if we have concepts beyond "preview" and "regular" (even though I can't imagine this at present). I would also hope that this interface could exist in Drupal core, not in the Layout Builder module, so that contrib doesn't need to depend on Layout Builder for preview detection.

I definitely think the latter of the two is the more tenable option, at least as far as backward compatibility and developer experience are concerned. It may even be more clear than using contexts. Regardless, these ideas are just from me spitballing at 10pm on a Friday, so let me know what you think.

larowlan’s picture

I agree the second option would be better. We can even put it on BlockInterface without it being a BC break under the 1:1 rule because we have BlockBase

clayfreeman’s picture

Would it be okay to expand the scope of this issue to also include the injection of preview information into layout plugins? (This is the original reason my company took interest in this issue. The context solution allowed this out of the box, but if we drop that in favor of my proposal in #40, then this will no longer be possible.)

Given my previous naming convention, we'd have PreviewAwarePluginInterface::setInPreview(bool $in_preview): void in Drupal core instead, which would then be usable by both block plugins & layout plugins.

clayfreeman’s picture

StatusFileSize
new5.59 KB

Here's an example patch detailing what I think would be optimal as an implementation strategy. Obviously, there are a few outstanding things requiring discussion/review:

  1. Where should PreviewAwarePluginInterface live in Drupal core? I've placed it in Drupal\Core\Plugin for now to split the difference between where this interface is used (i.e., block & layout plugins for now), but I could see an argument for this living in Drupal\Core\Layout also.
  2. Do we need to worry about cacheable metadata? I think not, especially since preview mode for blocks shouldn't be cacheable in theory.
  3. I need to write tests (obviously).

Overall, I think this approach is much simpler than using the context system. Hopefully maintainers are amenable to including injection support for layout plugins too!

clayfreeman’s picture

Status: Postponed » Needs review
StatusFileSize
new9.18 KB

Adding a functional test to cover preview-aware block & layout plugins for initial code review.

clayfreeman’s picture

StatusFileSize
new9.18 KB
tim.plunkett’s picture

Status: Needs review » Needs work

I like the switch to setter injection. Much cleaner than my first context-based attempt, and sidesteps all those wrinkles with the context system.
Just a few nits on the docs, the code itself is good to go

  1. +++ b/core/lib/Drupal/Core/Layout/LayoutDefault.php
    --- /dev/null
    +++ b/core/lib/Drupal/Core/Plugin/PreviewAwarePluginInterface.php
    
    +++ b/core/lib/Drupal/Core/Plugin/PreviewAwarePluginInterface.php
    @@ -0,0 +1,21 @@
    + * Provides an interface for Layout Builder preview injection support.
    ...
    + * Block & layout plugins can implement this interface to be informed when
    + * preview mode is being used in Layout Builder.
    

    Thanks for preemptively placing this outside of LB. Except then this first oneline doc seems a bit overspecific. I'm fine with the longer form explanation to mention Layout Builder (and it could probably even do with an @see to Section::toRenderArray as an example).

    Also ditch the &, we use "and" in text

  2. +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Block/PreviewAwareBlock.php
    @@ -0,0 +1,33 @@
    +    if ($this->inPreview) {
    

    What are the odds that we'll have a feature request for a isInPreview() getter within six months? :D

    I'm fine with not adding one for now though

clayfreeman’s picture

Status: Needs work » Needs review
StatusFileSize
new9.32 KB
new857 bytes

Thanks for your review! Here are the requested revisions.

clayfreeman’s picture

Title: Allow block plugins to determine if they are being previewed » Allow block and layout plugins to determine if they are being previewed
Issue summary: View changes

Updating issue title and summary to match the new approach. Draft change record posted here.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

larowlan’s picture

Issue tags: +Needs followup

This turned out really great, awesome work.

I think there's one thing we should add, which is an implementation of hook_preprocess_layout that sets an 'is_preview' variable that can be used in layout/block twig templates. This would allow, e.g. a layout plugin to ensure the region attributes/wrappers were present in preview, but could be stripped in actual view. Or you could conditionally add a class to a region that might trigger some Javascript that you don't want to execute on the preview (e.g. you probably don't want a carousel to be interactive in layout builder preview).

I think that should be a follow-up though, as we should focus on shipping something useful and iterate from there.

Adding issue credits.

@BhumikaVarshney, I'm unchecking the credit box for your screenshot - we don't provide credit for screenshots showing a patch applies, the automated testbot will tell us that.

larowlan’s picture

I've queued a run of the patch against 10.0.x - if that passes I'll commit this tomorrow.

clayfreeman’s picture

The failure on 10.0.x appears to be transient after a rerun.

tim.plunkett’s picture

Yeah that random fail is being discussed here #3268244-29: [random test failure] Un-skip and fix QuickEditIntegrationTest::testArticleNode(), thanks for requeueing @clayfreeman

  • larowlan committed 1c4a6d6 on 10.0.x
    Issue #3027653 by clayfreeman, tim.plunkett, Pavel Ruban, raman.b,...

  • larowlan committed eba2a2d on 9.4.x
    Issue #3027653 by clayfreeman, tim.plunkett, Pavel Ruban, raman.b,...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed 1c4a6d6 and pushed to 10.0.x. Thanks!

Backported to 9.4.x

Published change record.

Can we close #3194767: Required contexts with default values should not fail the requirements check in the context handler now?

@clayfreeman can you create the follow-up discussed in #51 please - thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.