Example Showing How to Get The Next & Previous Content Items In ZauberCMS
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">←</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">→</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 👇