Layouts and Layout Builder
Pinto Layout exists to easily create Drupal Layouts, as in Layout Discovery (layout_discovery.module), as used in Layout Builder, etc.
Pinto Layout supports defining Layouts in multiple ways. The method you choose will depend on how you want your Components to be designed, and thusly is a preference than a rule.
Because both Pinto and Pinto Layout are so flexible with how components are set up, there are an arguably overwhelming amount of options. You'll find modified lines-of-code to be quite small however.
Site configuration
Most setups require the following to be added to site settings.php:
$settings['twig_sandbox_allowed_classes'] = [
\Drupal\Core\Template\Attribute::class,
\Drupal\pinto_layout\PintoLayout\Data\RegionAttributes::class,
];Factory methodologies
- Constructor as factory
- Use regular class constructors (
__construct) as the factory.
- Use regular class constructors (
- Component implements
PintoLayoutInterface- Component implements
\Drupal\pinto_layout\PintoLayout\PintoLayoutInterface. - The component can use any factory method, but implement
PintoLayoutInterfaceand its requiredcreateForLayoutmethod. This method is responsible for creating an instance of the component, and transferring region values into the component. - Regions are defined on the class itself with
#[Regions]attribute.
- Component implements
- Defined factory method
- Component uses
#[LayoutDefinition]withfactoryMethodabove the class: - Just like the
PintoLayoutInterfacemethod above, but with a method reference instead of an interface.
- Component uses
- Externally Mapped
- A component can be designated as a Layout without adding any Drupal or Layout concepts by setting up mappings externally.
- A service is added, the service implements
\Drupal\pinto_layout\PintoLayout\External\ExternallyDefinedInterface ExternallyDefinedInterface::getDefinitions()generates mappings. These mappings include references to the component, and region definitions, among other things.- A static factory method is defined anywhere, which produces a component. A mapping of region values is provided, similar to
PintoLayoutInterface::createForLayoutin the previous method.
Choose the first option if you use a public constructor with a limited set of slots, otherwise choose the second or third option if you don't mind mixing Layout concepts in with your component. Choose the fourth option to keep components free of Layout concepts.
If the component is not a Slots component type, only Externally Mapped may be used.
Constructor as factory
Setup and configure Pinto components as usual. Add regions as parameters, and add the #[Drupal\pinto_layout\Attribute\Region] attribute adjacent to the regions.
TIP
All non-region arguments of the constructor must have a default value.
Properties typed with \Drupal\pinto_layout\PintoLayout\Data\RegionAttributes will be supplied with attributes used for the layout container, and wrappers for each region. These attributes are necessary to work with the Layout Builder editor, but are otherwise unused on render or outside of Layout Builder.
final class Obj {
use DrupalInvokableSlotsTrait;
public function __construct(
#[Region]
public readonly mixed $region1,
#[Region]
public readonly mixed $region2,
public RegionAttributes $regionAttributes,
) {
}
}Associated Twig template:
<div {{ regionAttributes.containerAttributes() }}>
<div {{ regionAttributes.regionAttributes('region1') }}>
{{ region1 }}
</div>
<div {{ regionAttributes.regionAttributes('region2') }}>
{{ region2 }}
</div>
</div>Component implements PintoLayoutInterface
Setup and configure Pinto components as usual.
Modify the component to implement the interface, and define regions above the class with \Drupal\pinto_layout\Attribute\Regions attribute.
#[Regions([
'region1',
'region2',
])]
final class Obj implements PintoLayoutInterface {
use DrupalInvokableSlotsTrait;
public function __construct(
public readonly mixed $region1,
public readonly mixed $region2,
public RegionAttributes $regionAttributes,
) {
}
public static function createForLayout(LayoutData $layoutData): static {
return new static(
region1: $layoutData->regionsData->getRegion('slot1'),
region2: $layoutData->regionsData->getRegion('slot2'),
regionAttributes: $layoutData->regionAttributes,
);
}
}Associated Twig template:
<div {{ regionAttributes.containerAttributes() }}>
<div {{ regionAttributes.regionAttributes('region1') }}>
{{ region1 }}
</div>
<div {{ regionAttributes.regionAttributes('region2') }}>
{{ region2 }}
</div>
</div>Defined factory method
Setup and configure Pinto components as usual.
Implement a public static method, which takes a LayoutData parameter or has parameters with the same names as the regions.
Add #[LayoutDefinition] attribute above the class.
Configure #[LayoutDefinition($factoryMethod) to refer to the method.
Regions can be defined with #[Region] property, or #[LayoutDefinition($regions)].
#[LayoutDefinition(
factoryMethod: 'customFactory',
)]
final class Obj {
use DrupalInvokableSlotsTrait;
public function __construct(
#[Region]
public readonly mixed $region1,
#[Region]
public readonly mixed $region2,
public RegionAttributes $regionAttributes,
) {
}
public static function customFactory(LayoutData $layoutData): static {
return new static(
region1: $layoutData->regionsData->getRegion('slot1'),
region2: $layoutData->regionsData->getRegion('slot2'),
regionAttributes: $layoutData->regionAttributes,
);
}
}Associated Twig template:
<div {{ regionAttributes.containerAttributes() }}>
<div {{ regionAttributes.regionAttributes('region1') }}>
{{ region1 }}
</div>
<div {{ regionAttributes.regionAttributes('region2') }}>
{{ region2 }}
</div>
</div>Alternative with parameters
public static function customFactory(
mixed $slot1,
mixed $slot2,
RegionAttributes $regionAttributes,
): static {
return new static(
region1: $slot1,
region2: $slot2,
regionAttributes: $regionAttributes,
);
}Externally Mapped
Setup and configure Pinto components as usual.
No Layout-specific modifications are required.
final class Obj {
use DrupalInvokableSlotsTrait;
public function __construct(
public readonly mixed $region1,
public readonly mixed $region2,
public RegionAttributes $regionAttributes,
) {
}
}Associated Twig template:
<div {{ regionAttributes.containerAttributes() }}>
<div {{ regionAttributes.regionAttributes('region1') }}>
{{ region1 }}
</div>
<div {{ regionAttributes.regionAttributes('region2') }}>
{{ region2 }}
</div>
</div>Definitions service
namespace Drupal\my_module;
final class PintoLayoutDefinitions implements ExternallyDefinedInterface {
public function getDefinitions(): iterable {
yield ExternallyDefined::create(
id: 'layout_id_for_component',
label: new TranslatableMarkup('Layout for component'),
pintoEnum: PintoList::Obj,
regions: [
'region1',
'region2',
],
factoryMethod: [static::class, 'createLayoutObject'],
);
}
public static function createLayoutObject(LayoutData $layoutData): Obj {
return new Obj(
region1: $layoutData->regionsData->getRegion('region1'),
region2: $layoutData->regionsData->getRegion('region2'),
regionAttributes: $layoutData->regionAttributes,
);
}
}The factory method can be located anywhere. In this example it is colocated in the definition service for simplicity.
ExternallyDefined::create($pintoEnum) is a reference to the enum case, as in Lists.
ExternallyDefined::create($regions) can use the Regions type to customise region ID and labels independently.
Service setup
Set up class as a service. Critically, autoconfigure is set on the service:
services:
_defaults:
autoconfigure: true
autowire: true
public: false
Drupal\my_module\PintoLayoutDefinitions:
public: trueReplicating Region Attributes from Core
The preferred way to use RegionAttributes is as a single all-in-one variable. But in some cases you may need to match the same $region_attributes Twig variable design as Cores' Layout Discovery.
The first example is modified accordingly:
final class Obj {
use DrupalInvokableSlotsTrait;
public function __construct(
#[Region]
public readonly mixed $region1,
#[Region]
public readonly mixed $region2,
public RegionAttributes $region_attributes,
public array $attributes = [],
) {
}
protected function build(Slots\Build $build): Slots\Build {
return $build
->set('attributes', $this->regionAttributes->containerAttributes())
->set('region_attributes', $this->regionAttributes->regionsAsArray());
}
}Associated Twig template:
<div {{ attributes }}>
<div {{ region_attributes.region1 }}>
{{ region1 }}
</div>
<div {{ region_attributes.region2 }}>
{{ region2 }}
</div>
</div>Customising Layout ID
Layout ID's are automatically generated, but you may want to customise them as these ID's are stored and serialized in content.
#[LayoutDefinition(
id: 'a_layout',
)]
final class Obj {For externally defined layouts, this option is available at ExternallyDefined::create($id).
Customising Layout Label
Layout labels are automatically generated by default, but you may want to customise them. These labels are shown when choosing a layout in Layout Builder, among other places.
#[LayoutDefinition(
label: new TranslatableMarkup('Custom Layout Label'),
)]
final class Obj {For externally defined layouts, this option is available at ExternallyDefined::create($label).