Specify insertion point for ContentCollection items

Problem

Given two NodeTypes, Timeline and TimelineEvent. Timeline is both Content and ContentCollection. When inserting a TimelineEvent into Timeline the position in the DOM is wrong. The screenshot shows the new TimelineEvent (the <li> tag), the arrow where it should have been placed:

That happens, because the element is wrapped and the UI inserts the new content directly into it. Not quite as expected in this particular case, but as implemented. So not really a bug…

Trying to fix it–or at least find a workaround

  1. Tried it like described in this blog post by @sebobo, but that doesn’t help (even though AFX looks way better.) It works for that case, because new children are actually supposed to be inserted directly into the element. For my case, the same problem exists.

  2. Then I came up with this fix, actually using a ContentCollection for the items:

     diff --git a/DistributionPackages/Foo.Bar.Base/Resources/Private/Fusion/NodeTypes/Timeline.fusion b/DistributionPackages/Foo.Bar.Base/Resources/Private/Fusion/NodeTypes/Timeline.fusion
     index db91a1a1..6635979f 100644
     --- a/DistributionPackages/Foo.Bar.Base/Resources/Private/Fusion/NodeTypes/Timeline.fusion
     +++ b/DistributionPackages/Foo.Bar.Base/Resources/Private/Fusion/NodeTypes/Timeline.fusion
     @@ -2,7 +2,8 @@ prototype(Foo.Bar.Base:Timeline) < prototype(Neos.Neos:Content) {
          templatePath = 'resource://Foo.Bar.Base/Private/Templates/NodeTypes/Timeline.html'
    
          title = ${q(node).property('title')}
     -    items = Neos.Fusion:Collection {
     +    items = Neos.Neos:ContentCollection {
     +        tagName = 'ul'
              collection = ${q(node).children().get()}
              itemName = 'node'
              itemRenderer = Foo.Bar.Base:TimelineItem
     diff --git a/DistributionPackages/Foo.Bar.Base/Resources/Private/Templates/NodeTypes/Timeline.html b/DistributionPackages/Foo.Bar.Base/Resources/Private/Templates/NodeTypes/Timeline.html
     index 2812e43a..dd5df866 100644
     --- a/DistributionPackages/Foo.Bar.Base/Resources/Private/Templates/NodeTypes/Timeline.html
     +++ b/DistributionPackages/Foo.Bar.Base/Resources/Private/Templates/NodeTypes/Timeline.html
     @@ -7,9 +7,7 @@
                      {neos:contentElement.editable(property: 'title', tag: 'h2')}
                  </div>
    
     -            <ul>
     -                {items -> f:format.raw()}
     -            </ul>
     +            {items -> f:format.raw()}
    
              </div>
          </div>
    

    Now when clicking the plus icon on the toolbar of the “inner” collection, all works fine, but there is still the “outer” wrapping around the whole element, and if inserting a TimelineItem using that, it still ends up at the same wrong place. :frowning:

  3. This is what I came up with next, after some time and a finding a hint by @mficzel in this discuss post:

     prototype(Foo.Bar.Base:Timeline) < prototype(Neos.Neos:Content) {
         // …
         @process.contentElementWrapping >
     }
     // and add contentElementWrapping.wrap around the Timeline title
     // in the template to make that work.
    

    Beautifully solves the problem! Except… now when inserting a Timeline element, the returned HTML has no “outer” wrapping anymore, and only the first “inner” wrapping around the title is seen by the UI and added to the DOM of the document. As a result the rendering is broken and added TimelineItem nodes do not show up, unless the document is reloaded.

Summary: No way to solve the issue right now.

A possible real solution

Somehow we need to wrap the whole element and specify where new children are supposed to be added in the DOM. This is a rough sketch of a way to do that, as braindumped during a discussion with @christianm today:

prototype(Foo.Bar.Base:Timeline) < prototype(Neos.Neos:Content) {
    // …

    // Disable the direct insertion of children on the outer wrapping
    @process.contentElementWrapping.insertionPoint = false

    items = Neos.Fusion:Collection {
        tagName = 'ul'
        nodePath = '.'
        // Add a new lightweight wrapper where insertion _should_ take place
        @process.contentCollectionWrapping = Neos.Neos:ContentCollectionWrapping
    }
}

This would be backwards-compatible. Now it only needs to be implemented… :wink:

1 Like

This has been fixed for Neos 5.2 in https://github.com/neos/neos-ui/pull/2609.

To sepcify a point for inserting children of a contentcollectiomn node in your markup, you can use an attribute on the container element: data-__neos-insertion-anchor. If found, that container will be used for inserting new items, otherwise the parent element (as usual).

Nice and easy, but so far won’t be available for anthing before 5.2…

Related to that are the PRs https://github.com/neos/neos-development-collection/pull/2891
and https://github.com/neos/neos-ui/pull/2667

With those two changes the ContentCollection will by default work as expected.

I started the change as a feature as initially I didn’t expect to find a solution that would make content collections just work without further effort.

In retrospect I would have loved it as a fix that would include 4.x of course.

Hey,

as the change is rather small I guess, why not backport it additionally? :slight_smile:

All the best,
Sebastian

1 Like

Fine with me.

I had already added a link to the change to my blog post but will make it clearer how to accomplish this after the next Neos releases then.
Several people already asked me about it, that’s also why I made the change :wink: