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:
- Allow the layout builder form to operate as a field widget over the top of the
layout_builder__layout
field. - 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. - 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
Comment | File | Size | Author |
---|---|---|---|
#83 | after_2.png | 102.9 KB | Sam152 |
#83 | before_2.png | 96.5 KB | Sam152 |
#83 | after_1.png | 102.76 KB | Sam152 |
#83 | before_1.png | 107.23 KB | Sam152 |
#75 | 3004536-lb_entityform-75-interdiff.txt | 1.48 KB | tim.plunkett |
Comments
Comment #2
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedIf 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!
Comment #3
tim.plunkettI 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.
Comment #4
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThanks for taking the time to read over the @tim.plunkett!
IMO form elements might be an important part of integrating with other components, but I suppose it depends on how those conversations shake out.
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.
Comment #5
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI 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.
Comment #6
tim.plunkettHere's a first pass at converting from that sandbox to an actual patch.
Comment #8
tim.plunkettThis 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.
Comment #9
tim.plunkettThis breaks horribly when comment module is installed (and comments are enabled for the bundle)
Comment #10
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI 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.
Comment #11
tedbowHere is failing test
Comment #12
tedbowThis 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.
Comment #13
tedbowFor some reason this patch makes viewing the user profile fail.
Here is:
Comment #15
tim.plunkettNo 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 🤔🤔🤔
Comment #16
tim.plunkettReroll for #2936360: Remove duplicate references to the "layout_builder__layout" field from Layout Builder, no other changes
Comment #17
mark_fullmerManual 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
Comment #18
tim.plunkettGreat 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.
Comment #19
tim.plunkettI didn't do #17.6 and #17.7, whoops. Missing some permission, cause there's no revision tab available.
Comment #20
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI 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?
Comment #21
tim.plunkettThere 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
Comment #23
tim.plunkettSwitching 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
Comment #24
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedPatch so far looks great to me, only a few comments.
We no longer need the latter part of the docblock.
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
Can we just change the original route instead of creating then altering?
Comment #25
tim.plunkett1) Fixed
2) Fair point, but just to be safe I changed the name from
layout_form
tolayout_builder__form
, I feel a little better about overwriting it blindly if it's namespaced3) 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 :)
Comment #26
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedOkay, 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?
Comment #27
tim.plunkettI 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
Comment #28
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedOkay, awesome. We could always change the default form to use buttons and put them at the bottom for consistency.
Comment #29
tim.plunkettI did not get a chance to work on this yet. Unassigning for now, but this is still a priority.
Here's a reroll.
Comment #30
tim.plunkettComment #32
tim.plunkettI'm super biased, but I really like this interdiff :D
Comment #34
tim.plunkettOkay, not quite as nice.
Comment #36
tim.plunkettGoing to work on this again, rerolling for now.
Comment #38
tim.plunkettReroll after #3027938: Abstract the contents of LayoutBuilderController into a render element
Comment #39
tim.plunkettThis makes saving actually use the new buttons, removing the local tasks.
Comment #40
tim.plunkettThis was bothering me, so I refactored that whole bit.
Comment #41
tim.plunkettNote that this currently incorporates the patch from #3009344: Reloading the Layout Builder while JS is available differs from when JS is not available
Comment #43
tim.plunkettFixing #40, and rerolled
Comment #44
tim.plunkettAs seen in #2938182-7: Design intuitive affordances for Layout Builder (for illustrating which parts of the page are editable in a given context), and as discussed in #2917777: Improvements to the styling, grouping, etc. of the Layout Builder UI actions form, the buttons should be at the top of the form.
Comment #45
tim.plunkettI 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.
Comment #46
tedbowShould 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.
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 usemymodule_form_node_article_layout_builder__form_form_alter
.Also should override
getBaseFormId()
others the base form id for all node layout overrides would be justnode_form
which is the same as all other node operations. So if a module is currently implementinghook_form_BASE_FORM_ID_alter
fornode_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'
Comment #47
tim.plunkett(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
tolayout_builder__form
, but I agreelayout_builder
is enough4) Fair enough!
Comment #49
tim.plunkettBecause 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.
Comment #50
tim.plunkettHere's a version _without_ the revision changes.
Comment #53
tim.plunkettI had the right idea but the wrong fix. Also restoring the @todo for the revert link.
Comment #54
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedShould 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.
Super nit, but we call it "Layout Builder" and "layout builder". Also that second comment should probably s/pages/overrides/?.
Seems like we should just delete this method if we're accessing the property directly?
Wow, this got way cleaner. Nice.
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.
Comment #55
tim.plunkett#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.
Comment #56
omrmankar+1
Comment #58
tim.plunkett#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.
Comment #59
Kristen PolThe interdiff looks fine. I'll take it for a quick spin.
Comment #60
tedbowunused use. shouldn't need any changes to this file now
Should we have LayoutEntityFormTrait?
actions()
is almost the samegetSectionStorage() is the same
buildEntity() could be the same if there was protect field for
contextKey
save() has chunk the same
Should this actually be DefaultsSectionStorageInterface since the entity forms is made to work with defaults not generic storage?
Should this actually be OverridesSectionStorageInterface since the entity forms is made to work with overrides not generic storage?
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.
Why are adding this?
Comment #61
Kristen PolFYI: 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.
Comment #62
tim.plunkett#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)
Comment #63
xjmGiven 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.
Comment #64
xjmWhat happened to the comment regression test from back in #11?
Also, those needed screenshots would be helpful for reviewing this. Thanks!
Comment #65
tim.plunkettRebuttal 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.
Comment #66
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedManually 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
: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:
Comment #67
tim.plunkettWhat about abandoning the Widget approach and just printing the LB UI directly in the form?
The rest of the changes would stay the same.
Comment #68
tim.plunkettI retested the patch and it's failing, which is a relief.
Here's a patch for #66 while I look into #67 more.
Comment #71
tim.plunkettOn 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.
Comment #72
phenaproximaI think this makes a lot of sense. Only found some random nitpicks.
Shouldn't $display be LayoutEntityDisplayInterface?
Should this be @internal?
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. :)
Can we use $this->setEntity() here?
Nit: We should probably use $this->entityTypeManager->getStorage()->load() here.
"Serving"?
Should be marked explicitly @internal?
This should probably use $this->entityTypeManager->getStorage()->create().
Let's use $this->setEntity().
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."
Should this be @internal too?
Comment #73
tim.plunkett1) 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:
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.
Comment #74
phenaproximaThe changes look excellent. One teensy thing I found in the interdiff, tho:
That slash probably shouldn't be there. :)
Comment #75
tim.plunkettHah, I'm also missing a `return `.
Comment #79
phenaproximaIt's Christmas-ey! RTBC for me once framework managers sign off.
Comment #80
larowlanNice, this is a lot better UX than the local tasks were.
$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.
Comment #81
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThat is addressed in #65.
Comment #82
larowlanNevermind, @Sam152 pointed me to #65
Comment #83
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedManual testing looks good. Adding screenshots to the IS as requested.
Comment #84
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #85
phenaproximaI think the new hook will probably need a change record. Tagging for that.
Comment #86
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI have drafted a CR here: https://www.drupal.org/node/3032274
Comment #88
phenaproximaLooks good to me!
Comment #89
xjmI know the visual changes aren't specifically the point of the issue, but they're also a UX improvement for several reasons.
Comment #90
larowlanCrediting @mark_fullmer and @phenaproxima for reviews that shaped the patch
Crediting @Sam152 for the issue summary and manual testing screenshots
Comment #91
larowlanCommitted a9a5382 and pushed to 8.7.x. Thanks!
Published change record
Comment #93
micail CreditAttribution: micail commentedAfter 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.
Comment #94
tim.plunkettCan you open a new issue with full steps to reproduce, and link it here? Thanks!
Comment #96
godotislate CreditAttribution: godotislate commented@tim.plunkett
I was able to reproduce #93 and created an issue here: https://www.drupal.org/project/drupal/issues/3046667
Comment #97
wombatbuddy CreditAttribution: wombatbuddy commentedI 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.
Comment #98
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented@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.
Comment #99
wombatbuddy CreditAttribution: wombatbuddy commented@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.
Comment #100
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented@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.