Skip to content

Custom pager with only next previous mode (version 2)

Jin edited this page Apr 3, 2022 · 10 revisions

!!! Note: This is version 2 of this post: https://github.com/serenity-is/Serenity/wiki/Grid---Mixin---Custom-grid-pager-without-counting-total-records ...

Demo

In first version, beause I need to stop total row counting to optimize speed of grid (count total records is a heavy job and will reduce performance when you have a table with a lot records), I created a new pager with support only next/previous button

The pager transfer pager's information between serenity original pager vs custom pager. This is truly a pain for maintaining code. If you look at code of version 1, it is really ugly ^^

Version 1 you also write custom code in repository.cs class (or YourEntityListHandler.cs in new Serenity version), if you implement the next/previous pager then you have to re-write code for any page you want to apply, that is bored.

Also it does not know if the last page is exactly the last page or not. For example, if page size is 10, and you have total 30 rows, then last page is page 3, but it does not know if there is a next page or not because fetched entities of page 3 equals to pageSize ( = 10). Therefor, if you want to disable next page button when it hits the last page then you can not.

To handle that problem and make code cleaner I decided to use directly serenity's original pager with a bit custom about UI.

Firstly, you will need to create 2 files, one for OnlyNextPreviousPagerMixin.ts and one for UseNextPreviousPagerBehavior.cs (yes, this times we will use Serenity Behavior feature)

In this version the idea is we will take more 1 record to check if database still has next page, for example, instead take 20 rows as normal, we will take 21 rows, if database returns 21 row, it means we still have next page, if it just return 20 rows or less, it means current page is last page

Code

UseNextPreviousPagerBehavior.cs

using Serenity.Data;
using Serenity.Services;
using System;
using System.Collections.Generic;

namespace PUT_YOUR_NAMESPACE_HERE
{
    public class MyBaseListRequest : ListRequest
    {
        public bool EnablePagerOnlyNextPreviousMode { get; set; }
    }

    public interface IUseNextPreviousPager
    {
    }

    public class UseNextPreviousPagerBehavior : IImplicitBehavior, IListBehavior
    {
        IRow _row;

        public bool ActivateFor(IRow row)
        {
            var mt = row as IUseNextPreviousPager;
            if (mt == null)
                return false;

            _row = row;

            return true;
        }

        public void OnAfterExecuteQuery(IListRequestHandler handler)
        {
        }

        public void OnApplyFilters(IListRequestHandler handler, SqlQuery query)
        {
            if (handler.Request is MyBaseListRequest customRequest)
            {
                if (customRequest.EnablePagerOnlyNextPreviousMode)
                {
                    var oldTake = handler.Request.Take;
                    query.Skip(handler.Request.Skip);
                    query.Take(oldTake + 1);

                    // Setting CountRecords to false stops the count(*) query from running
                    query.CountRecords = false;
                }
            }
        }

        public void OnBeforeExecuteQuery(IListRequestHandler handler)
        {
        }

        public void OnPrepareQuery(IListRequestHandler handler, SqlQuery query)
        {
        }

        public void OnReturn(IListRequestHandler handler)
        {

            if (handler.Request is MyBaseListRequest customRequest)
            {
                if (customRequest.EnablePagerOnlyNextPreviousMode)
                {
                    //Type generic = typeof(ListResponse<>);
                    //Type[] typeArgs = { _row.GetType() };

                    //Type constructed = generic.MakeGenericType(typeArgs);
                    //var instance = Activator.CreateInstance(constructed);

                    //dynamic response = Convert.ChangeType(handler.Response, instance.GetType());

                    dynamic response = Convert.ChangeType(handler.Response, handler.Response.GetType());

                    response.TotalCount = int.MaxValue;

                    if (response.CustomData == null)
                    {
                        response.CustomData = new Dictionary<string, object>();
                    }

                    response.CustomData.Add("J_IsFirstPage", handler.Request.Skip == 0);

                    if (handler.Response.Entities.Count > handler.Request.Take)
                    {
                        response.CustomData.Add("J_OnlyNextPreviousMode_HasNextPage", true);

                        //var items = handler.Response.Entities.Cast<IRow>();

                        handler.Response.Entities.RemoveAt(handler.Request.Take);
                    }
                }
            }
        }

        public void OnValidateRequest(IListRequestHandler handler)
        {
        }
    }
}

OnlyNextPreviousPagerMixin.ts

namespace SimpleFeedly {
    export class OnlyNextPreviousPagerMixinOptions {
        grid: Serenity.DataGrid<any, any>;
    }

    export class OnlyNextPreviousPagerMixin {

        option: OnlyNextPreviousPagerMixinOptions;

        constructor(opt: OnlyNextPreviousPagerMixinOptions) {
            this.option = opt;

            this.initPager();
        }

        private initPager() {
            this.option.grid.element.find(".slick-pg-stat").remove();
            this.option.grid.element.find(".slick-pg-total").remove();
            this.option.grid.element.find(".slick-pg-first").remove();
            this.option.grid.element.find(".slick-pg-last").remove();

            this.option.grid.element.find(".slick-pg-control").contents().each(function () {
                //Text node
                if (this.nodeType === 3 && Q.trimToEmpty($(this).text()) == "/") {
                    $(this).remove();
                }
            });
            this.option.grid.element.find(".slick-pg-next").addClass("pager-button-disabled");
            this.option.grid.element.find(".slick-pg-prev").addClass("pager-button-disabled");
            this.option.grid.element.find(".slick-pg-first").addClass("pager-button-disabled");

            (this.option.grid.view.params as any).EnablePagerOnlyNextPreviousMode = true;
        }

        public handleOnViewProcessData(response: Serenity.ListResponse<any>) {
            let hasNextPage = (response as any)?.CustomData?.J_OnlyNextPreviousMode_HasNextPage ?? false;
            let isFirstPage = (response as any)?.CustomData?.J_IsFirstPage ?? false;

            this.option.grid.element.find(".slick-pg-next").toggleClass("pager-button-disabled", !hasNextPage);
            this.option.grid.element.find(".slick-pg-prev").toggleClass("pager-button-disabled", isFirstPage);
        }
    }
}

You also need a css class to (fake) disable buttons:

.pager-button-disabled {
    pointer-events: none;
    opacity: 0.3;
}

How to use

Put bellow into your grid.ts

/// <reference path="../../Common/Mixins/OnlyNextPreviousPagerMixin.ts" />

namespace PUT_YOUR_NAMESPACE_HERE {
@Serenity.Decorators.registerClass()
    export class YOUR_GRID extends Serenity.EntityGrid<any, any> {
        // ....
        // ....

        private onpp: OnlyNextPreviousPagerMixin;   // <===================== ADD THIS LINE

        constructor(container: JQuery) {
            super(container);

            this.onpp = new OnlyNextPreviousPagerMixin({ grid: this });   // <===================== ADD THIS LINE
        }

        protected onViewProcessData(response) {
            var lr = super.onViewProcessData(response);

            this.onpp.handleOnViewProcessData(response);    // <===================== ADD THIS LINE

            return lr;
        }
    }
}

Finally, add IUseNextPreviousPager into your Row.cs, for example:

public sealed class YOUR_ROW : Row<YourEntity.RowFields>, IIdRow, INameRow, IUseNextPreviousPager // <========== ADD HERE
{
    [DisplayName("Id"), Identity, IdProperty]
    public Int64? Id
    {
        get { return fields.Id[this]; }
        set { fields.Id[this] = value; }
    }

Done !

Let me know ( email to [email protected] ) if you have any ideas to improve this custom pager, thank you

Clone this wiki locally