Problem/Motivation

Currently layout_builder is very limited when it comes to more complex layouts. The issue is lack of nesting or wrapping ability. See:

I believe that all use cases bringing people towards these issues could be solved if we would simply allow people to create layouts with configurable regions. These could be a "second class citizens", yet would allow devs or contrib modules to develop custom complex rules of layout building, and store them in layouts configurations.

Proposed resolution

Introducing a new DynamicRegionsProviderInterface that could be implemented by Layout classes. That would allow layout to provide additional regions if needed. Thanks to that, we could work around any limits currently set by the layout_builder, by delegating dynamic construction of more complex layout structures to the Layout class itself.

Remaining tasks

  • Patch review
  • Discuss and agree on the solution

API changes

Addition of a new optional interface for layout classes. This change is completely backward compatible.

Issue fork drupal-3075939

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

Luke_Nuke created an issue. See original summary.

luke_nuke’s picture

Status: Active » Needs review
StatusFileSize
new4.89 KB

Status: Needs review » Needs work

The last submitted patch, 2: 3075939-implement_dynamic_layout_regions.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

luke_nuke’s picture

Attempt at fixing a patch.

luke_nuke’s picture

Status: Needs work » Needs review
luke_nuke’s picture

We could also have an additional, optional interface for a layout e.g.: DynamicRegionsProviderInterface with a getDynamicRegions definition. That way we would be completely backward compatible, but i'm not sure if it's not an overkill, what do you think?

luke_nuke’s picture

I created a new patch with this different interface approach, and a test.

effulgentsia’s picture

Interesting idea.

would allow devs or contrib modules to develop custom complex rules of layout building, and store them in layouts configurations

A proof of concept of this would be helpful. For example, how would you build on #8 to solve #3053145: Allow layout builder sections to be nested.?

luke_nuke’s picture

Thanks for taking an interest effulgentsia.

1. Create your custom layout implementing DynamicRegionsProviderInterface .
2. Implement a configuration form, that allow to choose and add a "sublayout", sublayouts can be configurable and their list can be draggable, so you can reorder them.
3. List of available sublayout types can be hardcoded in your custom layout's class or pulled from somewhere else. Every sublayout type should have a set of regions.
4. On form submit of your main Layout implementing DynamicRegionsProviderInterface create and save proper configuration structure, with unique names (e.g. starting from uuid) for each region, for each sublayout.
5. Now your getDynamicRegions method will simply extract list of regions from each sublayout from your configuration and return a flat list of unique regions.
7. In your build() method or layout's template do all the magic that is left to do. Which is making the layout renderable out of sublayout's custom themes with injected regions.

It works, I'm already doing it.

luke_nuke’s picture

And if you want to have sublayouts nested even deeper? It entirely depends on your form-fu.

luke_nuke’s picture

So in other words.
1. Store unique (e.g. prefixed with a random uuid) region names in configuration of any structure that you like.
2. Return flat array of all regions using getDynamicRegions method.
3. Extract region from the configuration and inject them in the proper templates associated with your "sublayouts" or whatever substructures you decided to create.

Contrib module could make this process easier. To be honest, it's kind of possible even without this patch - you could modify plugin definition in your layout's constructor, to add new regions to it. But it's hacky as hell. What I try to do is to make dynamic regions officially recognized as a thing, to make them always work and have tests that would assure it.

effulgentsia’s picture

It works, I'm already doing it.

Are you able to share that code? If not, that's a shame but understandable. But if you are, it would be helpful in reviewing #8 to see #10 in action.

luke_nuke’s picture

I just created a sandbox project that is a proof of concept of the #10 . It just provides user with a configurable section, made of subsections/sublayouts. It's pretty basic.

https://www.drupal.org/sandbox/luken/3077035

It came to my mind that another perspective that can be used to explain this approach is that it's about delegating responsibility for more dynamic region structures to contrib/custom modules, which is the simplest and least invasive solution to the immediate problems presented by the Layout Builder currently.

vinodhini.e’s picture

StatusFileSize
new249.65 KB

#8 patch does not apply

luke_nuke’s picture

Re-roll.

Status: Needs review » Needs work

The last submitted patch, 16: implement_dynamic_layout_regions-3075939-16.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

luke_nuke’s picture

Status: Needs work » Needs review
StatusFileSize
new10.53 KB

Oops, re-roll.

joncjordan’s picture

So far this seems like a very promising approach. I've tested the patch in #18 and it says it didn't apply (when I try to apply patch via composer), however I see the changes are being applied. Here's the output from my the patch failure:


patching file lib/Drupal/Core/Layout/DynamicRegionsProviderInterface.php

patching file lib/Drupal/Core/Layout/LayoutDefault.php
Hunk #1 succeeded at 31 (offset -2 lines).
patching file lib/Drupal/Core/Layout/LayoutDefinition.php

patching file modules/layout_builder/src/Element/LayoutBuilder.php

Hunk #2 succeeded at 248 (offset -3 lines).

Hunk #3 FAILED at 284.

1 out of 3 hunks FAILED
 -- saving rejects to file modules/layout_builder/src/Element/LayoutBuilder.php.rej


patching file modules/layout_builder/src/Form/MoveBlockForm.php

Hunk #2 FAILED at 127.

1 out of 2 hunks FAILED -- saving rejects to file modules/layout_builder/src/Form/MoveBlockForm.php.rej


patching file modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestDynamicRegionsPlugin.php

patching file modules/layout_builder/tests/modules/layout_builder_test/templates/layout-builder-test-dynamic-regions-section.html.twig

patching file modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php

Hunk #1 succeeded at 948 (offset -48 lines).

patch '-p4' --no-backup-if-mismatch -d 'web/core' < '/var/folders/4v/z5dd6lyj6r397nfwm_qwtxqc0000gn/T/5d6850ea455fb.patch'
Executing command (CWD): patch '-p4' --no-backup-if-mismatch -d 'web/core' < '/var/folders/4v/z5dd6lyj6r397nfwm_qwtxqc0000gn/T/5d6850ea455fb.patch'
patching file Core/Layout/DynamicRegionsProviderInterface.php

can't find file to patch at input line 32
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|diff --git a/core/lib/Drupal/Core/Layout/LayoutDefault.php b/core/lib/Drupal/Core/Layout/LayoutDefault.php
|index 175844fe1e..296aac6cb9 100644
|--- a/core/lib/Drupal/Core/Layout/LayoutDefault.php
|+++ b/core/lib/Drupal/Core/Layout/LayoutDefault.php
--------------------------
File to patch: 
Skip this patch? [y] 
Skipping patch.
1 out of 1 hunk ignored

can't find file to patch at input line 51
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|diff --git a/core/lib/Drupal/Core/Layout/LayoutDefinition.php b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
|index c87b618d11..de20c3c212 100644
|--- a/core/lib/Drupal/Core/Layout/LayoutDefinition.php
|+++ b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
--------------------------
File to patch: 
Skip this patch? [y] 
Skipping patch.

2 out of 2 hunks ignored

can't find file to patch at input line 85
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------

|diff --git a/core/modules/layout_builder/src/Element/LayoutBuilder.php b/core/modules/layout_builder/src/Element/LayoutBuilder.php
|index 80dcff90f7..8388352014 100644
|--- a/core/modules/layout_builder/src/Element/LayoutBuilder.php
|+++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
--------------------------
File to patch: 
Skip this patch? [y] 
Skipping patch.

3 out of 3 hunks ignored

can't find file to patch at input line 121
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|diff --git a/core/modules/layout_builder/src/Form/MoveBlockForm.php b/core/modules/layout_builder/src/Form/MoveBlockForm.php
|index b3fd9dcbf8..da2543baad 100644
|--- a/core/modules/layout_builder/src/Form/MoveBlockForm.php
|+++ b/core/modules/layout_builder/src/Form/MoveBlockForm.php
--------------------------
File to patch: 
Skip this patch? [y] 
Skipping patch.

2 out of 2 hunks ignored

patching file tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestDynamicRegionsPlugin.php

patching file tests/modules/layout_builder_test/templates/layout-builder-test-dynamic-regions-section.html.twig

can't find file to patch at input line 206
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------

|diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
|index c5efc3110d..e08faf1db8 100644
|--- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
|+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
--------------------------
File to patch: 
Skip this patch? [y] 
Skipping patch.

1 out of 1 hunk ignored

   Could not apply patch! Skipping. The error was: Cannot apply patch https://www.drupal.org/files/issues/2019-08-27/implement_dynamic_layout_regions-3075939-18.patch

I do have it working in a test environment, and implemented the functionality from your sandbox module. Everything seems to be working, not sure why the patch is failing.

Thanks for your work Luke_Neke.

luke_nuke’s picture

Hi, joncjordan.
1. Are you sure you have checked out the newest 8.8.x branch?
2. Do any other core patches apply without issues?
3. Try to remove the core directory and re-run composer install.

I just re-tested it and it should apply without issues, so it look like a local issue.

joncjordan’s picture

No I was running against latest core stable release 8.7.6 - Aug 07 2019. You're probably right about it being a local issue. Either way, the patch DOES seem to apply as the files have the changes and the new files are where they need to be.

I've tested the functionality and it works great. It seems to be a promising approach to nested subsections and something the will become a realized need as more and more people start using layouts. Sure you can build endless layouts ahead of time, but sometimes you don't know what layout you need until you're actually building the page. This offers site builders that flexibility.

We'd likely need some method in our custom layout class for subsection removals. I wonder how you might handle that. Overall great work so far.

luke_nuke’s picture

Oh, this patch was made for 8.8.x branch, that's why you got such big offsets. I'm glad it applied though. If this feature would be agreed on, I think it could be backported to 8.7.x branch, as it's completely BC in its current form.

We'd likely need some method in our custom layout class for subsection removals. I wonder how you might handle that. Overall great work so far.

Removal from the forms perspective isn't difficult, although you also have blocks assigned to these dynamic regions so in theory you would need to access node context from your Layout class and modify it on submit, to remove these blocks, or at the very least you could require their removal during the form validation, also by accessing node context. Again, it can be handled.

I see there are also few coding standards issues in the last patch, I will fix them soon.

Added:
Actually I think there would be two contexts that you would have to deal with: the one for display - when we are talking about non per-node layouts, and the node one if we are talking about per-node layouts.

luke_nuke’s picture

Fixed coding standards issues.

luke_nuke’s picture

Issue summary: View changes
luke_nuke’s picture

Issue summary: View changes
luke_nuke’s picture

Issue summary: View changes

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

tim.plunkett’s picture

Issue tags: +Blocks-Layouts

Still needs a review. Just tagging.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

tim.plunkett’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Layout/LayoutDefault.php
    @@ -33,7 +33,13 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
    +    $regionNames = $this->getPluginDefinition()->getRegionNames();
    +    if ($this instanceof DynamicRegionsProviderInterface) {
    +      $regionNames = array_merge(array_keys($this->getDynamicRegions()), $regionNames);
    +    }
    

    This is the crux of the patch, but still doesn't feel right to me.

    Consider that this is changing only one of the 8 places that getRegionNames() is called in core.

    One other idea would be to add a getRegionNames() method to LayoutInterface, and have LayoutDefault delegate to the plugin, allowing classes to provide their own implementation. And then document on the plugin's method that it is only intended for internal use...

  2. +++ b/core/lib/Drupal/Core/Layout/LayoutDefault.php
    @@ -33,7 +33,13 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
    +    foreach ($regionNames as $region_name) {
    

    Nit: should be $region_names

  3. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -250,8 +251,12 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
    +    if ($layout instanceof DynamicRegionsProviderInterface) {
    +      $regions = array_merge($layout->getDynamicRegions(), $regions);
    +    }
    

    Similar problem to the above

  4. +++ b/core/modules/layout_builder/src/Form/MoveBlockForm.php
    @@ -126,7 +127,13 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
    +      $regions = $layout_definition->getRegions();
    +      if ($layout instanceof DynamicRegionsProviderInterface) {
    +        $regions = array_merge($layout->getDynamicRegions(), $regions);
    +      }
    

    And again

  5. +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestDynamicRegionsPlugin.php
    @@ -0,0 +1,38 @@
    + *   template = "layout-builder-test-dynamic-regions-section"
    

    Nit: include trailing comma

  6. +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/templates/layout-builder-test-dynamic-regions-section.html.twig
    @@ -0,0 +1,9 @@
    +    {% if content.dynamicregion %}
    +      <div {{ region_attributes.dynamicregion }}>
    +        {{ content.dynamicregion }}
    +      </div>
    

    Ideally this would actually be dynamic, and not need to be hardcoded. Compare to layout.html.twig which uses getRegionNames dynamically right now.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

JLeMosy made their first commit to this issue’s fork.

tim.plunkett’s picture

Nothing in the issue fork...

mradcliffe’s picture

I performed Novice Triage on this issue. I added the Easy out of the Box, NorthAmerica2021, Novice and Needs issue summary update tags.

I think the issue summary could use an update based on some pending questions brought up in Tim's review. Additionally the patch probably needs to be re-rolled at this point and we can fix the issues in the code review.

After that, start a thread in the #easy-out-of-the-box Slack channel to ask for help / discuss Tim's 1st point in the review in #30.

suresh prabhu parkala’s picture

Status: Needs work » Needs review
StatusFileSize
new10.52 KB

Just a re-roll against 9.2.x.

mradcliffe’s picture

Looks like we need to add "dynamicregions" to the cspell dictionary following instructions in this change record: https://www.drupal.org/node/3122084

mradcliffe’s picture

Status: Needs review » Needs work
Issue tags: -Needs reroll

Back to Needs work, removed needs reroll.

bitmap0100’s picture

I'm going to apply the patch and make the changes, test, then upload a new patch.

bitmap0100’s picture

I added dynamicregions to the cspell dictionary and recombined that with the latest patch I hope I did this correctly.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

larowlan’s picture

I was asked to look at this by James LeMosy.

I'm not sure this is needed, there's already an api in core for dynamic regions, LayoutDefinition::getRegions can be subclassed.

Here's a video of it in use.

https://drupal.slack.com/archives/C0T77ULN9/p1598562327038800

And here's a contrib module that does something similar: https://www.drupal.org/project/lb_tabs

So I think this can be closed works as designed.

jlemosy’s picture

Thanks, larowlan. So, if it is already part of core, what needs to be done to make it functional? It seems like progress on this has stalled, but it is still a frequently-requested feature. I've tried my hand at picking up where this issue left off, but I have had a hard time understanding how to use the API.

larowlan’s picture

I guess the next step is to convey what part of it isn't functional. The links above show it is in use already, so it would be good to understand where the gaps are

jlemosy’s picture

Sorry, "make it functional" was not exactly what I meant. I am just unclear how to use the API to achieve the original goal of this issue, which is to be able to add multiple layouts within sections.

larowlan’s picture

Ok, so I think perhaps we need more docs and examples

I'll try and find some time for that next week

jlemosy’s picture

Thanks, that would be much appreciated!

larowlan’s picture

I wrote a blog-post about achieving dynamic tabs

That's the code that corresponds with this video

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

dalemoore’s picture

Hi @larowlan, the video you've linked to twice on here isn't viewable. It looks like you have to be part of the Drupal Association Slack? Can you repost the video so we can see how your dynamic tabs works?

larowlan’s picture

Sure

larowlan’s picture

Also, I recommend joining the slack, the community is a huge part of the power of Drupal

dalemoore’s picture

Thanks, that's helpful to see how it worked.

I couldn't get signing up for Drupal Slack to work before (though I'm a new Slack user). It required either a Google or Apple account or an @association.drupal.org email. The "Continue with Google" method wouldn't work. But I found the link under "Joining" here and was able to create an account on there. Thanks.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

wim leers’s picture

AFAICT @larowlan showed in the blog post he created 3 years ago at https://www.previousnext.com.au/blog/creating-layout-plugin-dynamic-regions that this is already supported?

@Luke_Nuke: can you confirm that your use case can indeed be achieved without modifying Drupal core?

Can we close this issue? 🤞

dqd’s picture

Status: Needs work » Postponed (maintainer needs more info)

That would be great news and +1 for what @WimLeers sad: If the scope of this issue is already covered in core let's move on and close this. Would be great if @Nuke_Luke can confirm this. Apart from that I will go and test now to confirm. Other users who can confirm that is works already would be helpful too. Regarding our policies I set it to PMNMI for now(?)

danielveza’s picture

Status: Postponed (maintainer needs more info) » Closed (works as designed)

Solutions for how to achieve this have been posted in this video and it has been over three months since the last update where this was marked as Postponed (maintainer needs more info).

Therefore I'm marking this one as closed. Please feel free to reopen or open a follow up ticket if you think there is still work to be done here.

Thanks!