Flowpack.Listable in combination with Neos.Demo Blog

Hello together,

i’m posting here because i’m struggling about to implement a working Pagination for the Blog of Neos.Demo. All in all, Flowpack.Listable is working perfectly fine with my own little created NodeTypes and i can list them, create Paginations and so on.

So i’ve tried to List some elements of BlogPostingList.fusion. Well, i’ve also tried to list Neos.Demo:Document.Blog and Neos.Demo:Document.BlogPosting, but because of the current encapsulation by using Cards/Container i’m not sure how to actually get any news and it wasn’t working anyway :smiley:

So what i thought is implementing a own BlogPostingList.fusion, as this element is already creating a list.

##
# "ListableBlogList" element
#
prototype(Neos.Demo:Content.ListableBlogList) < prototype(Flowpack.Listable:PaginatedCollection) {
  collection = ${q(site).find('[instanceof Neos.Demo:Content.BlogPostingList]').get()}
  itemsPerPage = 1
  maximumNumberOfLinks = 2
  listRenderer = 'Flowpack.Listable:Collection'
  showPreviousNextLinks = true
}
##
# "BlogPostingList" element
#
prototype(Neos.Demo:Content.BlogPostingList) < prototype(Neos.Neos:ContentComponent) {

    blogArticles.@if.has = ${q(node).property('blogs')}
    blogArticles = ${q(q(node).property('blogs')).children('[instanceof Neos.Demo:Document.BlogPosting]')}
    blogArticles.@process.sortBy = ${value.sort("datePublished", "DESC")}
    blogArticles.@process.limit = ${value.slice(0, q(node).property('limit'))}

    renderer = Neos.Demo:Presentation.Cards.Container {
        content = Neos.Fusion:Loop {
            items = ${props.blogArticles}
            itemName = "blogPosting"
            itemRenderer = Neos.Demo:Presentation.Cards.Card {
                imageUri = Neos.Neos:ImageUri {
                    asset = ${q(blogPosting).property('image')}
                    maximumWidth = 400
                    maximumHeight = 225
                }
                title = ${q(blogPosting).property('title')}
                content = ${q(blogPosting).property('abstract')}
                uri = Neos.Neos:NodeUri {
                    node = ${blogPosting}
                }
            }
        }
    }

    @cache {
        mode = "cached"
        entryIdentifier {
            node = ${node}
        }
        entryTags {
            # invalidate cache when the node rendered changes
            1 = ${Neos.Caching.nodeTag(node)}
            # invalidate cache when one of the selected blogs changes (might become hidden)
            2 = ${Neos.Caching.nodeTag(q(node).property('blogs'))}
            # invalidate cache when a descendent of the selected blogs changes
            3 = ${Neos.Caching.descendantOfTag(q(node).property('blogs'))}
        }
    }
}


##
# "BlogPostingList" element for Flowpack.Listable
#
prototype(Neos.Demo:Content.BlogPostingList.Short) < prototype(Neos.Neos:ContentComponent) {

    blogArticles.@if.has = ${q(node).property('blogs')}
    blogArticles = ${q(q(node).property('blogs')).children('[instanceof Neos.Demo:Document.BlogPosting]')}
    blogArticles.@process.sortBy = ${value.sort("datePublished", "DESC")}
    blogArticles.@process.limit = ${value.slice(0, q(node).property('limit'))}

    renderer = Neos.Demo:Presentation.Cards.Container {
        content = Neos.Fusion:Loop {
            items = ${props.blogArticles}
            itemName = "blogPosting"
            itemRenderer = Neos.Demo:Presentation.Cards.Card {
                imageUri = Neos.Neos:ImageUri {
                    asset = ${q(blogPosting).property('image')}
                    maximumWidth = 400
                    maximumHeight = 225
                }
                title = ${q(blogPosting).property('title')}
                content = ${q(blogPosting).property('abstract')}
                uri = Neos.Neos:NodeUri {
                    node = ${blogPosting}
                }
            }
        }
    }

    @cache {
        mode = "cached"
        entryIdentifier {
            node = ${node}
        }
        entryTags {
            # invalidate cache when the node rendered changes
            1 = ${Neos.Caching.nodeTag(node)}
            # invalidate cache when one of the selected blogs changes (might become hidden)
            2 = ${Neos.Caching.nodeTag(q(node).property('blogs'))}
            # invalidate cache when a descendent of the selected blogs changes
            3 = ${Neos.Caching.descendantOfTag(q(node).property('blogs'))}
        }
    }
}

However, so far the list was empty. For any hint i’m super happy :smiley:

I think i came a little bit more far here, but i’m still a bit clueless. I can see the correct pagination of the items of Blogposts (2). Also i’ve tried to minify the Short NodeType:

##
# "ListableBlogList" element
#
prototype(Neos.Demo:Content.ListableBlogList) < prototype(Flowpack.Listable:PaginatedCollection) {

  collection = ${q(site).find('[instanceof Neos.Demo:Document.BlogPosting]').get()}
  itemsPerPage = 1
  maximumNumberOfLinks = 2
  listRenderer = 'Flowpack.Listable:Collection'
  showPreviousNextLinks = true
}
prototype(Neos.Demo:Document.BlogPosting) < prototype(Neos.Demo:Document.Page) { ... }
/**
 * "BlogPostingList" element for Flowpack.Listable
 */
prototype(Neos.Demo:Document.BlogPosting.Short) < prototype(Neos.Neos:ContentComponent) {

    structuredData.blogPosting = Neos.Seo:StructuredData.RootObject {
        type = "BlogPosting"
        attributes = Neos.Fusion:DataStructure {
            url = Neos.Neos:NodeUri {
                node = ${documentNode}
            }
            author = Neos.Seo:StructuredData.Object {
                type = "Person"
                attributes.name = ${q(node).property('authorName')}
            }
            headline = ${q(node).property('title')}
            abstract = ${q(node).property('abstract')}
            datePublished = ${Date.format(q(node).property('datePublished'), 'Y-m-d')}
            image = Neos.Seo:StructuredData.Object {
                type = "ImageObject"
                attributes.url = Neos.Neos:ImageUri {
                    asset = ${q(node).property('image')}
                    maximumWidth = 400
                    maximumHeight = 225
                    allowCropping = true
                }
            }
        }
    }
}

But now there is the following error thrown:

No Fusion object found in path "root<Neos.Fusion:Case>/documentType<Neos.Fusion:Matcher>/element<Neos.Demo:Document.Page>/content<Neos.Neos:ContentCollection>/content<Neos.Neos:ContentCollectionRenderer>/itemRenderer<Neos.Neos:ContentCase>/default<Neos.Fusion:Matcher>/element<Neos.Demo:Content.ListableNewsList>/renderer<Neos.Fusion:Array>/list<Neos.Fusion:Renderer>/element<Flowpack.Listable:Collection>/itemRenderer<Flowpack.Listable:ContentCaseShort>/default<Neos.Fusion:Matcher>/element<Neos.Demo:Document.BlogPosting.Short>/renderer" Please make sure to define one in your Fusion configuration.

root<Neos.Fusion:Case>/ documentType<Neos.Fusion:Matcher>/ element<Neos.Demo:Document.Page>/ content<Neos.Neos:ContentCollection>/ content<Neos.Neos:ContentCollectionRenderer>/ itemRenderer<Neos.Neos:ContentCase>/ default<Neos.Fusion:Matcher>/ element<Neos.Demo:Content.ListableNewsList>/ renderer<Neos.Fusion:Array>/ list<Neos.Fusion:Renderer>/ element<Flowpack.Listable:Collection>/ itemRenderer<Flowpack.Listable:ContentCaseShort>/ default<Neos.Fusion:Matcher>/ element<Neos.Demo:Document.BlogPosting.Short><Neos.Demo:Document.BlogPosting.Short>/

Anyway, the Fusion object Neos.Demo:Document.BlogPosting.Short is existing :thinking:

Okay i think it is because the original BlogPosting is inheriting from Type:

prototype(Neos.Demo:Document.Page)

while the Short is inheriting from:

prototype(Neos.Neos:ContentComponent)

But when i inherit from Document.Page, the whole page is rendered twice and everything is messed up :sweat:

I found a solution, as it was somehow not possible to re-use the already exisiting properties and template, so i did it like that:

prototype(Neos.Demo:Document.BlogPosting.Short) < prototype(Neos.Neos:ContentComponent) {
	
	date = ${Date.format(q(node).property('datePublished'), 'Y-m-d')}
	authorName = ${q(node).property('authorName')}
	title = ${q(node).property('title')}
	content = ${q(node).property('abstract')}
    uri = Neos.Neos:NodeUri {
        node = ${node}
    }
	imageUri = Neos.Neos:ImageUri {
		asset = ${q(node).property('image')}
		width = 1248
		height = 702
    }
	

	renderer = afx`
		<div class="blogentry">
			<div class="head">
					<div class="tag">
						{I18n.translate('Neos.Demo:Presentation.Cards:cards.authorPublishedBy')} {props.authorName}
					</div>
					<picture>
						<img @if.has={props.imageUri} src={props.imageUri} aria-label={props.headline} alt={props.headline}/>
					</picture>
					<div>{I18n.translate('Neos.Demo:Presentation.Cards:cards.imageNotAvailable')}</div>
			</div>
			<div class="detail">
				<h2>{props.title}</h2>
					<div>
						<p>
							<!--
								<time datetime={Date.format(props.date, 'Y-m-d')} @if={props.date}>
									{Date.formatCldr(props.date, 'd. MMMM y')}
								</time>
							-->
							<time>{props.date}</time>
						</p>
						<p>{props.content}</p>
					</div>
					<div class="more">
						<a href={props.uri}">{I18n.translate('Neos.Demo:Presentation.Cards:cards.moreButton')}</a>
					</div>
			</div>
		</div>
	`
}

What i’ve noticed, somehow i can’t use the following as from the Neos.Demo example:

<time datetime={Date.format(props.date, 'Y-m-d')} @if={props.date}>
	{Date.formatCldr(props.date, 'd. MMMM y')}
</time>

As it throws:

The given date "2023-04-27" was neither an integer, "now" or a \DateTimeInterface instance.

root<Neos.Fusion:Case>/ documentType<Neos.Fusion:Matcher>/ element<Neos.Demo:Document.Page>/ content<Neos.Neos:ContentCollection>/ content<Neos.Neos:ContentCollectionRenderer>/ itemRenderer<Neos.Neos:ContentCase>/ default<Neos.Fusion:Matcher>/ element<Neos.Demo:Content.ListableBlogList>/ renderer<Neos.Fusion:Array>/ list<Neos.Fusion:Renderer>/ element<Flowpack.Listable:Collection>/ itemRenderer<Flowpack.Listable:ContentCaseShort>/ default<Neos.Fusion:Matcher>/ element<Neos.Demo:Document.BlogPosting.Short>/ renderer<Neos.Fusion:Tag>/ content<Neos.Fusion:Tag>/ content<Neos.Fusion:Join>/ item_2<Neos.Fusion:Tag>/ content<Neos.Fusion:Join>/ item_2<Neos.Fusion:Tag>/ content<Neos.Fusion:Join>/ item_1<Neos.Fusion:Tag>/ content<Neos.Fusion:Join>/ item_1<Neos.Fusion:Tag>/ content<>/

By using {props.date} it is working and i could also transform it e.g. with:

date = ${Date.format(q(node).property('datePublished'), 'd.m.Y')} but why can’t i use Date.formatCldr in that case?

What do you think about my solution? Also it would be great to find out why i can’t use the formatCldr function.

Thanks! :slight_smile:

Fixed, i didn’t had to format it twice…

changed from:

date = ${Date.format(q(node).property('datePublished'), 'Y-m-d')}

to just:

date = ${q(node).property('datePublished')}