Content Collection Fusion Objects
#Neos.Neos:ContentCollection
Neos.Neos:ContentCollection
renders child nodes of a given node. This is relevant in the following cases:
- Inside your page template (
Neos.Neos:Page
), to render the main content of a page - Inside a
Neos.Neos:ContentComponent
, where you render nested content nodes (e.g. in a Multi-Column element).
Neos.Neos:ContentCollection
always renders a wrapping <div> element, which is enriched with additional attributes needed for the Neos inline editing to work.
Basically. it works like this (in Pseudocode):
// Pseudocode, simplified
$collectionNode = $node->getNode("main"); // configurable path
$output = '';
foreach ($collectionNode->getChildren() as $childNode) {
$nodeTypeName = $childNode->getNodeTypeName();
$output .= renderFusion($nodeTypeName, /* context */ ['node' => $childNode]);
}
return $output;
As input, you can configure a node name to be used as the collection node. This node name is always searched relative to the current context node (i.e. the one you can access as ${node}
in Fusion).
Then, Neos.Neos:ContentCollection
iterates over all child nodes of the collection node. For each child node, it tries to render the fusion object with the same name as the node type. Additionally, it changes the context node
to the child node while rendering it.
#Practical Usage inside Pages
Neos.Neos:ContentCollection
is most often used inside the rendering of a page.
For this example, we assume that the page node has a main child node (which is usually configured as auto-created inside NodeTypes.yaml
) with the Node Type Neos.Neos:ContentCollection
(or a subtype).
A practical example can look like this:
prototype(MyVendor.AwesomeNeosProject:Document.AbstractPage) < prototype(Neos.Neos:Page) {
body = MyVendor.AwesomeNeosProject:Layout.Default {
content = Neos.Neos:ContentCollection {
nodePath = 'main'
}
}
}
prototype(MyVendor.AwesomeNeosProject:Layout.Default) < prototype(Neos.Fusion:Component) {
// API
content = ''
menu = ''
// Implementation
renderer = afx`
<section>
Some very long markup here for the page template
<nav>{props.menu}</nav>
<article>{props.content}</article>
</section>
`
}
ContentCollection should only be used inside Neos.Neos:Page or Neos.Neos:ContentComponent.
It is discouraged to use ContentCollection
inside Neos.Fusion:Component
, as this breaks the conceptual layering of the Fusion objects. This is the same rule as with Neos.Neos:Editable
.
#Practical Usage inside ContentComponents
Neos.Neos:ContentCollection
is also used inside the rendering of a content node with child nodes.
For this example, we assume that our Node TwoColumn has two auto-created child nodes of node type Neos.Neos:ContentCollection, named column1 and column2:
prototype(MyVendor.AwesomeNeosProject:Content.TwoColumn) < prototype(Neos.Neos:ContentComponent) {
column1 = Neos.Neos:ContentCollection {
nodePath = 'column1'
}
column2 = Neos.Neos:ContentCollection {
nodePath = 'column2'
}
renderer = afx`
<div>{props.column1}</div>
<div>{props.column2}</div>
`
}
#Practical Usage inside ContentCollections wrapped ContentComponents
For instance when building Slider node types, it is often useful to make the Slider node type directly extend from node type Neos.Neos:ContentCollection, as this greatly reduces the number of node types involved.
In this case, the context node itself is the ContentCollection, so we do not need to go one level down.
This can be implemented by not specifying the nodePath while rendering, as in the following example:
// we assume the NodeType "Slider" is of type ContentCollection
prototype(MyVendor.AwesomeNeosProject:Content.Slider) < prototype(Neos.Neos:ContentComponent) {
slides = Neos.Neos:ContentCollection
renderer = afx`
{props.slides}
`
}
This idea is fully explained by core team member Sebastian Helzle in his blog:
#Setting additional attributes on the wrapping <div>
As stated above, Neos.Neos:ContentCollection
always renders a wrapping <div>
. When working with certain JavaScript libraries like sliders, these sometimes assume an exact markup structure, where no nested tags are allowed.
For this case, it is useful to know that Neos.Neos:ContentCollection
inherits from Neos.Fusion:Tag
, and thus, you can directly set arbitrary HTML attributes on the wrapping <div>
by using attributes.*
as follows:
// we assume the NodeType "Slider" is of type ContentCollection
prototype(MyVendor.AwesomeNeosProject:Content.Slider) < prototype(Neos.Neos:ContentComponent) {
slides = Neos.Neos:ContentCollection {
attributes.class = "my-extra-css-class"
}
renderer = afx`
{props.slides}
`
}
#Internal Behavior
Neos.Neos:ContentCollection
is inheriting from Neos.Fusion:Tag
for rendering the wrapping <div>
, and the relevant Neos backend attributes.
Internally, it uses Neos.Neos:ContentCollectionRenderer
for looping over its child nodes.
Neos.Neos:ContentCollectionRenderer
extends from Neos.Fusion:Loop
for doing the iteration of the child nodes. Every child node, in turn, is rendered then by Neos.Neos:ContentCase
.
Neos.Neos:ContentCase
implements the lookup how to find a Fusion prototype to use, given a Node - and by default, it says "use the node type name as fusion prototype name":
# Neos.Neos:ContentCase inherits Neos.Fusion:Case and overrides the default case
# with a catch-all condition for the default case, setting the type variable to
# the name of the current nodes' node type
#
prototype(Neos.Neos:ContentCase) < prototype(Neos.Fusion:Case) {
default {
@position = 'end'
condition = true
type = ${q(node).property('_nodeType.name')}
}
}
#Neos.Neos:PrimaryContent
Neos.Neos:PrimaryContent
wraps Neos.ContentCollection with an additional extension point, so that others can override the primary rendering of a page based on some conditions.
In practice, this scenario is way less likely than we originally envisioned it to be - so feel free to not use Neos.Neos:PrimaryContent
at all in your projects, and instead simply use Neos.Neos:ContentCollection
directly.
If you want to use Neos.Neos:PrimaryContent
, the following rules apply:
- There should be only one usage of
Neos.Neos:PrimaryContent
on any given page. - This means: only use
Neos.Neos:PrimaryContent
insideNeos.Neos:Page
. Never useNeos.Neos:PrimaryContent
insideNeos.Neos:ContentComponent
.
#Internal Behavior
Neos.Neos:PrimaryContent
extends from Neos.Fusion:Case, and, by default, delegates rendering to Neos.Neos:ContentCollection. This can be nicely seen in the default implementation:
# Primary content should be used to render the main content of a site and
# provides an extension point for other packages
#
prototype(Neos.Neos:PrimaryContent) < prototype(Neos.Fusion:Case) {
nodePath = 'to-be-defined-by-user'
@context.nodePath = ${this.nodePath}
@ignoreProperties = ${['nodePath']}
default {
condition = true
renderer = Neos.Neos:ContentCollection {
nodePath = ${nodePath}
}
@position = 'end'
}
}