< 1. PresentationObjects and Components    |    Index    |    4. Integration Recipes >
--- # 3. Slots **A working demo of slots can be visited at:** https://github.com/grebaldi/presentationobjects-slots-demo Slots are a way to tell the Fusion runtime what exact prototype to use to render a certain PresentationObject. This enables you to keep the Fusion footprint of your project limited to presentational components and entry points for content elements and handle the rest of the integration within PresentationObject factories. Slots are themelves PresentationObjects and are accompanied by the built-in presentational component `PackageFactory.AtomicFusion.PresentationObjects:Slot`. This article will show you how to use them. ## The SlotInterface The SlotInterface in PHP consists of only one method signature, namely `getPrototypeName`: ```php interface SlotInterface { public function getPrototypeName(): string; } ``` A presentational component uses a slot like this: ```fusion prototype(Vendor.Site:Button) < prototype(PresentationObjectComponent) { @presentationObjectInterface = 'Vendor\\Site\\Presentation\\Block\\Button\\ButtonInterface' renderer = afx` ` } ``` ```php final class Button implements ButtonInterface { /** * @param ButtonVariant $variant * @param ButtonType $type * @param HorizontalAlignment $horizontalAlignment * @param string $title * @param SlotInterface $label */ public function __construct( ButtonVariant $variant, ButtonType $type, HorizontalAlignment $horizontalAlignment, string $title, SlotInterface $label ) { $this->variant = $variant; $this->type = $type; $this->horizontalAlignment = $horizontalAlignment; $this->title = $title; $this->label = $label; } // ... } ``` In the example above, `Button` would accept any instance of a class that implements `SlotInterface` as its `$label`, allowing you to provide a variety of presentation objects in its place. All PresentationObjects generated by the kickstarter implement `SlotInterface` by default. For those objects `getPrototypeName` will point to the prototype that has been generated alongside the PresentationObject. ## Content, Collection, Editable, Value This package provides a set of built-in slot implementation that are designed to help with some common tasks. ### Content If you have the need to apply ContentElementWrapping to a nested PresentationObject in your PresentationObject factory code, `Content` will help you out. `Content` has a static factory method `fromNode` that takes a `TraversableNodeInterface` and a `contentPrototypeName` and redirects rendering inside Fusion to the latter: ```php Content::fromNode($node, $contentPrototypeName) ``` `$contentPrototypeName` is optional. By default, `$node->getNodeType()->getName()` is used, similar to `Neos.Neos:ContentCase`. The fusion prototype addressed by `$contentPrototypeName` can in theory be any known fusion prototype, but for most cases it is recommended for it to be a prototype extending `Neos.Neos:ContentComponent`. ### Collection `Collection` is a Wrapper for `Neos.Fusion:Loop` and simply joins an array of `SlotInterface`s. In Factories, it can be initialized via static factory method, taking `TraversableNodes` as its first argument: ```php /* ... */ use PackageFactory\AtomicFusion\PresentationObjects\Presentation\Slot\Collection; final class DeckFactory extends AbstractComponentPresentationObjectFactory { /* ... */ /** * @param TraversableNodeInterface $node * @return DeckInterface */ public function forDeckNode(TraversableNodeInterface $node): DeckInterface { // Optional: Use assertions to ensure the incoming node type assert($node->getNodeType()->isOfType('Vendor.Site:Content.Deck')); return new Deck( Collection::fromNodes($this->findCardNodes(), function (TraversableNodeInterface $cardNode, int $key, Iteration $iteration): SlotInterface { // ... }) ); } /* ... */ } ``` The second argument is optional. By default, rendering will be directed through `Content` using `$node->getNodeType()->getName()` as the rendering prototype name for each item. The above example could be shortened to: ```php /* ... */ use PackageFactory\AtomicFusion\PresentationObjects\Presentation\Slot\Collection; final class DeckFactory extends AbstractComponentPresentationObjectFactory { /* ... */ /** * @param TraversableNodeInterface $node * @return DeckInterface */ public function forDeckNode(TraversableNodeInterface $node): DeckInterface { // Optional: Use assertions to ensure the incoming node type assert($node->getNodeType()->isOfType('Vendor.Site:Content.Deck')); return new Deck( Collection::fromNodes($this->findCardNodes()) ); } /* ... */ } ``` The third argument is an `Iteration` object, containing the following information: `$iteration->getIndex(): int` - The 0-based index of the current item `$iteration->getCycle(): int` - The 1-based index of the current item `$iteration->getCount(): ?int` - The total number of items, given that the provided `iterable` is also countable `$iteration->isFirst(): bool` - Whether the current item is the first item `$iteration->isLast(): bool` - Whether the current item is the last item `$iteration->isOdd(): bool` - Whether the 1-based index of the item is odd `$iteration->isEven(): bool` - Whether the 1-based index of the item is even `Collection`s can also be created from arbitrary iterables: ```php Collection::fromIterable($listOfImages, function (Image $image, int $key, Iteration $iteration): SlotInterface { // ... }); ``` ### Value Value wraps any given php value into a `SlotInterface` and sees through that it is properly stringified - thus saving us the headache of the good ol' dreadful `Array to string conversion`-Exception. In Factories, it can be initialized via static factory method: *`EXAMPLE: PresentationObject Factory`* ```php /* ... */ use PackageFactory\AtomicFusion\PresentationObjects\Presentation\Slot\Value; final class ButtonFactory extends AbstractComponentPresentationObjectFactory { /** * @return ButtonInterface */ public function forSaveAction(): ButtonInterface { return new Button( Value::fromAny('Save') ); } /** * @return ButtonInterface */ public function forCancelAction(): ButtonInterface { return new Button( Value::fromAny('Cancel') ); } } ``` ### Editable In plain AtomicFusion we would use `Neos.Neos:Editable` to integrate a property that is supposed to be editable via CK Editor in the Neos UI. The analogous mechanism for PresentationObjects is the `Editable` slot implementation. `Editable` takes a `TraversableNodeInterface`, a `propertyName` and the flag `isBlock`. Internally, it actually redirects rendering to the `Neos.Neos:Editable` fusion prototype. In Factories, it can be initialized via static factory method: *`EXAMPLE: PresentationObject Factory`* ```php /* ... */ use PackageFactory\AtomicFusion\PresentationObjects\Presentation\Slot\Editable; final class TextFactory extends AbstractComponentPresentationObjectFactory { /** * @param TraversableNodeInterface $node * @return TextInterface */ public function forTextNode(TraversableNodeInterface $node): TextInterface { // Optional: Use assertions to ensure the incoming node type assert($node->getNodeType()->isOfType('Vendor.Site:Content.Text')); return new Text( Editable::fromNodeProperty($node, 'content', true) ); } } ``` The third argument is optional. If true, an additional `
` is wrapped around the property value (sometimes needed for a proper editing experience). By default, it's set to `true`, which matches the default of `Neos.Neos:Editable`. ---