Overview

Discovered at #3509608-53: Unable to save an optional image with its default value (because it does not correspond to a Media entity): allow saving DefaultRelativeUrlPropSource.

When a component is placed it is assumed that it contains at least some HTML, which is wrapped in comments and has a data attribute attached so the XB UI can manipulate it.

However, it is not mandatory for components to output any HTML at all, e.g. if the component contains defensive code such as

{% if image and image.src %}
  <img src="{{ image.src }}" alt="{{ image.alt }}" />
{% endif %}

It is also possible for a component to output text, but not HTML.

Proposed resolution

  • Layers panel:

    Relying on the layers isn't necessarily ideal so there's a trade-off between preview accuracy and discoverability of these components. I'll make sure to discuss this with @callumharrod and @reneelund in future but I don't think this is something we need to solve urgently.

    — @lauriii at #3497990-16: [later phase] Support Block's PreviewAwarePluginInterface (and similar for other component sources)

  • Canvas: When we are previewing a component that does not contain any HTML tags, detect this special situation and wrap them in a container, so they can still be previewed and manipulated correctly by the UI.

Out of scope: supporting Block plugins' PreviewAwarePluginInterface — that's for #3497990: [later phase] Support Block's PreviewAwarePluginInterface (and similar for other component sources) to handle.

User interface changes

Comments

longwave created an issue. See original summary.

longwave’s picture

wim leers’s picture

Component: Page builder » Component sources
Assigned: Unassigned » longwave
Issue summary: View changes
Status: Active » Postponed (maintainer needs more info)
Issue tags: +stable blocker

More context: #3509608-54: Unable to save an optional image with its default value (because it does not correspond to a Media entity): allow saving DefaultRelativeUrlPropSource.

When we are previewing a component that does not contain any HTML tags, detect this special situation and wrap them in a container, so they can still be previewed and manipulated correctly by the UI.

How do you propose doing that for code components?

Wouldn't Handle component instances that render to the empty string be more accurate?

longwave’s picture

Status: Postponed (maintainer needs more info) » Active

There is nothing stopping an SDC or a block outputting a text string only, with no surrounding tags? I haven't tested but I think this would render as text in the preview but would not be draggable as there is no element to receive the click.

Code components are more tricky and would require hooking into the Astro renderer I guess, I haven't thought about or looked into that yet. Perhaps that can wait for a separate issue, or perhaps because of the way React components work there always has to be a wrapping element? Again, I haven't tested this yet.

wim leers’s picture

I knew there were existing issues for this problem space already, but couldn't find them. Just stumbled upon one: #3497990: [later phase] Support Block's PreviewAwarePluginInterface (and similar for other component sources).

That's basically the same problem:

  1. Depending on explicit input ("settings" for block plugins, "props" for SDCs), the block may result in an empty render array to render:
        $content = $block->build();
        if (Element::isEmpty($content)) {
          $content['#access'] = $access;
          return $content;
        }
    

    \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\BlockComponent::renderComponent()

  2. Core has a solution for this: PreviewAwarePluginInterface, but nothing in core actually uses this to render an alternative representation; only Layout Builder does it in a heavy-handed way:
          if ($event->inPreview()) {
            if ($block instanceof PreviewFallbackInterface) {
              $preview_fallback_string = $block->getPreviewFallbackString();
            }
            else {
              $preview_fallback_string = $this->t('"@block" block', ['@block' => $block->label()]);
            }
            // @todo Use new label methods so
            //   data-layout-content-preview-placeholder-label doesn't have to use
            //   preview fallback in https://www.drupal.org/node/2025649.
            $build['#attributes']['data-layout-content-preview-placeholder-label'] = $preview_fallback_string;
    
            if ($is_content_empty && $is_placeholder_ready) {
              $build['content']['#markup'] = $this->t('Placeholder for the @preview_fallback', ['@preview_fallback' => $block->getPreviewFallbackString()]);
            }
          }
    

… we could use the same strategy, but wrap that string in a wrapper.

Thoughts? And … should we just merge this into #3497990: [later phase] Support Block's PreviewAwarePluginInterface (and similar for other component sources)? 😅

longwave’s picture

wim leers’s picture

Reopening per @lauriii at #3497990-18: [later phase] Support Block's PreviewAwarePluginInterface (and similar for other component sources).

Thanks to #3518832: ComponentSource robustness: add `ComponentSourceTestBase::testGetClientSideInfo()`, the sdc.xb_test_sdc.image-optional-without-example test case (added by #3507641: Allow components containing non-required images and no examples to be placed) for \Drupal\Tests\experience_builder\Kernel\Plugin\ExperienceBuilder\ComponentSource\ComponentSourceTestBase::testGetClientSideInfo() already provides an empty string example.

We of course only want to do this when rendering a component for XB previewing purposes (so: ComponentSourceInterface::renderComponent(isPreview: TRUE)). Which means the following are closely related — until they're both in, we'll need to be careful with the test coverage:

  1. #3516705: Auto-saved changes to code components are not visible in preview-on-hover-component-list until published
  2. #3499352: SDCs should only have get HTML comments injected when `renderComponent(isPreview: TRUE)`

(Because the test coverage that #3518832 added is still expecting the component previews to be rendered with isPreview: FALSE, which #3518832 is fixing/changing 👍)

wim leers’s picture

Title: Handle components without HTML wrappers » Handle components instances rendering nothing: wrap them in a container
Issue summary: View changes

Making what @lauriii proposed more explicit.

wim leers’s picture

lauriii’s picture

Would there be a way for us to only add the wrapper if a component doesn't render anything or renders a text node?

wim leers’s picture

Title: Handle components instances rendering nothing: wrap them in a container » [PP-2] Handle components instances rendering nothing: wrap them in a container
Assigned: longwave » Unassigned
Status: Active » Postponed
Related issues: +#3518656: Add Dynamic Page Cache and BigPipe support: `block` ComponentSource does not use placeholders/#lazy_builder

If we go as far as #3485878: Server-rendered component instances should NEVER result in a user-facing error, should fall back to a meaningful error instead (+ log): possibly.

But my fear is that Block plugins' highly optimized rendering (which XB currently does not yet support — #3518656: Add Dynamic Page Cache and BigPipe support: `block` ComponentSource does not use placeholders/#lazy_builder is fixing that) will prevent us from doing this.

In fact, I think #3518656: Add Dynamic Page Cache and BigPipe support: `block` ComponentSource does not use placeholders/#lazy_builder should be a blocker to this; otherwise we can't guarantee that this is implemented correctly/completely the first time.

wim leers’s picture

wim leers’s picture

Title: [PP-2] Handle components instances rendering nothing: wrap them in a container » [PP-1] Handle components instances rendering nothing: wrap them in a container

Oops.

lauriii’s picture

Project: Experience Builder » Drupal Canvas
Version: 0.x-dev » 1.x-dev

Experience Builder has been renamed to Drupal Canvas in preparation for its beta release. You can now track issues on the new project page.