Skip to content

Latest commit

 

History

History
252 lines (193 loc) · 8.63 KB

File metadata and controls

252 lines (193 loc) · 8.63 KB

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:

interface SlotInterface
{
    public function getPrototypeName(): string;
}

A presentational component uses a slot like this:

prototype(Vendor.Site:Button) < prototype(PresentationObjectComponent) {
    @presentationObjectInterface = 'Vendor\\Site\\Presentation\\Block\\Button\\ButtonInterface'

    renderer = afx`
        <button type={props.model.type}>
            <PackageFactory.AtomicFusion.PresentationObjects:Slot model={presentationObject.label}/>
        </button>
    `
}
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:

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 SlotInterfaces.

In Factories, it can be initialized via static factory method, taking TraversableNodes as its first argument:

/* ... */
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:

/* ... */
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

Collections can also be created from arbitrary iterables:

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

/* ... */
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

/* ... */
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 <div> 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.