Problem/Motivation

There is this button in ContextualFormPanel:

    $build['update'] = [
      '#type' => 'button',
      '#value' => 'Update',
      '#submit_button' => FALSE,
      '#attributes' => [
        'type' => 'button',
        'data-wysiwyg-fix' => TRUE,
      ],
    ];

Which have its own logic in HtmxEvent::onInstanceUpdateButtonClick().

ContextualFormPanel seems to work well without it now and this create visual noise, some confusion ("do I need to click on it?") and uncessary code.

Proposed resolution

Can we remove it? Is it still useful in some situations? Which ones? Is it useful for accessibility?

Use case where needed

Example with an entity view on node type articles with Tags reference field.

  • Librairies > Blocks > Referenced entities > Tags (Taxonomy term) referenced by Tags
  • Move to display, publish appear, click publish
  • Click the Tags field in the builder or layer
  • Select source: [Entity] -> [Field]
  • Field : Name
  • Select source: [Field Item] Text value

The Publish button is not available, no HTMX request is performed.

Click "Update" button, then the HTMX request is triggered and "Publish" is available.

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

pdureau created an issue. See original summary.

pdureau’s picture

Assigned: Unassigned » just_like_good_vibes
pdureau’s picture

"Update" button is still useful with the "entity -> field" source.

mogtofu33’s picture

Issue summary: View changes
pdureau’s picture

Some additional tests from #3626043: Config panel loses source settings on AJAX actions and saves raw values

Setup:

  1. In Display Builder, I add a Field source and I open the contextual form
  2. I pick "Component per item" as field formatter
  3. I pick my component
  4. I open a slot details

Add a widget source (so without opening a modal):

  1. I click on "Add content"
  2. ✅ The modal open with WYSIWYG as default source
  3. I type in the textarea and change focus
  4. ✅ The preview is automatically updated with what i typed
  5. I save
  6. ✅ The preview still OK

Inside an inline modal, the preview behavior change according to the source:

Source On source load (nice to have) Live update (nice to have) Update on Save button (mandatory)
WYSWYG Link ✅ updated ✅ updated ✅ kept
Entity Link ✅ empty ⚠️ still empty ❌ still empty
Field label ✅ updated Not applicable ✅ kept
Field item ✅ empty Not applicable ✅ kept
Component ⚠️ not updated ⚠️ not updated ❌ not updated
Icon ✅ empty ⚠️ not updated ❌ not updated

We have similar behavior with "Cancel" button, but it may be just another effect of the same cause.

pdureau’s picture

I will give a try, in 2 steps:

  1. Fix "automatic" updates in inline-modals components/display_builder/js/display_builder.js
    • Re-send after any AJAX in the form, instead of only change event, so we cover Cancel, Remove and "Add content". Check we are not creating unexpected revisions
    • Re-send a save dropped by htmx. When we change something while a save is in flight, htmx queues the second save and may lost it.
  2. Remove Update button in ContextualFormPanel and the related logic in HtmxEvents

And the related tests.

pdureau’s picture

Status: Active » Needs review

MR created: https://git.drupalcode.org/project/display_builder/-/merge_requests/510

Setup:

  1. In Display Builder, I add a Field source and I open the contextual form
  2. I pick "Component per item" as field formatter
  3. I pick my component
  4. I open a slot details

Add a widget source (so without opening a modal):

  1. I click on "Add content"
  2. ✅ The modal open with WYSIWYG as default source
  3. I type in the textarea and change focus
  4. ✅ The preview is automatically updated with what i typed
  5. I save
  6. ✅ The preview still OK

Inside an inline modal, the preview behavior change according to the source:

Source On source load (nice to have) Live update (nice to have) Update on Save button (mandatory)
WYSWYG (plain text) ✅ empty ✅ updated ✅ kept
WYSWYG (HTML) ✅ empty ⚠️ not updated ✅ kept
Entity Link ✅ empty
Empty because we render a sample entity that's never saved, so it has no URL. On a real entity it renders.
Field label ✅ updated Not applicable ✅ kept
Field item ✅ empty Not applicable ✅ kept
Component ✅ updated ✅ updated ✅ updated
Icon ✅ empty ✅ updated ✅ updated

So it seems to work well.

However, let's also check:

  • the changes done in playwright tests, are they legit? they fail anyway
  • if we create unexpected revisions or not
pdureau’s picture

Assigned: pdureau » Unassigned

the changes done in playwright tests, are they legit?

They look legit, because they are in reality only 2 changes:

Don't target the removed contextual_form_update Add duration.blur()
tests/src/Playwright/Tests/contextual_form.spec.ts ✅ ✅
tests/src/Playwright/Tests/preview.spec.ts ✅
tests/src/Playwright/Tests/textarea.spec.ts ✅ ✅
tests/src/Playwright/objects/DisplayBuilder.ts ✅ ✅

if we create unexpected revisions or not

We create more revision, because we don't wait to push an "Update" button to propagate change, but as far as I tested, the revision are added each time I am doing an impactful change (switch component, add a label...), not at every action (pick a formatter...). So it looks good.

The only problem is all the revisions being titled "Update [Entity] ➜ [Field] configuration"

We can do better (#3534427: Add details in instance update logs) but it is out of scope of this ticket.

Open for review. It is more convenient to do the review with Entity View Display.

csakiistvan’s picture

Assigned: Unassigned » csakiistvan
csakiistvan’s picture

Assigned: csakiistvan » Unassigned
Status: Needs review » Needs work
StatusFileSize
new708.95 KB
new792.68 KB
new919.28 KB

❌ Does not fix the issue — applied MR !510, rebuilt caches and configured a Tags referenced-entities block on the article Entity View Display: the Update button is gone and the preview updates on its own, but clicking "Add content" under the field now saves the empty nested source and the builder preview crashes with Error: Call to a member function getValue() on null until a source is picked. Without the MR the same click does not crash the preview, and the Update button was not needed there to get the preview updated.

Prerequisites
  • Display Builder enabled on node.article.default with the default profile.
  • Blocks › Referenced entities › "Tags (Taxonomy term) referenced by Tags", Source [Entity] ➜ [Field], Field Name, then "Add content" and leave the new Source on "- Select -".
Actual results
  • MR !510: the preview shows the error above, thrown in DerivableContextSourceBase::getPropValue() (ui_patterns, line 106); picking [Field item] Text value recovers it.
  • 1.0.x: no error; selecting [Field item] Text value updated the preview without clicking Update, so the use case in the issue summary did not reproduce.
Code review
  • MR !510 at d9f6ce62, rebased on 1.0.x, pipeline green.
  • Drupal.Ajax.prototype.success now triggers a save after every #ajax response, not only change events, so the "Add content" click persists an incomplete source.
andres alvarez’s picture

Assigned: Unassigned » andres alvarez
andres alvarez’s picture

StatusFileSize
new91.85 KB
new86.9 KB

Follow-up to comment #11 by @csakiistvan.

I added a fix in ApiController::update(). A new removeEmptySlotRows() method drops, before setSource(), any sources row with an empty source_id. The automatic save that fires after every #ajax response therefore no longer persists incomplete rows. The Update button removal and the JS are unchanged.

  • Tests: two new Kernel tests in ContextualFormPanelRoundTripTest (testEmptySlotRowIsNotPersisted and testCompleteSlotRowIsPersisted). They fail without the fix and pass with it. The contextual_form, preview and textarea Playwright specs pass with the fix (13/13, Firefox only).
  • Screenshots (before and after): on the "Test switch" block (Type: Component, Component: Test 1), in Slot 1 I set the Source to "- Select -" and reload. Before, the empty row is still stored after the reload. After, no row remains. The row shows as "Wysiwyg" because that is the select's default value; the stored source_id was empty.

What I could not verify:

  • I did not reproduce the Call to a member function getValue() on null error, neither before nor after the fix. From the ui_patterns code, getSourcePlugins() only does an isset() check on source_id, so an empty value reaches createSourcePlugin(''), which returns NULL. But I did not execute that path. In my test setup the builder does not offer the entity_field source in that block's slot.
  • I did not verify the summary's use case either (Tags, Entity → Field → Field item → Text value, Publish without the Update button).

@csakiistvan, could you share which component, context and steps you used to reach the nested source? With that I can reproduce it and confirm the fix covers the error.

andres alvarez’s picture

Assigned: andres alvarez » Unassigned
Status: Needs work » Needs review
csakiistvan’s picture

Thanks @andres alvarez for the follow-up fix and the new Kernel tests.

❌ Does not fix the issue — applied MR !510, rebuilt caches and repeated the steps from comment #11 on the article Entity View Display: the getValue() on null crash is gone, but leaving the new nested Source on "- Select -" now shows Warning: Undefined array key 0 in DerivableContextSourceBase->getSourcePlugins() (ui_patterns, line 166) twice in the preview and as a message. Picking [Field item] Text value clears it and the preview updates without the Update button, so the empty-source path still needs handling.

Suggestion

Drop the key once no row is left, so ui_patterns takes its existing !isset($source_configuration['source_id']) early return (not tested here):

if ($key === 'sources') {
  $value = \array_filter($value, ...);
  if ($value === []) {
    unset($settings[$key]);
    continue;
  }
}

Alternatively, guard it in ui_patterns with !empty($sources['sources']) instead of isset().

pdureau’s picture

Status: Needs review » Needs work

Drop the key once no row is left, so ui_patterns takes its existing !isset($source_configuration['source_id']) early return (not tested here):

Thansk. I will do that and redo the tests from comment #9.

pdureau’s picture

Assigned: Unassigned » pdureau