Wrap items in a collection with custom markup


(Jan Wessel) #1

Hello everyone,

I’m a total Neos Noob, so the following might sound very basic to you.

I’m facing a challenge where I need to have a number of items in a contentCollection to be wrapped in custom markup. I need this in order to build carousels that accept existing domain specific node types as children. I read about the basic carousel ind the docs and also about Flowpack.Listable but both don’t seem to suit my needs. One other thread is also not really helpful to me as it only vaguely describes the solution.

I could of course change our existing carousel script to change the markup or adapt to the new structure, but I need the same principle in other places as well.

Is there a way in fluid/fusion to achieve this?

Thanks for your help!
Regards Jan


(Soren Malling) #2

Hi Jan,

Can you give a some more insight in what doesn’t solve your issue in the basic caroussel example? Have you managed to select the nodes you need already and only struggle with the rendering?


(Jan Wessel) #3

Hi Soren,

in its basic form the fusion looks like this (though i stopped working on it):

prototype(SITE:Carousel) < prototype(Neos.Neos:Content) {
    templatePath = 'resource://SITE/Private/Templates/NodeTypes/Carousel.html'

    items = Neos.Neos.ContentCollection {
        nodePath = 'items'
        content.iterationName = 'carouselItemsIteration'
        attributes.class = 'slider'
        tagName = 'ul'
        itemName = 'item'
    }

    carouselItemArray = ${q(node).children('items').children()}
}

The result is of course the collection object with the items.


Now in the fluid template I have the following:

{namespace neos=Neos\Neos\ViewHelpers}
{namespace media=Neos\Media\ViewHelpers}

<div{attributes -> f:format.raw()}>
    <div class="slider-holder" id="{node.identifier}">

    {items -> f:format.raw()}

Of course I cannot render the items directly (as they are inside an object -> Cannot cast object of type “Neos\Fusion\FusionObjects\Helpers\FusionPathProxy” to string.). I could theoretically iterate over the object (using f:for) and access the items and thus their properties directly. But I want to avoid that as I already have node types for every included item.

In essence what I expect to build is ul.slider > li.slider__item and inside the rendered node type.

Does that makes sense?


(Jan Wessel) #4

So, I changed my approach slightly but to no avail, the children do not render:

Fusion:
prototype(SITE:Carousel) < prototype(Neos.Neos:Content) {
templatePath = ‘resource://SITE/Private/Templates/NodeTypes/Carousel.html’

    carouselContent = Neos.Fusion:Collection {
        collection = ${q(node).children('[instanceof Neos.Neos:ContentCollection]').children()}
        itemName = 'carouselItem'
        iterationName = 'iterator'
        itemRenderer = Neos.Fusion:Template {
            templatePath = 'resource://SITE/Private/Templates/NodeTypes/CarouselItem.html'
            element = ${carouselItem}
        }
    }
}

Fluid (Carousel):
{namespace neos=Neos\Neos\ViewHelpers}
{namespace media=Neos\Media\ViewHelpers}
{namespace fusion=Neos\Media\ViewHelpers}

<div{attributes -> f:format.raw()}>
<div class="slider-holder<f:if condition='{isTimeline}'> slider--timeline</f:if>">

    <ul class="slides">
        {carouselContent -> f:format.raw()}
    </ul>
</div>
</div>

Fluid (CarouselItem):
{namespace neos=Neos\Neos\ViewHelpers}
{namespace media=Neos\Media\ViewHelpers}
{namespace fusion=Neos\Media\ViewHelpers}

<li{attributes -> f:format.raw()} class="slide">
{element}
</li>

The result is the correct surrounding markup, but the children do not render, but display as:
Node /sites/site/node-vmbm2ctwttgmx/main/node-lxsroodowvl5j/items/node-b7c9xj06okprd@live;language=de[SITE:ITEM]


(Soren Malling) #5

That is because {element} is the node it self (what you see is the nodePath) so you are only missing to render the properties (like img src, title etc)


(Jan Wessel) #6

That’s exactly the point, Soren. I don’t want to render the properties by themselves, because for every item in the collection I already have a fluid template set up that should be used for rendering.


(Soren Malling) #7

Since {element} is just a node filled with data, you will have to either use the correct template then or create a new one


(Jan Wessel) #8

Thanks, Soren.
I get the reason and idea behind it, but my knowledge is limited.

I need a third level of templating which would be the template for the correct node type, so the hierarchy would be carousel > carouselItem > nodeType.

I’m sorry for asking that, but how do I implement the correct template?

My first idea is to make the carouselItem a contentCollection with just one item.

Update: it’s basically working, but now in every slide the whole content is rendered (every item of the collection)!


(Jan Wessel) #9

To keep you updated: my problem has been solved by @robert!

The trick was to use custom wrapping like so:

“Carousel” element

prototype(SITE:Carousel) {

items = Neos.Neos:ContentCollection {
	nodePath = 'items'
	itemName = 'carouselItem'
	tagName = 'ul'

	attributes.class = 'slider'
}

prototype(Neos.Neos:ContentCollectionRenderer) {
	itemRenderer.@process.wrapWithContainer = ${'<li class="slide">' + value + '</li>'}
}

contentItemsArray = ${q(node).children('items').children()}

}

See the itemRenderer.@process part!


(Bernhard Witz) #10

Hi @JanW

I struggle with the same problem. For the Slick Carousel The only thing I need is something like this:

<div class="carousel">
    <div>content of slide 1</div>
    <div>content of slide 2</div>
    <div>content of slide 3</div>
</div> 

Instead I’m only able to get something like this:

<div class="carousel">
	<div class="neos-contentcollection">
        <div>content of slide 1</div>
        <div>content of slide 2</div>
        <div>content of slide 3</div>
    </div>
</div> 

It would be great if you could share your code for the carousel with me or explain how this trick with the custom wrapping works.


(Jan Wessel) #11

Hi, I assume you have the div with the class carousel in your fluid code, right? The easiest would be to remove that and instead use sth. like
carousel = Neos.Neos:ContentCollection {
[…]
attributes.class = ‘carousel’
}

inside your fusion definition.


(Bernhard Witz) #12

Thanks for your fast answer. I’m still confused but at least I found a solution. With the example code from readthedocs.io it renders the HTML code:

<div class="my-carousel">
    <div class="carousel slide" id="eaa3de90-7dd0-40aa-b547-e33b87396988">
        <!-- Wrapper for slides -->
        <div class="carousel-inner neos-contentcollection">
			<div>content of slide 1</div>
	        <div>content of slide 2</div>
	        <div>content of slide 3</div>
    	</div>
    </div>
</div>

To initialize the slides with Slick I just use the class “.carousel-inner” instead of “.carousel”. It’s not really pretty because the two wrapping div’s are useless but at least it’s working now :slight_smile:


(Jan Wessel) #13

Could you post your fluid and fusion code?


(Bernhard Witz) #14

Sure. It’s more or less the code from the readthedocs.io slider tutorial.

NodeTypes.Carousel.yaml

'My.Package:Carousel':
  superTypes:
    'Neos.Neos:Content': true
  childNodes:
    carouselItems:
      type: 'Neos.Neos:ContentCollection'
  ui:
    label: 'Carousel'
    group: 'plugins'
    icon: 'icon-picture'
    inlineEditable: true

Carousel.fusion

prototype(My.Package:Carousel) {
    carouselItems = Neos.Neos:ContentCollection {
        nodePath = 'carouselItems'
        content.iterationName = 'carouselItemsIteration'
        attributes.class = 'carousel-inner'
    }
}

Carousel.html

{namespace neos=Neos\Neos\ViewHelpers}
{namespace fusion=Neos\Fusion\ViewHelpers}
<div{attributes -> f:format.raw()}>
    <div class="carousel slide" id="{node.identifier}">

        <!-- Wrapper for slides -->
        {carouselItems -> f:format.raw()}

    </div>
</div>

I guess half of the code is unnecessary for what I need…


(Jan Wessel) #15

I think you could just leave out the two outer divs and assign both the id and attributes in
carouselItems = Neos.Neos:ContentCollection {}.

Sth. along the lines of
attributes.id = ${q(node).property(‘identifier’)}

But you’ll figure that out!


(Bernhard Witz) #16

Indeed! Some solutions are easier than expected :slight_smile:

Here how it looks if someone else wants to build a simple slick carousel.

NodeTypes.Carousel.yaml

'My.Package:Carousel':
  superTypes:
    'Neos.Neos:Content': true
  childNodes:
    carouselSlides:
      type: 'Neos.Neos:ContentCollection'
      constraints:
        nodeTypes:
          'My.Package:CarouselSlide': true
          '*': false
  ui:
    label: 'Carousel'
    group: 'mysettings'
    icon: 'icon-picture'
    inlineEditable: true

Carousel.fusion

prototype(My.Package:Carousel) {
    carouselSlides = Neos.Neos:ContentCollection {
        nodePath = 'carouselSlides'
        attributes.class = 'carousel'
    }
}

Carousel.html

{carouselSlides -> f:format.raw()}

Rendered HTML (before initialization through slick):

<div class="carousel neos-contentcollection">
    <div>content of slide 1</div>
    <div>content of slide 2</div>
    <div>content of slide 3</div>
</div>

Slick carousel documentation: http://kenwheeler.github.io/slick/