Overview

⚠️ The client-side/UI data model was introduced very early on and is very simple: LayoutNode and friends were introduced on May 9, 2024, during DrupalCon Portland, before we even were using the d.o issue queue! It's simple and good enough for #3454094: Milestone 0.1.0: Experience Builder Demo. But it's too simple for #3455753: Milestone 0.2.0: Early preview.

It currently looks like this:

export interface LayoutNode {
  name?: string;
  uuid: UUID;
  nodeType: 'slot' | 'component' | 'root';
  type?: string;
  children: LayoutNode[];
  props?: {} | undefined;
}

The client-side/UI data model must evolve because:

  1. When trying to place the shoe_tab_panel SDC-as-an-XB component that #3446722: Introduce an example set of representative SDC components; transition from "component list" to "component tree" introduced, you'll notice that the name SDC prop cannot be used as normal. This was documented long ago and left for the future:
    // @todo the current quick-and-dirty UI PoC unfortunately prevents any prop from being named `name`, because it expects that to convey the component name — but it's not actually one of the props consumed by the SDC.
    

    → handled in #3487773: Get component name from components list not from the component's model

  2. Designs for 7.1 Token support are coming, and implementing them will bring the ability to the UI to use DynamicPropSources in addition to StaticPropSources, to use data stored in base/bundle fields of the host entity ⇒ more client data model changes needed
  3. The work on #3475672: Research: Possible backend implementations of auto-save, drafts, and publishing might result in yet more client data model changes
  4. The docs for the Redux integration should help inform a more complete client data model #3446434: Document "Semi-Coupled Theme Engine" and "Redux-integrated field widgets" components
  5. Finally, different component types may have different kinds of inputs, for example blocks-as-components will likely need block settings, not StaticPropSources: #3484666: Show configuration forms for Block components
  6. If product requirement 13. Undo and Redo must also apply to bundle/base fields on the host entity (i.e. Field Widgets in the "regular entity form"), that would also impact the UI data model. Product lead @lauriii should clarify that 😄🙏 Confirmed by @lauriii in #14.
  7. #3467870: Support `{type: array, …}` prop shapes might also impact this.

Proposed resolution

  1. #3499540: Remove dynamic prop sources from test data
  2. #3499554: Move client-side assumptions about prop form data shape into a series of prop-specific transforms
  3. #3494684: Add ::clientModelToInput() to ComponentSourceInterface, the inverse of ::inputToClientModel()
  4. #3493941: Maintain a per-component set of prop expressions/sources
  5. #3493943: SDC+JS Component Sources: split default values into `resolved` and `source`
  6. #3499550: Support server-side massaging and validating of ComponentInputsForm values
  7. #3504421: Rename `PropSourceComponent.field_data` to `.propSources`
  8. #3500152: Remove InputBehaviorsBlockSettingsForm and consolidate with input components form
  9. #3487284: [META] Add explicit end-to-end test coverage for using all core field widgets including multiple cardinalities in the "Page Data" (content entity) form

User interface changes

None.

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

Wim Leers created an issue. See original summary.

gauravvvv’s picture

Assigned: Unassigned » gauravvvv
gauravvvv’s picture

Assigned: gauravvvv » Unassigned
kristen pol’s picture

Issue summary: View changes

Added link to code in summary.

kristen pol’s picture

Good to know this... we have components that use "name" for props so tracking this:

#3470573: Track issue that XB doesn't allow props to be called "name"

wim leers’s picture

wim leers’s picture

Title: Some components cannot be used in XB because UI prevents SDC props being named `name` » Evolve XB UI's data model to allow SDCs with `name` prop, DynamicPropSource support, etc.
Version: » 0.x-dev
Assigned: Unassigned » jessebaker
Issue summary: View changes
Status: Active » Postponed
wim leers’s picture

Issue summary: View changes
wim leers’s picture

@lauriii, point 6 in the issue summary's Overview needs a clarification/response from you. 🙏

wim leers’s picture

Issue summary: View changes
wim leers’s picture

#3469610: Prepare for multiple component types: prefix Component config entity IDs with `sdc` just landed. Component config entity IDs are now present in the client-side data model, instead of SDC plugin IDs. Next up: #3475584: Add support for Blocks as Components. So one small step in the right direction has already been made! :)

wim leers’s picture

Title: Evolve XB UI's data model to allow SDCs with `name` prop, DynamicPropSource support, etc. » Evolve XB UI's data model to allow SDCs with `name` prop, non-SDC components, DynamicPropSource support, etc.
lauriii’s picture

Assigned: lauriii » Unassigned
Status: Postponed (maintainer needs more info) » Active
Issue tags: -Needs product manager review

If product requirement 13. Undo and Redo must also apply to bundle/base fields on the host entity (i.e. Field Widgets in the "regular entity form"), that would also impact the UI data model. Product lead @lauriii should clarify that 😄🙏

The undo/redo should apply consistently to any content and design changes in XB. This means that for consistency, the undo and redo should also apply to changes in the values of base fields and bundle fields. 👍

wim leers’s picture

Thanks for confirming. That massively complicates things obviously. That means the undo/redo history must track not only the XB component tree (its tree structure + props values), but also the host entity's values — both in server-side representations (to be able to update props values using DynamicPropSources) and client-side representations (to be able to update the corresponding bundle/base field widgets — presumably without triggering AJAX updates to ensure instantaneous reactivity of the UI when undoing/redoing).

That's quite a big challenge. Because field widgets today do not support that kind of undo/redo either… 🙈

wim leers’s picture

Issue summary: View changes
wim leers’s picture

Issue summary: View changes

Oops, fix bad copy/paste 😅

wim leers’s picture

Issue summary: View changes

wim leers credited bnjmnm.

wim leers credited tedbow.

wim leers’s picture

@tedbow, @effulgentsia, @longwave, @bnjmnm, @jessebaker, @traviscarden and I just met and discussed this issue.

Notes: https://docs.google.com/document/d/1sMpbWxnOZM-yq4IbrabUBh7-RGYj_2-aYkBR...

We have a rough outline for what we think could/should work.

Next steps:

  1. first land #3446434: Document "Semi-Coupled Theme Engine" and "Redux-integrated field widgets" components
  2. then provide concrete examples for each combination in the table that that issue is adding to the docs
wim leers’s picture

Title: Evolve XB UI's data model to allow SDCs with `name` prop, non-SDC components, DynamicPropSource support, etc. » [PP-1] Evolve XB UI's data model to allow SDCs with `name` prop, non-SDC components, DynamicPropSource support, etc.
Issue summary: View changes
Related issues: +#3487773: Get component name from components list not from the component's model

@jessebaker started #3487773: Get component name from components list not from the component's model, which addresses the first point in the issue summary 👍

It's been RTBC since Thursday, but needs final sign-off from @jessebaker. He's back from PTO tomorrow. Postponing on that.

wim leers’s picture

Title: [PP-1] Evolve XB UI's data model to allow SDCs with `name` prop, non-SDC components, DynamicPropSource support, etc. » Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc.
Issue summary: View changes
Related issues: +#3484666: Show configuration forms for Block components

#3487773: Get component name from components list not from the component's model landed.

I think the most important next step is #3484666: Show configuration forms for Block components. I'll try to push this issue forward tomorrow, but ideally it would not be me 😇

wim leers’s picture

Issue summary: View changes
larowlan’s picture

We'll also probably need a version number or hash to prevent clobbering of other user's edits in multi-user env.

larowlan’s picture

Assigned: Unassigned » larowlan

Planning to pick this up next week

wim leers’s picture

Issue summary: View changes

When you do pick this up next week: see #3489899: Add support for global regions, where @longwave, @lauriii and @jessebaker articulated already one significant change to support PageTemplate: we'll get rid of the special "root" in the client-side data model, in favor of multiple named component trees, with the content region's component tree representing the component tree of the currently edited content entity's XB component tree, if the XB UI is currently looking at a route pointing to a content entity.

larowlan’s picture

Issue summary: View changes

.

larowlan’s picture

larowlan’s picture

Issue summary: View changes

.

wim leers’s picture

* locked regions so that content-type templates don't impact page templates

How could they ever? Content type templates would be independent of PageTemplate config entities? 🤔

The challenge I see with content type templates is that they're a single component tree (i.e. comparable to a single region's component tree in a PageTemplate), but with the need to have arbitrary subtrees locked. It sounds like you're thinking of naming the editable subtrees? 🤔 But that depends on what the product/UX decisions would be for #3455629: [PP-1] [META] 7. Content Templates — aka "default layouts" — affects the tree+props data model.

larowlan’s picture

I added

locked regions so that content-type templates don't impact page templates

based on comms in 2 in https://www.drupal.org/project/experience_builder/issues/3489899#mr429-n... but it sounds like I've represented that wrong

larowlan’s picture

my current thinking on how to solve this involves the following:

  • stop calling postPreview from a useEffect hook in sendPreviewRequest
  • put the previewHtml in the redux store as its own prop, select that in Preview and pass that along as the iframe src
  • add a getPreviewHtml to the preview API and add a controller for that, use that for the initial load
  • change formStateToStore to instead POST to the preview endpoint, send the current model AND the component ID and the new prop values for that component
  • in the preview endpoint do the widget massaging stuff
  • return the built previewHTML like preview currently does (top level html key) but also return editedComponentId and newValues (After having run Drupal widget massage etc)
  • client side unwrap the response and set the previewHTML in the store using onQueryStarted and updateQueryDate in the POST preview mutation https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#u.... Also use onQueryStarted in this mutation to dispatch the model change

I think this gets us:

  • no additional http request because we're already doing a round trip for the preview when we change values
  • drupal/server side is the authority on how to take the form values and manipulate them
  • I think we can simplify a fair bit of the logic in things like getPropValues because all that is done Drupal side - in fact I'm pretty sure we can delete that with this in place

I'll start building this out tomorrow

bnjmnm’s picture

Re #37 👏 I'd been hoping we could get to a point where the form could just POST what it has vs a bunch of client side manipulation and this seems like a feasible way to do it.

Some things that come to mind that may not need to be considered for this initial implementation (or at all 🤷) but just to get them out there:

  • Are there scenarios where validation status is based on something more complex than what can be easily enforced clientside (html5/AJV)? Particularly if the criteria can't be determined until the value massaging takes place? If this is the case, perhaps this can be factored into what the Preview Endpoint returns - if the FE is aware of a validation issue it can then output a message etc and it will be clear why the preview is not updating.
  • This approach seems useful for other component edit forms such as block settings, which would also presumably POST to the preview endpoint. Although I think everyone participating in this issue is aware, I want to explicitly mention that the props form will work differently from forms such as Block Settings or Entity Forms because although SDC Prop Forms use field widgets, they (unlike those other forms) are not working with fields and have different mappings, validation etc. I believe the data needed to account for those differences is already present in the proposed approach, but JIC.
  • I've been wanting an excuse to work with parts of the Redux API likeonQueryStarted + updateQueryDate, but having not used them yet I'm not sure what specifically is being addressed through their use instead of a plain old store update. I have my hunches, but if you can share details I don't have to make a wrong assumption in public
larowlan’s picture

  • Are there scenarios where validation status is based on something more complex than what can be easily enforced clientside (html5/AJV)? Particularly if the criteria can't be determined until the value massaging takes place? If this is the case, perhaps this can be factored into what the Preview Endpoint returns - if the FE is aware of a validation issue it can then output a message etc and it will be clear why the preview is not updating.

    Great idea, we can also return any invalid entries, but it would require moving the form state context into the global redux slice. I don't think that's an issue and is probably desirable in the long run

  • This approach seems useful for other component edit forms such as block settings, which would also presumably POST to the preview endpoint. Although I think everyone participating in this issue is aware, I want to explicitly mention that the props form will work differently from forms such as Block Settings or Entity Forms because although SDC Prop Forms use field widgets, they (unlike those other forms) are not working with fields and have different mappings, validation etc. I believe the data needed to account for those differences is already present in the proposed approach, but JIC.

    Yes I think this will likely end up being wiring on the backend to the source plugins with any luck

  • I've been wanting an excuse to work with parts of the Redux API likeonQueryStarted + updateQueryDate, but having not used them yet I'm not sure what specifically is being addressed through their use instead of a plain old store update. I have my hunches, but if you can share details I don't have to make a wrong assumption in public

    I'm not afraid of being wrong in public - I do it all the time. I think its important for newcomers to see experienced campaigners like us get it wrong. It helps with imposter syndrome. To answer your question I _think_ we can avoid an extra request to the backend when we update the model by making use of this feature. We will need to keep the postPreview call in useEffect for other operations that modify the tree but don't need to post to Drupal (new component insert, re-ordering, duplication, delete). This means when the proposed POST to do validation/massaging returns a new model and a new preview, we don't want that update to the model to trigger another postPreview call. I think there' should be a combination of those APIs we can call to prevent that needing an extra round trip if we already have the data. But I could also be wrong, once I get into it I'll know.

  • One thing this does do is move us further from real time updates because now we're waiting for a round-trip to Drupal before we update the model but really, we weren't updating the preview without the round trip anyway. I think to get to that we'd need to implement the transformers mentioned in the google doc and try to convert as many of the massageFormValues in the BE to transformers. We could retain the round trip when we don't have info on transformers (e.g. for widget classes we don't know about in contrib). But all of that is a long way off because we don't have real time preview updates yet anyway

larowlan’s picture

Pushed a WIP to branch 3467954-server-side-model-massage will continue tomorrow

larowlan’s picture

Made some good progress here, server side model validation and massaging is happening in a single request that also updates the preview

Work to date https://git.drupalcode.org/issue/experience_builder-3467954/-/compare/0....

Working on removing the media workarounds at present

larowlan’s picture

larowlan’s picture

Title: Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc. » [PP-2] Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc.
Status: Active » Postponed
larowlan’s picture

Added child #3493941: Maintain a per-component set of prop expressions/sources which is a discrete next step with manageable scope

larowlan’s picture

larowlan’s picture

Title: [PP-2] Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc. » [PP-1] Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc.
wim leers’s picture

Title: [PP-1] Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc. » Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc.
Status: Postponed » Needs work
effulgentsia’s picture

effulgentsia’s picture

Has the work in this issue turned up specific cases of #3487284: [META] Add explicit end-to-end test coverage for using all core field widgets including multiple cardinalities in the "Page Data" (content entity) form that could be documented on that issue? Also, I'm hoping some of the work here helps elucidate how to then implement those cases as they're discovered.

effulgentsia’s picture

#3491978-7: Implement saving block settings forms raises an interesting question about block settings forms having some characteristics of props forms and some characteristics of page data forms, and I'm wondering if the work done in this issue helps make that clean.

larowlan’s picture

Title: Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc. » META: Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc.
Issue summary: View changes

Updated issue summary with the 5 issues I think we need to resolve here to mark this as done.

I've put them in order I think they should be tackled

  1. #3499540: Remove dynamic prop sources from test data this can be actioned
  2. #3499554: Move client-side assumptions about prop form data shape into a series of prop-specific transforms this can be actioned and will help #3491978: Implement saving block settings forms
  3. #3493941: Maintain a per-component set of prop expressions/sources this has a WIP branch that is blocked on ^ and is passing PHPUnit tests
  4. #3493943: SDC+JS Component Sources: split default values into `resolved` and `source` this is blocked on ^ but there are @todos in that branch for where the works is required
  5. #3499550: Support server-side massaging and validating of ComponentInputsForm values there is a WIP branch in this issue for this, I will rebase and move it over there. We probably should wait until #3499554: Move client-side assumptions about prop form data shape into a series of prop-specific transforms is done first so that we keep block settings moving
larowlan’s picture

wim leers’s picture

wim leers’s picture

wim leers’s picture

#3500152: Remove InputBehaviorsBlockSettingsForm and consolidate with input components form landed yesterday!

#3499550: Support server-side massaging and validating of ComponentInputsForm values is in review, with currently one known FE and one known BE regression. I'm confident we'll be able to fix those.

wim leers’s picture

Component: Page builder » Data model
larowlan’s picture

Issue tags: +stable blocker
wim leers’s picture

larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Works for me

wim leers’s picture

Assigned: larowlan » Unassigned

Thanks!

Status: Fixed » Closed (fixed)

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