Problem/Motivation

At this moment, components inside a section are rendered in order of adding.
The weight and region is ignored.

For usecases where you want e.g. an offset calculated between multiple components, it is required that these components are rendered in the correct order.

Steps to reproduce

Create 2 items in a section
- place the last created item first
- put a breakpoint in the item render. The first created component will be rendered first despite its position

Proposed resolution

sort by region and weight in the toRenderArray

Remaining tasks

Write patch and tests

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

Issue fork drupal-3226285

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

StryKaizer created an issue. See original summary.

strykaizer’s picture

Status: Active » Needs review
StatusFileSize
new1.01 KB

Patch without test added.

strykaizer’s picture

Title: Layout builder components should be rendered in order of sections and component weight » Components inside a section should be rendered in order of region and component weight
tim.plunkett’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests, +Blocks-Layouts

Interesting. So the resulting markup is correct, but the order of execution is what is causing problems for you?

ankithashetty’s picture

StatusFileSize
new1.01 KB
new820 bytes

Just fixed the custom command failure errors in #2. Retaining status as "Needs work" for tests, thanks!

tim.plunkett’s picture

Status: Needs work » Needs review

Switching to an MR and fixing the broken test. Still needs dedicated test coverage. NR to kick off test bot

strykaizer’s picture

Interesting. So the resulting markup is correct, but the order of execution is what is causing problems for you?

Correct, our usecase is a news magazine frontpage where content managers can place a "news listing" block in different positions.
We need those "news listing" blocks rendered in the correct order to keep track of the offset for the next "news_listing" block.

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.

kristen pol’s picture

Tagging for manual testing on 9.4.

Patch applies cleanly for Drupal 9.3, 9.4, and 10.

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.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

This issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.

For the tests requested in #4

Did not test.

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.

kevinquillen’s picture

StatusFileSize
new1.96 MB

Perusing the issue queue, I think this is the right issue for this. I also noticed that the 'storage' of the data is stored 'out' of order, as OP reports. It is stored by time of adding. If you edit, items never seem to move, only their region and weight notation.

Here is an example.

I edited a node layout and changed the order of these blocks (field blocks). I would expect that they are stored in the database in the order that they are set. Only the field data has them out of order; I moved a block from the 'second' region to the 'fourth' region, and changed the order of blocks in the third region.

The data (pardon the verbosity, it is serialized):

   ["00488840-db50-4afe-9c30-a123e6707fa9"]=>
    object(__PHP_Incomplete_Class)#7 (6) {
      ["__PHP_Incomplete_Class_Name"]=>
      string(38) "Drupal\layout_builder\SectionComponent"
      ["uuid":protected]=>
      string(36) "00488840-db50-4afe-9c30-a123e6707fa9"
      ["region":protected]=>
      string(6) "fourth"
      ["configuration":protected]=>
      array(4) {
        ["id"]=>
        string(46) "field_block:node:recipe:field_preparation_time"
        ["label_display"]=>
        string(1) "0"
        ["context_mapping"]=>
        array(1) {
          ["entity"]=>
          string(21) "layout_builder.entity"
        }
        ["formatter"]=>
        array(4) {
          ["type"]=>
          string(14) "number_integer"
          ["label"]=>
          string(6) "inline"
          ["settings"]=>
          array(2) {
            ["thousand_separator"]=>
            string(0) ""
            ["prefix_suffix"]=>
            bool(true)
          }
          ["third_party_settings"]=>
          array(0) {
          }
        }
      }
      ["weight":protected]=>
      int(0)
      ["additional":protected]=>
      array(0) {
      }
    }
    ["df8bfafc-210c-4d86-9745-e47081ab0fd4"]=>
    object(__PHP_Incomplete_Class)#8 (6) {
      ["__PHP_Incomplete_Class_Name"]=>
      string(38) "Drupal\layout_builder\SectionComponent"
      ["uuid":protected]=>
      string(36) "df8bfafc-210c-4d86-9745-e47081ab0fd4"
      ["region":protected]=>
      string(5) "third"
      ["configuration":protected]=>
      array(4) {
        ["id"]=>
        string(40) "field_block:node:recipe:field_difficulty"
        ["label_display"]=>
        string(1) "0"
        ["context_mapping"]=>
        array(1) {
          ["entity"]=>
          string(21) "layout_builder.entity"
        }
        ["formatter"]=>
        array(4) {
          ["type"]=>
          string(12) "list_default"
          ["label"]=>
          string(6) "inline"
          ["settings"]=>
          array(0) {
          }
          ["third_party_settings"]=>
          array(0) {
          }
        }
      }
      ["weight":protected]=>
      int(1)
      ["additional":protected]=>
      array(0) {
      }
    }
    ["f91febc6-d924-47a2-8ffd-b71d3b2597c7"]=>
    object(__PHP_Incomplete_Class)#9 (6) {
      ["__PHP_Incomplete_Class_Name"]=>
      string(38) "Drupal\layout_builder\SectionComponent"
      ["uuid":protected]=>
      string(36) "f91febc6-d924-47a2-8ffd-b71d3b2597c7"
      ["region":protected]=>
      string(5) "third"
      ["configuration":protected]=>
      array(4) {
        ["id"]=>
        string(42) "field_block:node:recipe:field_cooking_time"
        ["label_display"]=>
        string(1) "0"
        ["context_mapping"]=>
        array(1) {
          ["entity"]=>
          string(21) "layout_builder.entity"
        }
        ["formatter"]=>
        array(4) {
          ["type"]=>
          string(14) "number_integer"
          ["label"]=>
          string(6) "inline"
          ["settings"]=>
          array(2) {
            ["thousand_separator"]=>
            string(0) ""
            ["prefix_suffix"]=>
            bool(true)
          }
          ["third_party_settings"]=>
          array(0) {
          }
        }
      }
      ["weight":protected]=>
      int(0)
      ["additional":protected]=>
      array(0) {
      }
    }

I would expect that everything is stored in the order it is in the form.

To that end, correcting just the render array is not enough. The output from serialization should also be correct so that anyone using JSON:API for example gets the data in the correct order and does not have to waste frontend resources trying to figure it out.

Related: #2942975: [PP-1] Expose Layout Builder data to REST and JSON:API

While working with that issue and patch, I had to add this sorting code to a JSON Include parser:

  protected function sortSectionComponents(array &$section_data = []): array {
    foreach ($section_data as &$section_components) {
      /** @var \Drupal\Core\Layout\LayoutDefinition $layout */
      $layout = $this->layoutPluginManager->getDefinition($section_components['layout_id']);

      if (count($layout->getRegions()) > 1) {
        // We can only assume the defined region list is the 'order'.
        $regions = array_flip(array_keys($layout->getRegions()));

        // Sort the regions
        usort($section_components['components'], function($a, $b) use ($regions) {
          return $regions[$a['region']] <=> $regions[$b['region']];
        });

        // Now sort within the regions
        usort($section_components['components'], function($a, $b) use ($regions) {
          if ($regions[$a['region']] === $regions[$b['region']]) {
            return $a['weight'] <=> $b['weight'];
          }

          // What can we do with blocks that have the same weight value in one region? Can that happen?
          return 0;
        });
      } else {
        usort($section_components['components'], fn($a, $b) => $a['weight'] <=> $b['weight']);
      }
    }

    return $section_data;
  }

Where $section_data is just one sections worth of components. Ugly, but as far as I could tell it put the data in the order that someone had saved Layout Builder components for JSON:API.

I would posit that core itself should ensure the storage of the data is accurate and not leave it up to themers or API consumers to figure it out themselves. To that end, it seems like it would improve overhead performance by not needing to re-sort items for forms, render, or APIs because the data is stored as it was entered.

Also, I had to assume that the regions are ordered (weighted) by how they are defined in the layout YAML. I am not sure if that would always be true, I do not know how people are constructing their own YAML layout definitions.

So, when you get the data from Drupal regardless of the method, it should come like:

sections
--layout
----regions (ordered)
------components (ordered)
--layout
----regions (ordered)
------components (ordered)

etc

dennisdk’s picture

Patch #5 works great on 10.3.6!

The order that the blocks are put in, is now also the order of that they are rendered!

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.