Layout Builder: Exposing content type default layout with jsonapi

By alexmoreno, 15 September, 2020

There is an on going issue for Layout Builder which aims to expose the overridden layout of a given node. However to expose the default layout if a node is not overriding the default layout is a bit trickier.

The issue can be found here: https://www.drupal.org/project/drupal/issues/2942975

And this is the test I did to prove this is working like a dream:

https://www.drupal.org/project/drupal/issues/2942975#comment-13752188

 

Content type default layout

Now, as I said, this works just fine. However this assumes the nodes you are displaying are overriding the layout. In other words, the content editor is creating a layout for his new node as well as the content.

However in some situations the content editor may just want to create the node, fill some fields and save. No changes on the layout itself.

Then you are in bad luck, because the patch itself is of no help for this. You'll need it to cover when the editor is overriding the layout, but not when they are not.

Layout Builder in a json response

Layout Builder in a json response

Why this happens

Now, in your particular case when the node is not overriding the layout, what happens is that the node does not contain any information about said layout, and hence it's returning that empty.

On the beginning I thought it could be a bug, but after digging and talking to different people, and if you think about it, because you are not saving any information of that layout on that particular node, Drupal has no information on the node itself about the layout, and hence, it is not able to find that field, or it finds that it's empty.

As I was saying that would not be a bug but more of an expected behaviour.

The solution

Now, there is always a solution to every problem. The issue with this problem is that you really need to be familiar with two things in Drupal 8 here.

One is, as you can expect, Layout Builder. The other one is the way Drupal manages the configuration.

Because Drupal now stores everything as a configuration, the data for the content type will be as well stored in there. That was the first clue to where you could find the missing information we are craving for.

What we are going to do is create an extra field, a Druapal computed field, which is going to store the json information that we need.

We'll create a .module file with the next content.

lb_default_layout.module

  1. <?php
  2. use Drupal\Core\Entity\EntityTypeInterface;
  3.  
  4. /**
  5.  * Implements hook_entity_base_field_info().
  6.  */
  7. function lb_default_layout_entity_base_field_info(EntityTypeInterface $entity_type) {
  8. if ($entity_type->id() === 'node') {
  9. $fields['layout_builder__default_layout'] = \Drupal\Core\Field\BaseFieldDefinition::create('map')
  10. ->setName('layout_builder__default_layout')
  11. ->setLabel(t('Default LB layout'))
  12. ->setTargetEntityTypeId('node')
  13. ->setComputed(TRUE)
  14. ->setClass('\Drupal\lb_default_layout\Plugin\Field\LayoutBuilderInformation');
  15. return $fields;
  16. }
  17. }

Drupal computed field

Next, we just need to use that field to collect that information.

As I was saying, that information is going to be available as Drupal configuration. Let's have a look.

src/Plugin/Field/LayoutBuilderInformation.php

  1. <?php
  2.  
  3. namespace Drupal\lb_default_layout\Plugin\Field;
  4.  
  5. use Drupal\Core\Field\FieldItemList;
  6. use Drupal\Core\TypedData\ComputedItemListTrait;
  7.  
  8. /**
  9.  * Defines a metatag list class for better normalization targeting.
  10.  */
  11. class LayoutBuilderInformation extends FieldItemList {
  12.  
  13. use ComputedItemListTrait;
  14.  
  15. protected $routeMatch;
  16.  
  17. /**
  18.   * {@inheritdoc}
  19.   */
  20. protected function computeValue() {
  21. $route_match = \Drupal::routeMatch();
  22. $path = $route_match->getRouteObject()->getPath();
  23.  
  24. // Loop through all layout and add default layout value.
  25. // layout_library prefix is optional. If omitted, all configuration object names that exist are returned.
  26. foreach (\Drupal::configFactory()->listAll("layout_library") as $config_item) {
  27. // We check if the current path has a suitable bundle from the layout library.
  28. // IE: [YOUR_URL]/jsonapi/node/landing_page would get the targetBundle landing_page
  29. if (strpos($path, \Drupal::configFactory()->get($config_item)->get('targetBundle')) !== false) {
  30. $this->list[0] = $this->createItem(0, \Drupal::configFactory()->get($config_item)->get('layout'));
  31. }
  32. }
  33.  
  34. }
  35. }

Layout library

This code is going to try to identify the path which has been triggered to fetch the json information. For example:

http://ddevblt.ddev.site:8000/jsonapi/node/landing_page?filter[drupal_internal__nid][value]=1

On this case we can see that the content type is landing_page.

The loop will iterate over the different layouts on the library, and fetch the one that is applying to said content type.

Note that you'll have to use a layout library instead of using the default layout for a given content type (see image as well): admin/structure/layouts

 

Layout library

Layout library

Share the love

I'll make sure I share the module on itself, so it's easier for anyone willing to use this to simply use composer and install it from drupal.org

As always, please feel free to share feedback, comments... if you have a better way of solving this I'd love to hear your opinion