Avatar
← Back

Example Showing How to Get The Next & Previous Content Items In ZauberCMS

User Avatar
YodasMyDad 14 days ago

One of the things I wanted to make sure when I was building ZauberCMS, was to make sure it was as extendable as possible and you could access and use the same DbContext the core app uses. 

When building this blog, I wanted to add a next and previous blog navigation at the bottom of each post. Nothing like this is available OOTB, so I had to make it myself. 

Because this is just Blazor, and I have access to Mediatr & EF Core it was super easy to do. Firstly I just created a command and handler, exactly like the pattern in the ZauberCMS core.

public class NextPreviousPostCommand : IRequest<(Content? previous, Content? next)>
{
    public Guid ContentId { get; set; }
}

public class NextPreviousPostHandler(IServiceProvider serviceProvider) : IRequestHandler<NextPreviousPostCommand, (Content? previous, Content? next)>
{
    public async Task<(Content? previous, Content? next)> Handle(NextPreviousPostCommand request, CancellationToken cancellationToken)
    {
        using var scope = serviceProvider.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<ZauberDbContext>();
        
        var currentContent = await dbContext.Contents
            .Where(c => c.Id == request.ContentId)
            .FirstOrDefaultAsync(cancellationToken: cancellationToken);

        if (currentContent == null)
        {
            return (null, null); // Return null if no such content exists
        }
        
        var contentType = currentContent.ContentTypeId;

        var previousContent = await dbContext.Contents
            .Where(c => c.DateCreated < currentContent.DateCreated && c.Published && !c.Deleted && c.ContentTypeId == contentType)
            .OrderByDescending(c => c.DateCreated)
            .FirstOrDefaultAsync(cancellationToken: cancellationToken);

        var nextContent = await dbContext.Contents
            .Where(c => c.DateCreated > currentContent.DateCreated && c.Published && !c.Deleted && c.ContentTypeId == contentType)
            .OrderBy(c => c.DateCreated)
            .FirstOrDefaultAsync(cancellationToken: cancellationToken);


        return (previousContent, nextContent);
        
    }
}

No I have a way to access the posts, I created a Blazor component that allowed a ContentId to be passed in and then called the above Handler from the OnParametersSetAsync() (I use OnParametersSetAsync because I want it to fire when the ContentID parameter changes).

@using Lee.Core.Data.Commands
@if (PostData.HasValue && (PostData.Value.previous != null || PostData.Value.next != null))
{
    <div class="grid grid-cols-1 gap-2 mx-1 md:mx-auto max-w-3xl md:grid-cols-2">
        @if (PostData.Value.previous != null)
        {
            <a href="/@PostData.Value.previous.Url" class="flex space-x-2 p-3 border border-gray-300 bg-white hover:border-purple-300 hover:bg-purple-50">
                <div class="font-medium text-2xl">&larr;</div>
                <div class="flex flex-col space-y-1">
                    <h4 class="line-clamp-3 leading-snug">@PostData.Value.previous.Name</h4>
                    <div class="pt-1 mb-2 text-sm text-gray-500">
                        @PostData.Value.previous.DateCreated.Humanize()
                    </div>
                </div>
            </a>
        }
        else
        {
            <div></div>
        }
        @if (PostData.Value.next != null)
        {
            <a href="/@PostData.Value.next.Url" class="flex space-x-2 p-3 border border-gray-300 bg-white hover:border-purple-300 hover:bg-purple-50">
                <div class="flex flex-col space-y-1">
                    <h4 class="line-clamp-3 leading-snug">@PostData.Value.next.Name</h4>
                    <div class="pt-1 mb-2 text-sm text-gray-500">
                        @PostData.Value.next.DateCreated.Humanize()
                    </div>
                </div>
                <div class="font-medium text-2xl">&rarr;</div>
            </a>
        }
        else
        {
            <div></div>
        }
    </div>
}

@code {
    [Parameter, EditorRequired] public Guid ContentId { get; set; }
    private (Content? previous, Content? next)? PostData { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        PostData = await Mediator.Send(new NextPreviousPostCommand { ContentId = ContentId });
    }
}

If you look down here you can see this component in action 👇

© 2024 Lee - Powered By ZauberCMS