Skip to content

Grid Mixin Custom grid pager without counting total records

Jin edited this page Apr 3, 2022 · 2 revisions

!!! Note: Check version 2 here: https://github.com/serenity-is/Serenity/wiki/Custom-pager-with-only-next-previous-mode-(version-2)

Demo

Demo


site.less

.slick-pg-in{
    width: 100% !important;
}

MyBaseListRequest.cs

public interface IPagingRequest
{
    bool EnableOnlyNextPreviousMode { get; set; }
}

public class MyBaseListRequest : ListRequest, IPagingRequest
{
    public bool EnableOnlyNextPreviousMode { get; set; }
}

CustomPagerWithOnlyNextPreviousMixin.ts

namespace [YOUR_NAME_SPACE].Common {
    export class CustomPagerWithOnlyNextPreviousMixin<TItem> {

        private options: CustomPagerWithOnlyNextPreviousMixinOptions<TItem>;
        private dataGrid: Serenity.DataGrid<TItem, any>;

        private _customPagerCurrentPage: number = 1;
        private _customPager: JQuery = null;
        private _originalPager = null;
        private _pagingMode: ('full' | 'next-previous-only');
        private _btnSwitch: JQuery = null;

        constructor(options: CustomPagerWithOnlyNextPreviousMixinOptions<TItem>) {

            var self = this;
            this.options = options;
            var dg = this.dataGrid = options.grid;
            this._pagingMode = options.pagingMode = options.pagingMode || 'next-previous-only';

            if (this._originalPager == null) {
                this._originalPager = this.options.grid.element.find(".s-SlickPager");
            }

            this.options.grid.element.find(".slick-pg-in").hide();

            if (this._customPager == null) {
                this._customPager = $("<span class='next-previous-pager'><button class='custompager-pre'><strong>«</strong> Previous</button><span style='padding: 0 2px;'></span><button class='custompager-next'>Next <strong>»</strong></button><span style='padding: 0 2px;'></span><b>Page</b> <span class='custompager-curpage'>1</span></span>");
                this._originalPager.find(".slick-pg-in").append(this._customPager);
            }

            if (this._btnSwitch == null) {
                this._btnSwitch = $(`<input type="checkbox" title="Full Pager" class="paging-mode-switch pull-right" style="margin-right: 5px; cursor: pointer" ${(options.pagingMode == "full" ? ' checked' : '')}>`);

                this._btnSwitch.appendTo(dg.element.find(".slick-pg-in"));

                this._btnSwitch.change((evt) => {

                    var isFullMode: boolean = $(evt.target).is(":checked");

                    

                    // update current page number
                    if (!isFullMode) {                        
                        this._customPagerCurrentPage = parseInt(this.options.grid.element.find(".slick-pg-current").val());
                        this._originalPager.find(".custompager-curpage").text(this.options.grid.element.find(".slick-pg-current").val());
                    }

                    this.switchView(isFullMode ? 'full' : 'next-previous-only');
                });
            }

            this._originalPager.find(".custompager-pre").click(e => {
                if (this._customPagerCurrentPage > 1) {
                    this._customPagerCurrentPage--;
                    this.dataGrid.view.seekToPage = this._customPagerCurrentPage;
                    this.dataGrid.refresh();
                    this._originalPager.find(".custompager-curpage").text(this._customPagerCurrentPage);
                }
                return;
            });

            this._originalPager.find(".custompager-next").click(e => {
                this._customPagerCurrentPage++;
                this.dataGrid.view.seekToPage = this._customPagerCurrentPage;
                this.dataGrid.refresh();
                this._originalPager.find(".custompager-curpage").text(this._customPagerCurrentPage);
                return;
            });

            dg.view.onDataChanged.subscribe(() => {
                this.updatePageControls(!$(this._btnSwitch).is(":checked"));
            });

            // save setting
            var oldCurrentSettings = (dg as any).getCurrentSettings;
            
            (dg as any).getCurrentSettings = function (flag) {
                var settings = oldCurrentSettings.apply(dg, [flag]);
                settings['customPagerMode'] = $(self._btnSwitch).is(":checked") ? 'full' : 'next-previous-only';
                
                return settings;
            };

            var oldRestoreSettings = (dg as any).restoreSettings;

            (dg as any).restoreSettings = function (settings, flags) {
                oldRestoreSettings.apply(dg, [settings, flags]);
                if (settings == null) {
                    var storage = this.getPersistanceStorage();
                    if (storage == null) {
                        self.switchView(self._pagingMode);
                        return;
                    }
                    var json = Q.trimToNull(storage.getItem(this.getPersistanceKey()));
                    if (!json) {
                        self.switchView(self._pagingMode);
                        return;
                    }
                    settings = JSON.parse(json);
                }


                var viewPagerMode = settings.customPagerMode || self._pagingMode;
                var currentViewPagerMode = $(self._btnSwitch).is(":checked") ? 'full' : 'next-previous-only';

                if (viewPagerMode != currentViewPagerMode) {
                    $(self._btnSwitch).click();
                }
            };
        }

        public updateNextButton(nbrOfRecords: number, nbrOfRowsPerPage: number): void {
            if (this.options.pagingMode === 'full') {
                return;
            }

            if (nbrOfRecords == 0 || nbrOfRecords < nbrOfRowsPerPage) {
                this._originalPager.find(".custompager-next").prop("disabled", true);
                this._originalPager.find(".custompager-next").css("opacity", 0.5);
            }
            else {
                this._originalPager.find(".custompager-next").prop("disabled", false);
                this._originalPager.find(".custompager-next").css("opacity", 1);
            }
        }

        private switchView(pMode: ('full' | 'next-previous-only')): void {
            this.updatePageControls(pMode == "next-previous-only");
            this.dataGrid.refresh();
            (this.dataGrid as any).persistSettings();
        }

        private updatePageControls(isNextPreviousOnlyMode: boolean) {
            if (isNextPreviousOnlyMode) {
                this._originalPager.find(".next-previous-pager").show();
                this._originalPager.find(".slick-pg-grp").hide();
                this._originalPager.find(".slick-pg-sep").hide();
                this._originalPager.find(".slick-pg-grp:first").show();
            }
            else {
                this._originalPager.find(".next-previous-pager").hide();
                this._originalPager.find(".slick-pg-grp").show();
                this._originalPager.find(".slick-pg-sep").show();
            }

            this.options.grid.element.find(".slick-pg-in").show();
        }

        public getCurrentPagerMode(): ('full' | 'next-previous-only') {
            return $(this._btnSwitch).is(":checked") ? 'full' : 'next-previous-only';
        }
    }

    export class CustomPagerWithOnlyNextPreviousMixinOptions<TItem> {
        grid: Serenity.DataGrid<TItem, any>;
        rowPerPage: number;
        pagingMode?: ('full' | 'next-previous-only');
    }
}

Endpoint.cs

public ListResponse<MyRow> List(IDbConnection connection, Common.MyBaseListRequest request)
{
    return new MyRepository().List(connection, request);
}

Repository.cs

public ListResponse<MyRow> List(IDbConnection connection, Common.MyBaseListRequest request)
{
    return new MyListHandler().Process(connection, request);
}

private class MyListHandler : CustomListRequestHandle<MyRow> { }

public class CustomListRequestHandle<TRow> : ListRequestHandler<TRow> where TRow : Row, new()
{
    protected override void ApplyFilters(SqlQuery query)
    {
        base.ApplyFilters(query);

        if (Request is Common.MyBaseListRequest customRequest)
        {
            if (customRequest.EnableOnlyNextPreviousMode)
            {
                query.ApplySkipTakeAndCount(this.Request.Skip, this.Request.Take, this.Request.ExcludeTotalCount || DistinctFields != null);

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

Grid.ts

/// <reference path="../../Common/Mixin/CustomPagerWithOnlyNextPreviousMixin.ts" />

private _pagerMixin: Common.CustomPagerWithOnlyNextPreviousMixin<Your_Row>;

protected onViewProcessData(response: Serenity.ListResponse<Your_Row>): Serenity.ListResponse<Your_Row> {
    var lr = super.onViewProcessData(response);

    this._pagerMixin.updateNextButton(lr.Entities.length, response.Take);

    return lr;
}

protected getViewOptions() {
    var opt = super.getViewOptions();
    opt.rowsPerPage = 20;
    return opt;
}

protected createToolbarExtensions(): void {
    super.createToolbarExtensions();
    var self = this;

    this._pagerMixin = new Rydell.Web.Common.CustomPagerWithOnlyNextPreviousMixin({
        grid: this,
        rowPerPage: this.getPagerOptions().rowsPerPage
    });
}

protected onViewSubmit() {
    if (!super.onViewSubmit()) {
        return false;
    }
    var request = this.view.params as Common.MyBaseListRequest;
    request.EnableOnlyNextPreviousMode = this._pagerMixin.getCurrentPagerMode() == 'next-previous-only';
    return true;
}

protected getPersistanceStorage(): Serenity.SettingStorage {
    return new Common.UserPreferenceStorage();
}

Credits: @minhhungit

Clone this wiki locally