Problem/Motivation

Since #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder (for a lot of use cases), the per-entity layout override page became the primary interface a content author might use to create and manage content that is displayed when a user is viewing an entity.

At the same time, discussions are taking place around how the layout pages are going to fit in with other modules like content moderation, core features like optionally creating new entity revisions and the existing CRUD entity access permissions and APIs.

A lot of the solutions proposed so far have been around enhancing the functionality of the per-entity layout builder form in order to bring it closer in line with the existing ways that entities are treated during standard content editing in the "Edit" tab. That is, we've seen proposals across various issues such as:

  • Load the latest revision of an entity (to mimic the changes content_moderation applies to entity forms).
  • Create new revisions of an entity automatically when updating a layout (when the user has selected such an option on the bundle form).
  • Add additional access checks to the override storage for the "update" operation based on the layouts host entity.

I think these are all good initiatives to enhance layout builder and increase integration, but I wonder if the root of what we're trying to accomplish here is to mimic all of the semantics that have already been built into entity forms. Could the layout builder override page be some kind of special entity form, that has easy or even automatic integration with other modules that have an expectation that entity forms are the primary mechanism of content authoring?

Using content moderation as a case study (since I am most familiar with this module), I believe the existing proposals to integrate with layout builder fall short because:

  • The layout override form is the encouraged mechanism for authoring new content.
  • Applying a moderation workflow to fields on the entity is redundant if those fields can be replaced with a non-reusable block and skip moderation altogether.
  • If a user transitions an item of content to state "Foo", they are not necessarily allowed to create new content revisions while leaving the state unchanged (think of something going into review, with an author no longer able to make changes to that content until a review has been completed).

The only way I see these two modules integrating in a way that is true to the sprit of both feature sets: rich content driven layouts/moderated content workflows, is if:

  • Entity "update" access is respected. (see related issue)
  • The latest revision is loaded. (see related issue)
  • The same moderation state transition widget is visible and available when saving the layout override form which allows the authoring of new content. (no related issue in discussion)

I believe if the layout override page was an entity form, we would almost get this level of integration automatically/for free. I also believe there are probably a lot of other authoring related contrib modules which would also benefit from such an affordance.

It would also flip the requirement for layout builder to integrate with every other module, it would instead use entity forms as the common and existing pattern for controlling the lifecycle of content authoring as the basis for compatibility and integration.

Proposed resolution

This part would largely be open for discussion. One possible approach could be:

  1. Allow the layout builder form to operate as a field widget over the top of the layout_builder__layout field.
  2. Create a transient EntityFormDisplay object connected to an entity form route, configured with only the components relevant to layout builder. Here is an example of this in action, and @eclipsegc mentioned ctools also uses this concept.
  3. Ensure adequate metadata exists in the form/route for modules to make decisions about adding components to the entity form where required. Modules that indiscriminately deal with entity access or entity forms would integrate easily or automatically.

Sandbox prototype: https://www.drupal.org/sandbox/sam/3008758
Video demo: https://www.youtube.com/watch?v=GT-bf_7o3cY

Remaining tasks

User interface changes

Manage display, before/after

Per entity override, before/after

API changes

Added new hook: hook_layout_builder_overrides_entity_form_display_alter()

Data model changes

N/A

CommentFileSizeAuthor
#83 after_2.png102.9 KBSam152
#83 before_2.png96.5 KBSam152
#83 after_1.png102.76 KBSam152
#83 before_1.png107.23 KBSam152
#75 3004536-lb_entityform-75-interdiff.txt1.48 KBtim.plunkett
#75 3004536-lb_entityform-75-PASS.patch59.26 KBtim.plunkett
#75 3004536-lb_entityform-75-FAIL.patch59.27 KBtim.plunkett
#73 3004536-lb_entityform-73-interdiff.txt9.1 KBtim.plunkett
#73 3004536-lb_entityform-73-PASS.patch59.26 KBtim.plunkett
#73 3004536-lb_entityform-73-FAIL.patch59.26 KBtim.plunkett
#71 3004536-lb_entityform-71-interdiff.txt1.69 KBtim.plunkett
#71 3004536-lb_entityform-71.patch56.12 KBtim.plunkett
#68 3004536-lb_entityform-68-interdiff.txt765 bytestim.plunkett
#68 3004536-lb_entityform-68.patch55.11 KBtim.plunkett
#62 3004536-lb_entityform-61-interdiff.txt10.94 KBtim.plunkett
#62 3004536-lb_entityform-61.patch55.03 KBtim.plunkett
#58 3004536-lb_entityform-58-interdiff.txt2.15 KBtim.plunkett
#58 3004536-lb_entityform-58.patch48.68 KBtim.plunkett
#55 3004536-lb_entityform-55-interdiff.txt3.76 KBtim.plunkett
#55 3004536-lb_entityform-55.patch46.53 KBtim.plunkett
#53 3004536-lb_entityform-53-interdiff.txt2.83 KBtim.plunkett
#53 3004536-lb_entityform-53.patch49.03 KBtim.plunkett
#50 3004536-lb_entityform-50-interdiff.txt10.42 KBtim.plunkett
#50 3004536-lb_entityform-50.patch48.67 KBtim.plunkett
#49 3004536-lb_entityform-49-interdiff.txt2.38 KBtim.plunkett
#49 3004536-lb_entityform-49.patch57.5 KBtim.plunkett
#47 3004536-lb_entityform-47-interdiff.txt23.61 KBtim.plunkett
#47 3004536-lb_entityform-47.patch56.8 KBtim.plunkett
#45 3004536-lb_entityform-45-interdiff.txt7.94 KBtim.plunkett
#45 3004536-lb_entityform-45.patch52.55 KBtim.plunkett
#44 3004536-lb_entityform-44-interdiff.txt1.68 KBtim.plunkett
#44 3004536-lb_entityform-44.patch51.03 KBtim.plunkett
#43 3004536-lb_entityform-43-interdiff.txt770 bytestim.plunkett
#43 3004536-lb_entityform-43.patch51.36 KBtim.plunkett
#40 3004536-lb_entityform-40-interdiff.txt3.44 KBtim.plunkett
#40 3004536-lb_entityform-40.patch59.24 KBtim.plunkett
#39 3004536-lb_entityform-39-interdiff.txt27.3 KBtim.plunkett
#39 3004536-lb_entityform-39.patch59.72 KBtim.plunkett
#38 3004536-lb_entityform-38.patch41.95 KBtim.plunkett
#36 3004536-lb_entityform-36.patch34.11 KBtim.plunkett
#34 3004536-lb_entityform-34-interdiff.txt2.26 KBtim.plunkett
#34 3004536-lb_entityform-34.patch34.41 KBtim.plunkett
#32 3004536-lb_entityform-32.patch34.02 KBtim.plunkett
#32 3004536-lb_entityform-32-interdiff.txt1.98 KBtim.plunkett
#29 3004536-lb_entityform-29.patch34.02 KBtim.plunkett
#25 3004536-lb_entityform-25-interdiff.txt3.83 KBtim.plunkett
#25 3004536-lb_entityform-25.patch33.83 KBtim.plunkett
#23 3004536-lb_entityform-23-interdiff.txt5.77 KBtim.plunkett
#23 3004536-lb_entityform-23.patch33.8 KBtim.plunkett
#18 3004536-lb_entityform-18-PASS.patch34.42 KBtim.plunkett
#18 3004536-lb_entityform-18-FAIL.patch4.65 KBtim.plunkett
#18 3004536-lb_entityform-18-interdiff.txt4.71 KBtim.plunkett
#16 3004536-lb_entityform-16.patch31.59 KBtim.plunkett
#15 3004536-lb_entityform-15-interdiff.txt4 KBtim.plunkett
#15 3004536-lb_entityform-15.patch31.51 KBtim.plunkett
#13 interdiff-11-13.txt2.61 KBtedbow
#13 3004536-13-user-view-FAIL.patch28.42 KBtedbow
#13 3004536-13-user-view-TEST_ONLY_PASS.patch2.61 KBtedbow
#12 3004536-12.patch26.51 KBtedbow
#12 interdiff-11-12.txt2.97 KBtedbow
#11 interdiff-8-11.txt2.5 KBtedbow
#11 3004536-11.patch26.14 KBtedbow
#8 3004536-lb_entityform-8.patch23.64 KBtim.plunkett
#8 3004536-lb_entityform-8-interdiff.txt18.04 KBtim.plunkett
#6 3004536-lb_entityform-6.patch10.92 KBtim.plunkett
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Sam152 created an issue. See original summary.

Sam152’s picture

If you've made it this far, thank you very much for reading! This my best effort to summarise a discussion had between @tedbow, @eclipsegc and myself during Drupal Europe.

I must admit I haven't been following the layout builder queue very closely and haven't done a detailed code review to validate the proposal. There are probably lots of other considerations and factors which haven't made it into the IS, but my main motivation was to clearly document one approach which I think could lead to a solid integration with content moderation.

It seemed like during our discussion that it would be possible, but there are potentially some complexities to the layout form which might make this tricky. I unfortunately don't remember what those are, so it would be great to either do some validation on this idea or put it rest and focus on the other integration issues.

Thanks!

tim.plunkett’s picture

I strongly disagree with the suggestions in this issue.

First, the Layout Builder UI is not a form itself. The primary interactions are not form-based, and only in some cases do certain actions result in a form being presented.

Second, the Layout Builder UI only currently happens right now to be presented in Drupal render arrays. There has been discussion of a decoupled UI that would use JSON API underneath for manipulating the data.

Third, the fact that there is any magic done only at the entity form level is exactly why we have all of these bugs in the first place.

Which brings us back to #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing as the true underlying issue.


This to me is very reminiscent of the move from form-level validation to data-level validation.
Coupling anything to the form representation of this is a mistake, and one that we made in the past and are still struggling to undo.

Sam152’s picture

Thanks for taking the time to read over the @tim.plunkett!

First, the Layout Builder UI is not a form itself. The primary interactions are not form-based, and only in some cases do certain actions result in a form being presented.

IMO form elements might be an important part of integrating with other components, but I suppose it depends on how those conversations shake out.

Third, the fact that there is any magic done only at the entity form level is exactly why we have all of these bugs in the first place.
...
Coupling anything to the form representation of this is a mistake, and one that we made in the past and are still struggling to undo.

I agree we need HTML agnostic entity semantics and handling, but in an HTML context I feel like entity forms and entity displays are a good way of reusing the solutions to HTML form related problems. For better or worse, there is a bunch of consideration and fiddling that needs to go in-between a load and save call of an entity.

Sam152’s picture

Issue summary: View changes

I am working on a project which requires layout builder and content moderation working together, I've been prototyping an integration based on the proposal in this issue in a sandbox here: https://www.drupal.org/sandbox/sam/3008758. I also recorded a video demo of this in action here: https://www.youtube.com/watch?v=GT-bf_7o3cY.

Adding to the IS, hopefully helps demonstrate what I had in mind for this better than words can.

tim.plunkett’s picture

Status: Active » Needs review
FileSize
10.92 KB

Here's a first pass at converting from that sandbox to an actual patch.

Status: Needs review » Needs work

The last submitted patch, 6: 3004536-lb_entityform-6.patch, failed testing. View results

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
18.04 KB
23.64 KB

This exposed #3009344: Reloading the Layout Builder while JS is available differs from when JS is not available, because of the page refresh caused by the form submit. Adding that patch into here for now.

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett
Status: Needs review » Needs work
Issue tags: +Needs tests, +Blocks-Layouts

This breaks horribly when comment module is installed (and comments are enabled for the bundle)

Sam152’s picture

I also wonder if we need the reset and cancel operations added for parity with the current approach. Let me know how you go, I'd be happy to pitch in if I can help anywhere.

tedbow’s picture

Here is failing test

tedbow’s picture

Status: Needs work » Needs review
FileSize
2.97 KB
26.51 KB
+++ b/core/modules/layout_builder/src/Plugin/Field/FieldWidget/LayoutBuilderWidget.php
@@ -0,0 +1,122 @@
+    return $layout_controller->layout($section_storage);

This fails because the comments fields uses #lazy_builder which I think is in compatible with forms. Of course other fields also will use this. Are there other things fields use when rendering that will be incompatible in a form context?

Here is patch that just renders the layout right here.

tedbow’s picture

For some reason this patch makes viewing the user profile fail.
Here is:

  1. A patch with just a test addition to test viewing the user. This passes without this patch
  2. Same patch as #11 with user view test. It should fail.
  3. I change drupalci.yml to only run this 1 test in both patches

Status: Needs review » Needs work

The last submitted patch, 13: 3004536-13-user-view-FAIL.patch, failed testing. View results

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
31.51 KB
4 KB

No API for how to handle revisions means no way of knowing if we got all the magical edgecases. Here it is that while even if the entity storage supports revisions, the entity type may not 🤔🤔🤔

tim.plunkett’s picture

mark_fullmer’s picture

Manual functional testing of this all checks out, as delineated below. I'm wondering if it would be good to add content_moderation + layout_builder test integration that validates the following:

0. With layout_builder & content_moderation configured for a revisionable entity type...
1. Save a node with no fielded content
2. Save a new layout, with a single 'basic block' inline block, in "draft" state.
3. View the node. Verify the basic block is not present
4. Publish the draft of the node
5. Verify the basic block is present, in the layout section chosen.
6. Revert to the previous revision
7. View the node. Verify the basic block is not present

tim.plunkett’s picture

Great idea @mark_fullmer!

Interdiff is the actual test added to the patch, I had to manipulate it for the FAIL patch to not assume it's a form.

tim.plunkett’s picture

I didn't do #17.6 and #17.7, whoops. Missing some permission, cause there's no revision tab available.

Sam152’s picture

I was going to suggest perhaps removing the hook from content moderation and doing the hook + test in a follow-up to keep the scope of this issue contained, but even better to just get it done. Nice!

@tim.plunkett now the comment form issue has been addressed, besides #19 was there anything else outstanding you wanted to do with this patch, or is it ready for a thorough review?

tim.plunkett’s picture

There are still a couple things to be resolved.
First, the local tasks are still there, so there is a duplicate "Save Layout" task that functions differently from the "Save" button (which should likely be renamed).
Second, the form is only for overrides, not for defaults. I think that will be confusing, and make resolving #2917777: Improvements to the styling, grouping, etc. of the Layout Builder UI actions form particularly difficult.
Third, I'm not 100% sold on the workaround for comment forms, but it might be okay...

All in all, it's still ready for a review I think

The last submitted patch, 18: 3004536-lb_entityform-18-FAIL.patch, failed testing. View results

tim.plunkett’s picture

Switching between users in the test is only testing CM functionality, not anything special here, so I removed it.
Also simplified by using the Powered by Drupal block instead of the block content module.

I tried to switch from node to entity_test_rev but it seems that the Revisions UI and the revert links only exist for nodes?!


Updated IS for remaining tasks

Sam152’s picture

Patch so far looks great to me, only a few comments.

  1. +++ b/core/modules/content_moderation/content_moderation.module
    @@ -172,6 +173,17 @@ function content_moderation_entity_view(array &$build, EntityInterface $entity,
    + * Implements hook_layout_builder_entity_form_display_alter() for content_moderation.
    

    We no longer need the latter part of the docblock.

  2. +++ b/core/modules/layout_builder/layout_builder.module
    @@ -55,6 +57,13 @@ function layout_builder_entity_type_alter(array &$entity_types) {
    +    if ($entity_type->entityClassImplements(FieldableEntityInterface::class) && !$entity_type->getFormClass('layout_form')) {
    

    Checking if the handler already exists or not provides an API for developers to customise the layout form for entity types.

    Does this new API need to be documented at all, or should perhaps it not be provided without a concrete use case

  3. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -161,6 +172,13 @@ public function buildRoutes(RouteCollection $collection) {
    +      // Switch from a standard controller to an entity form.
    +      $route = $collection->get("layout_builder.overrides.$entity_type_id.view");
    +      $view_defaults = $route->getDefaults();
    +      unset($view_defaults['_controller']);
    +      $view_defaults['_entity_form'] = "$entity_type_id.layout_form";
    +      $route->setDefaults($view_defaults);
    

    Can we just change the original route instead of creating then altering?

tim.plunkett’s picture

1) Fixed

2) Fair point, but just to be safe I changed the name from layout_form to layout_builder__form, I feel a little better about overwriting it blindly if it's namespaced

3) This is being altered because the initial creation of the route is in the trait which is also used for the defaults (Manage Display).
This should be fixed here as part of #21.2, added an inline comment so we don't forget :)

Sam152’s picture

Okay, cool.

Not really sure how we'll use an entity form for defaults. I suppose it would be an entity form over the top of the EntityViewDisplay but config entities don't use field api/widgets, so it would probably be a totally different approach, the layout configuration would probably need to be moved to a custom render element.

I don't think moving the defaults interface to an entity form would benefit from any of the points in the IS, so perhaps it would be best to evaluate in a follow up?

tim.plunkett’s picture

I think it's more about the divergence of the two UIs being confusing.
In HEAD both have local tasks in the same place for Save/Cancel.
With this patch, defaults will retain those but overrides will instead have form buttons.

I'm going to give this a shot this week, if it's horrible we can figure out a follow-up

Sam152’s picture

Okay, awesome. We could always change the default form to use buttons and put them at the bottom for consistency.

tim.plunkett’s picture

Priority: Normal » Major
FileSize
34.02 KB

I did not get a chance to work on this yet. Unassigning for now, but this is still a priority.
Here's a reroll.

tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned

Status: Needs review » Needs work

The last submitted patch, 29: 3004536-lb_entityform-29.patch, failed testing. View results

tim.plunkett’s picture

I'm super biased, but I really like this interdiff :D

Status: Needs review » Needs work

The last submitted patch, 32: 3004536-lb_entityform-32.patch, failed testing. View results

tim.plunkett’s picture

Okay, not quite as nice.

Status: Needs review » Needs work

The last submitted patch, 34: 3004536-lb_entityform-34.patch, failed testing. View results

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett
Status: Needs work » Needs review
FileSize
34.11 KB

Going to work on this again, rerolling for now.

Status: Needs review » Needs work

The last submitted patch, 36: 3004536-lb_entityform-36.patch, failed testing. View results

tim.plunkett’s picture

tim.plunkett’s picture

This makes saving actually use the new buttons, removing the local tasks.

tim.plunkett’s picture

+++ b/core/modules/layout_builder/src/Plugin/Field/FieldWidget/LayoutBuilderWidget.php
@@ -0,0 +1,124 @@
+      $entity->{OverridesSectionStorage::FIELD_NAME} = $temporary_section_storage->getSections();

This was bothering me, so I refactored that whole bit.

tim.plunkett’s picture

Status: Needs review » Needs work

The last submitted patch, 40: 3004536-lb_entityform-40.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
51.36 KB
770 bytes

Fixing #40, and rerolled

tim.plunkett’s picture

Title: Consider moving per-entity layout overrides into an entity form for better integration with other content authoring modules and core features » Move the Layout Builder UI into an entity form for better integration with other content authoring modules and core features
Issue tags: +Needs screenshots
FileSize
51.03 KB
1.68 KB
tim.plunkett’s picture

I cleaned up the instantiation of the one-off form display, and documented the new hook.
Also I cleaned up the formatting of the @todo, renamed the forms now that we have two of them, and added trailing commas to the new widget annotation where applicable.

I think this is ready for review.

tedbow’s picture

  1. +++ b/core/modules/layout_builder/layout_builder.api.php
    @@ -0,0 +1,29 @@
    +function hook_layout_builder_entity_form_display_alter(\Drupal\Core\Entity\Display\EntityFormDisplayInterface $display) {
    

    Should hook also have "override" in the title some how? \Drupal\layout_builder\Form\DefaultsEntityForm is also an entity form but the alter doesn't happen there.

  2. +++ b/core/modules/layout_builder/layout_builder.module
    @@ -54,7 +59,15 @@ function layout_builder_entity_type_alter(array &$entity_types) {
    +  // Ensure every fieldable entity type has a layout form.
    +  foreach ($entity_types as $entity_type) {
    +    if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
    +      $entity_type->setFormClass('layout_builder__form', OverridesEntityForm::class);
    +    }
    +  }
    

    Should we only be changing the form class if at least 1 bundle for the entity type has the layout builder enabled and overrides enabled?

    Maybe it doesn't matter since this is new form class key.

    Also why is the $operation parameter here 'layout_builder__form' instead of just 'layout_builder'. Looking at other entity classes in the annotations their form handler keys don't include 'form'.

    Right now this would create form ids like node_article_layout_builder__form_form which if you wanted to alter you would have to use mymodule_form_node_article_layout_builder__form_form_alter.

    Also should override getBaseFormId() others the base form id for all node layout overrides would be just node_form which is the same as all other node operations. So if a module is currently implementing hook_form_BASE_FORM_ID_alter for node_form then they would now be altering this new operation.

    While they probably should be checking operation they may have only been expecting 'edit' and 'add'

tim.plunkett’s picture

(treating each "Also" as new bulleted item)

1) Agreed
2) We can't rebuild entity types when enabling overrides, it's safer to do it here
3) In #25 I switched it from layout_form to layout_builder__form, but I agree layout_builder is enough
4) Fair enough!

Status: Needs review » Needs work

The last submitted patch, 47: 3004536-lb_entityform-47.patch, failed testing. View results

tim.plunkett’s picture

Because of the way forms rebuild their entities, we need to resync them as contexts.
Also expanding that part of the test to make it clearer why it was failing.

tim.plunkett’s picture

Here's a version _without_ the revision changes.

The last submitted patch, 49: 3004536-lb_entityform-49.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 50: 3004536-lb_entityform-50.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
49.03 KB
2.83 KB

I had the right idea but the wrong fix. Also restoring the @todo for the revert link.

Sam152’s picture

Should the interdiff from #50 be spun off into a new issue? Perhaps even under the content_moderation component, since it would be CM consuming an API provided by LB? I think the test probably also belongs in content_moderation for that reason.

Reviewing the whole patch, only nits found, this is looking really good to me.

  1. +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
    @@ -0,0 +1,129 @@
    + * Provides a form containing the Layout Builder UI for defaults.
    
    +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -0,0 +1,163 @@
    + * An entity form for serving layout builder pages.
    

    Super nit, but we call it "Layout Builder" and "layout builder". Also that second comment should probably s/pages/overrides/?.

  2. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -0,0 +1,163 @@
    +    $this->entity = $this->sectionStorage->getContextValue('entity');
    ...
    +    $this->sectionStorage->setContextValue('entity', $entity);
    ...
    +    $this->layoutTempstoreRepository->delete($this->sectionStorage);
    ...
    +    $form_state->setRedirectUrl($this->sectionStorage->getRedirectUrl());
    ...
    +      '#url' => $this->sectionStorage->getLayoutBuilderUrl('discard_changes'),
    ...
    +      '#url' => $this->sectionStorage->getLayoutBuilderUrl('revert'),
    ...
    +  /**
    +   * Retrieves the section storage object.
    +   *
    +   * @return \Drupal\layout_builder\SectionStorageInterface
    +   *   The section storage for the current form.
    +   */
    +  public function getSectionStorage() {
    +    return $this->sectionStorage;
    +  }
    

    Seems like we should just delete this method if we're accessing the property directly?

  3. +++ b/core/modules/layout_builder/src/Plugin/Field/FieldWidget/LayoutBuilderWidget.php
    @@ -0,0 +1,61 @@
    +  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    +    $element += [
    +      '#type' => 'layout_builder',
    +      '#section_storage' => $this->getSectionStorage($form_state),
    +    ];
    +    return $element;
    +  }
    

    Wow, this got way cleaner. Nice.

  4. +++ b/core/modules/layout_builder/tests/src/Functional/LayoutCommentFieldTest.php
    @@ -0,0 +1,84 @@
    +/**
    + * Tests the Layout Builder with a comment field.
    + *
    + * @group layout_builder
    + */
    +class LayoutCommentFieldTest extends BrowserTestBase {
    

    It looks like the code that was written to fix the bug this test was written for is gone. Does that mean we still need the test? We'd be testing for a bug we had but one that was never committed.

tim.plunkett’s picture

#54
1) Ah, yes
2) Similar to #3003666: Provide access to a Section or SectionComponent from within a $form_state, we need this for code outside of LB
3) 🙌🏻
4) Agreed, removed.

omrmankar’s picture

+1

Status: Needs review » Needs work

The last submitted patch, 55: 3004536-lb_entityform-55.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
48.68 KB
2.15 KB

#2924201: Resolve random failure in LayoutBuilderTest so that it can be added to HEAD introduced more tests looking for the link that is now a button.

Kristen Pol’s picture

The interdiff looks fine. I'll take it for a quick spin.

tedbow’s picture

  1. +++ b/core/modules/content_moderation/content_moderation.module
    @@ -11,6 +11,7 @@
    +use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
    

    unused use. shouldn't need any changes to this file now

  2. +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
    @@ -0,0 +1,129 @@
    +class DefaultsEntityForm extends EntityForm {
    

    Should we have LayoutEntityFormTrait?

    actions() is almost the same

    getSectionStorage() is the same

    buildEntity() could be the same if there was protect field for contextKey

    save() has chunk the same

  3. +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
    @@ -0,0 +1,129 @@
    +   * @var \Drupal\layout_builder\SectionStorageInterface
    +   */
    +  protected $sectionStorage;
    

    Should this actually be DefaultsSectionStorageInterface since the entity forms is made to work with defaults not generic storage?

  4. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -0,0 +1,163 @@
    +   * @var \Drupal\layout_builder\SectionStorageInterface
    

    Should this actually be OverridesSectionStorageInterface since the entity forms is made to work with overrides not generic storage?

  5. +++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php
    @@ -60,22 +62,20 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage
    +    if ($entity_type_id) {
    +      $main_defaults['_entity_form'] = "$entity_type_id.layout_builder";
    +    }
    +    else {
    +      $main_defaults['_controller'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::layout';
    +    }
    

    We should check here that the entity type has form handler for 'layout_builder' looking at layout_library it passes and entity_type_id but not the entity form. So I think this would fail.

  6. The issue summary should mention the new hook under API changes.
  7. +++ b/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php
    @@ -38,6 +38,16 @@ protected function setUp() {
    +   * Tests viewing a user.
    +   */
    +  public function testViewUser() {
    

    Why are adding this?

Kristen Pol’s picture

FYI: It errors out on simplytest.me so I didn't test it yet. I have found some patches don't spin up there even though they pass tests. I'm not sure why. I'm checking with @nerdstein on this.

tim.plunkett’s picture

#60
1) Fixed
2) I think they are different enough that a trait right now would be too early of an abstraction
3/4) We don't call any particularly specific methods for either interface, I don't see a reason to get overly specific with it
5) I regret writing this trait. I had to add the logic higher up.
6) Okay
7) Same as #54.4, so I removed it here. It was a test exposing a flaw in a much earlier approach that makes no sense now (had to do with #lazy_builder I think)

xjm’s picture

Given the architectural impacts of this change, the fact that this impacts how LB will integrate with other modules, and the concerns raised in #3, I think this issue should probably have a framework manager review.

xjm’s picture

What happened to the comment regression test from back in #11?

Also, those needed screenshots would be helpful for reviewing this. Thanks!

tim.plunkett’s picture

Rebuttal to #3, which was written by a particularly wonderful but misinformed individual:

First, while the primary interactions of Layout Builder UI are indeed not form-based, the benefit of having certain form widgets present (revision log, moderation state) outweighs any of these theoretical concerns.

Second, a decoupled version of this UI would also need to take into account the other fields presented by this new form, again referring to the revision log or moderation state.

Third, my original resistance to this issue was because it was coupling the move to an entity form with the changes needed for translation and revision support, and I was worried that the work would not be done in the "correct" place, aka #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing
Now that all of the custom translation/revision code has been removed from this issue and that other issue has been progressing, I no longer have this concern.

Finally, the work done in #3027938: Abstract the contents of LayoutBuilderController into a render element made this issue so much cleaner and not nearly as worrisome as it was before.


As far as the comment regression test, I removed that per #54.4
It was only added by me to prove a problem with the initial early code that has since been resolved by multiple other issues, mostly in FieldBlock itself.
It was out of scope here anyway, and no part of this issue could generate a patch to purposefully make that test fail.

Sam152’s picture

Status: Needs review » Needs work

Manually testing this, it looks like the widget on the overrides form doesn't appear. I tracked this down to field access and \Drupal\layout_builder\Field\LayoutSectionItemList::defaultAccess:

  /**
   * {@inheritdoc}
   */
  public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
    // @todo Allow access in https://www.drupal.org/node/2942975.
    return AccessResult::allowed();
  }

It looks like this is brand new from #3028301: Do not expose to Layout Builder's sections either in defaults or overrides to REST and other external APIs. Not sure how best to resolve this, this is an approach that works:

diff --git a/core/modules/layout_builder/src/Form/OverridesEntityForm.php b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
index 0531b1d299..ec0c24e60f 100644
--- a/core/modules/layout_builder/src/Form/OverridesEntityForm.php
+++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
@@ -97,7 +97,9 @@ protected function init(FormStateInterface $form_state) {
    */
   public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) {
     $this->sectionStorage = $section_storage;
-    return parent::buildForm($form, $form_state);
+    $form = parent::buildForm($form, $form_state);
+    $form[OverridesSectionStorage::FIELD_NAME]['#access'] = TRUE;
+    return $form;
   }

   /**
tim.plunkett’s picture

What about abandoning the Widget approach and just printing the LB UI directly in the form?
The rest of the changes would stay the same.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
55.11 KB
765 bytes

I retested the patch and it's failing, which is a relief.
Here's a patch for #66 while I look into #67 more.

The last submitted patch, 62: 3004536-lb_entityform-61.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 68: 3004536-lb_entityform-68.patch, failed testing. View results

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
56.12 KB
1.69 KB

On second thought, I think keeping the widget is fine and we can use this workaround with an explicit @todo since that will change eventually.

Also fixed another link-to-button change in a new test.

phenaproxima’s picture

I think this makes a lot of sense. Only found some random nitpicks.

  1. +++ b/core/modules/layout_builder/layout_builder.api.php
    @@ -0,0 +1,29 @@
    +/**
    + * Allows customization of the Layout Builder UI for per-entity overrides.
    + *
    + * @see hook_entity_form_display_alter()
    + * @see \Drupal\layout_builder\Form\OverridesEntityForm::init()
    + */
    +function hook_layout_builder_overrides_entity_form_display_alter(\Drupal\Core\Entity\Display\EntityFormDisplayInterface $display) {
    +  $display->setComponent('moderation_state', [
    +    'type' => 'moderation_state_default',
    +    'weight' => 2,
    +    'settings' => [],
    +  ]);
    +}
    

    Shouldn't $display be LayoutEntityDisplayInterface?

  2. +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
    @@ -0,0 +1,129 @@
    +class DefaultsEntityForm extends EntityForm {
    

    Should this be @internal?

  3. +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
    @@ -0,0 +1,129 @@
    +    return $this->entity->getEntityTypeId() . '_layout_builder_form';
    

    Not really a big deal for this patch, but IMHO we should always use $this->getEntity() in entity forms. Classes should consume their own APIs. But, that said, core is already littered with examples of this anti-pattern so I'll consider it a nitpick and not mention it again in this issue. :)

  4. +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
    @@ -0,0 +1,129 @@
    +    $this->entity = $this->sectionStorage->getContextValue('display');
    

    Can we use $this->setEntity() here?

  5. +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
    @@ -0,0 +1,129 @@
    +    return EntityViewDisplay::load($route_parameters['entity_type_id'] . '.' . $route_parameters['bundle'] . '.' . $route_parameters['view_mode_name']);
    

    Nit: We should probably use $this->entityTypeManager->getStorage()->load() here.

  6. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -0,0 +1,169 @@
    +/**
    + * An entity form for serving Layout Builder overrides.
    + */
    

    "Serving"?

  7. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -0,0 +1,169 @@
    +class OverridesEntityForm extends ContentEntityForm {
    

    Should be marked explicitly @internal?

  8. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -0,0 +1,169 @@
    +    $display = EntityFormDisplay::create([
    

    This should probably use $this->entityTypeManager->getStorage()->create().

  9. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -0,0 +1,169 @@
    +    $this->entity = $this->sectionStorage->getContextValue('entity');
    

    Let's use $this->setEntity().

  10. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -0,0 +1,169 @@
    +    $this->messenger()->addStatus($this->t('The layout override has been saved.'));
    

    I don't know if words "layout override" should be exposed to users. A friendlier message here might simply be "The layout has been saved."

  11. +++ b/core/modules/layout_builder/src/Plugin/Field/FieldWidget/LayoutBuilderWidget.php
    @@ -0,0 +1,61 @@
    +class LayoutBuilderWidget extends WidgetBase {
    

    Should this be @internal too?

tim.plunkett’s picture

1) No, LEDI is for entity view. This is a vanilla entity form display.

2) Fixed

3) This is copied straight from the parent class, but I'll humor you :)

4) Same

5) Changed. Could have been worse, the upstream code used entity_get_display() :)

6) Changed to match the other form.

7) Fixed

9) Fixed

10) This string isn't changed in the patch:

-    if ($section_storage instanceof OverridesSectionStorageInterface) {
-      $this->messenger->addMessage($this->t('The layout override has been saved.'));
-    }
-    else {
-      $this->messenger->addMessage($this->t('The layout has been saved.'));
-    }

11) Fixed with the full internal message (the forms don't need that)


Also per discussion with @phenaproxima I added a test implementation of hook_layout_builder_overrides_entity_form_display_alter().
For the FAIL patch I commented out the invocation of that alter.

phenaproxima’s picture

Status: Needs review » Needs work

The changes look excellent. One teensy thing I found in the interdiff, tho:

+++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
@@ -14,7 +14,9 @@
+ * Provides a form containing the Layout Builder UI for overrides/.

That slash probably shouldn't be there. :)

tim.plunkett’s picture

Hah, I'm also missing a `return `.

The last submitted patch, 73: 3004536-lb_entityform-73-FAIL.patch, failed testing. View results

The last submitted patch, 73: 3004536-lb_entityform-73-PASS.patch, failed testing. View results

The last submitted patch, 75: 3004536-lb_entityform-75-FAIL.patch, failed testing. View results

phenaproxima’s picture

It's Christmas-ey! RTBC for me once framework managers sign off.

larowlan’s picture

  1. +++ b/core/modules/layout_builder/src/Form/DefaultsEntityForm.php
    @@ -0,0 +1,130 @@
    +    $actions = parent::actions($form, $form_state);
    

    Nice, this is a lot better UX than the local tasks were.

  2. +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php
    @@ -79,13 +79,11 @@ protected function setUp() {
    +    // Reload the page to prevent random failures.
    +    $this->drupalGet($this->getUrl());
    

    $this->getSession()->reload() (and also ick)

Regarding #3 I assume that @tim.plunkett has had a change of heart on that front as he's been working on this patch?

@tim.plunkett can you confirm?

Leaving the needs FM tag until that point.

Sam152’s picture

That is addressed in #65.

larowlan’s picture

Nevermind, @Sam152 pointed me to #65

Sam152’s picture

Issue summary: View changes
FileSize
107.23 KB
102.76 KB
96.5 KB
102.9 KB

Manual testing looks good. Adding screenshots to the IS as requested.

Sam152’s picture

Issue tags: -Needs screenshots
phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record

I think the new hook will probably need a change record. Tagging for that.

Sam152’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record

I have drafted a CR here: https://www.drupal.org/node/3032274

The last submitted patch, 11: 3004536-11.patch, failed testing. View results

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me!

xjm’s picture

I know the visual changes aren't specifically the point of the issue, but they're also a UX improvement for several reasons.

larowlan’s picture

Crediting @mark_fullmer and @phenaproxima for reviews that shaped the patch
Crediting @Sam152 for the issue summary and manual testing screenshots

larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed a9a5382 and pushed to 8.7.x. Thanks!

Published change record

  • larowlan committed a9a5382 on 8.7.x
    Issue #3004536 by tim.plunkett, tedbow, Sam152, phenaproxima,...
micail’s picture

After updating the core, existing node layout form which contains a contact form can not save the changes. Nested contact form validation prevents submission. Sorry, if this is a lack of my knowledge.

tim.plunkett’s picture

Can you open a new issue with full steps to reproduce, and link it here? Thanks!

Status: Fixed » Closed (fixed)

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

godotislate’s picture

@tim.plunkett
I was able to reproduce #93 and created an issue here: https://www.drupal.org/project/drupal/issues/3046667

wombatbuddy’s picture

I installed Drupal 8.8.0 and it seems that this feature is absent.
I don't see the 'Layout (entity form)' tab which is present on the video.

Sam152’s picture

@wombatbuddy, the video was a prototype which included both forms side by side, after this patch was merged the existing tab was updated to be an entity form.

wombatbuddy’s picture

@Sam152, tell me please, is it possible now to use the 'Layout builder' for customizing layouts of entity forms?
I looked into
/core/modules/layout_builder/layout_builder.api.php
and I don't see the hook from the patch in it.
Thanks.

Sam152’s picture

@wombatbuddy layout builder only supports customising view displays, not form displays. This issue was related to the per-entity override authoring form, not for customising forms themselves.