Best Practice to wrap ContentCollectionItems with markup

Hi Erich

We do it with a set of configuration, fusion objects and an eel helper. Maybe this can be an inspiration for you. Just pick out the parts that fits your needs or the whole thing. I shortened it a bit because we use some fusion object in between that i just left out. So the code is not tested like it is, but the concept works more that 50 Neos websites already:

Our nodetypes fusion objects are all based on Neos.Neos:ContentComponent:

prototype(Vendor.Package:Content.Foo) < prototype(Neos.Neos:ContentComponent) {
  ...
  renderer = afx`...`
   # or you prefer fluid templates to render you nodes: renderer = Neos.Fusion:Template
}

We “enhanced” that prototype like this to let it wrap a div with class “container” around each content element:

prototype(Neos.Neos:ContentComponent) {
  renderer.@process.wrapContentElement = Neos.Fusion:Tag {
    tagName = 'div'
    attributes.class = 'container'
    content = ${value}

    @if.shallWrapContainer = ${Internezzo.Neos.ContentCollection.shallWrapContainer(node)}
  }
}  

With the eel-helper in the if-condition we control which elements are getting wrapped:

<?php
namespace Internezzo\Neos\Eel\Helper;

use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Eel\ProtectedContextAwareInterface;
use Neos\Flow\Annotations as Flow;

class ContentCollectionHelper implements ProtectedContextAwareInterface
{
    /**
     * Return true if the current node shall be wrapped with a container
     *
     * @param NodeInterface $node
     * @return string
     */
    public function shallWrapContainer(NodeInterface $node)
    {
        $contentCollection = (new FlowQuery(array($node)))->parent('[instanceof Neos.Neos:ContentCollection]')->get(0);
        if (!$contentCollection instanceof NodeInterface) {
            return false;
        }
        $collectionNodeName = $contentCollection->getName();
        /** @var NodeInterface $contentCollectionParent */
        $contentCollectionParent = (new FlowQuery(array($contentCollection)))->parent()->get(0);
        $configuration = array_change_key_case($contentCollectionParent->getNodeType()->getFullConfiguration()['childNodes'], CASE_LOWER)[$collectionNodeName];
        if (!array_key_exists('options', $configuration)) {
            return false;
        }
        if (!array_key_exists('wrapContainer', $configuration['options'])) {
            return false;
        }
        return  $configuration['options']['wrapContainer'];
    }

    /**
     * All methods are considered safe
     *
     * @param string $methodName
     * @return boolean
     */
    public function allowsCallOfMethod($methodName)
    {
        return true;
    }
}

Final part is that we add an option “wrapContainer” to all ContentCollections where we want the wrapping:

'Vendor.Package:Document.Foo':  
  childNodes:
    main:
      type: 'Neos.Neos:ContentCollection'
      options:
        wrapContainer: true