Thursday, December 20, 2018

SXA Pagination on a custom component

SXA Pagination component


The SXA pagination component adds a paging feature to your lists. Out of the box, it can work together with the (also out-of-the-box) PageList, FileList and EventList components. But what if you want this to work with your own component?

A custom paginable component


Sitecore StackExchange to the rescue! I found this nice answer https://sitecore.stackexchange.com/q/8105/237 that made me think it was going to be easy...

So for starters I have:
  • a controller with an action method
    (and a view with variants and such, but that is not in scope here - I've written another post if you want to know how to create our own rendering with variants)
  • a repository that gets items from the index (based on a Search scope.. might be my next post)
As I am getting too many items at once, I would like to add paging to this component. Preferably the out-of-the-box paging component (so I don't need to write it).

The answer on SSE indicated that a bit of configuration would do it.. but unfortunately it did not. My repository does not inherit from ListRepository (nor implements IListRepository) as that doesn't fit my needs. The steps I had to take:

Configuration in Sitecore

I'm not going to describe how to add your rendering to Sitecore and SXA - that is described in the official docs.

Your rendering does need rendering parameters. The rendering parameters temlate for your component should inherit IPaginable and IPagination (both from the Feature/Experience Accelerator/Page Content/Rendering Parameters folder).

This will give you the necessary parameters to make your component compatible with the pagination.

The parameters are described in the official docs on the pagination subject but your main focus is:
  • Page Size: the number of items on a page
  • List Signature: a string that will be used as signature - the same string needs to be entered in the rendering parameters of the pagination component to tell both renderings that they belong together. Note that this string will also appear in your querystring when paging..

The configuration mentioned in the SSE answer is now moved into the Sitecore item. So, on your rendering item you need to add IsPaginationEnabledRendering with a value true to the OtherProperties.


Changes to the repository

Our repository has a function GetModel to create the model for the controller. It actually calls VariantsRepository.FillBaseProperties and adds the items we fetch from the index to the model - the model inherits from VariantsRenderingModel.

This function needs an extra parameter of type IListPagination. This parameter will provide the Skip and PageSize information that we need to get the right page from the data. So this is the place where we reduce the full data set to the requested page.

Change to the controller

To be compatible with the Pagination component, our controller needs to derive from the PaginableController. We had to override 2 functions:

PaginationConfiguration

protected override IListPagination PaginationConfiguration
{
  get
    {
      var listPagination = ListPaginationContext.GetCurrent().Get(Rendering.Parameters["ListSignature"]);
      if (listPagination != null)
      {
        return listPagination;
      }

      var parameters = Rendering.Parameters;
      var scope = parameters["Scope"];
      return new ListPagination(parameters.ToDictionary(p => p.Key, p => p.Value), searchResultsRepository.GetItems(scope).Count());
    }
}
The first function to override is the one that get the PaginationConfiguration. The only changes made to the original function are the call to the repository - this needs an extra parameter in our case (and is not the default ListRepository as mentioned before).

Once this function is in place, use it to pass the PaginationConfiguration to the repository (GetModel - see above). At this point you should see that your component already can select the correct page from the data.


OnActionExecuting

We couldn't see any pagination yet though. To get that working, we needed to override one more function:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
  if (ListPaginationContext.GetCurrent().Initialized)
  {
    return;
  }

  var paginationService = ServiceLocator.ServiceProvider.GetService<IPaginationService>();
  foreach (var sitecoreRendering in Sitecore.Mvc.Presentation.PageContext.Current.PageDefinition.Renderings.Where(r => paginationService.IsPaginationEnabledRendering(ID.Parse(r.RenderingItemPath))))
  {
    var scope = Rendering.Parameters["Scope"];
    var itemsCount = searchResultsRepository.GetItems(scope).Count();
    ListPagination listPagination;
    if (itemsCount <= 0)
    {
      var parameters = sitecoreRendering.Parameters;
      listPagination = new ListPagination(parameters.ToDictionary(p => p.Key, p => p.Value), searchResultsRepository.GetItems(scope).Count());
    }
    else
    {
      var parameters = sitecoreRendering.Parameters;
      listPagination = new ListPagination(parameters.ToDictionary(p => p.Key, p => p.Value), itemsCount);
    }

    IListPagination pagination = listPagination;
    if (!ListPaginationContext.GetCurrent().Contains(pagination))
    {
      ListPaginationContext.GetCurrent().Add(pagination);
    }
  }

  ListPaginationContext.GetCurrent().Initialized = true;
}
This function will initialize the Pagination Context. But again, by default it will use the ListRepository and we didn't have that one - so we had to override it and use our own repository.

Conclusion

It's no rocket science if you know how to do it.. :)

I did spend some time figuring this out - but next time it will go much much faster and I will have paging on my components in no time. And maybe with this guide you will too..

ps: using SXA 1.7.1 at time of writing