Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tutorial] Custom grid & grid pager without counting total records #4458

Closed
minhhungit opened this issue May 24, 2019 · 15 comments
Closed

[Tutorial] Custom grid & grid pager without counting total records #4458

minhhungit opened this issue May 24, 2019 · 15 comments

Comments

@minhhungit
Copy link
Contributor

minhhungit commented May 24, 2019

Relate to some issues at here #4250 , #4399

I created a post to demo how to create a custom grid which just has only next/previous buttons, it will not count total records so in some cases (ex, table with many records) we won't worry about timeout issue

Here it is: Grid Mixin Custom grid pager without counting total records

Screenshot
image

@edwardch
Copy link
Contributor

Thanks @minhhungit, this is really useful!

I'm also interested to see how/where you are tracking user actions?

@minhhungit
Copy link
Contributor Author

@edwardch it's not complex, just put your "insert log" code into your methods, it will insert new record into a "action log" table, the rest, serenity will generate form for you

@minhhungit
Copy link
Contributor Author

minhhungit commented Jun 6, 2019

in next days I will update this tutorial, here is trailer for the updating

trailer

@minhhungit
Copy link
Contributor Author

Hi everyone, here is new code, at this version we implement it as a "mixin"

btw, I am waiting this pull request #4474, if that PR is approved then we can use CustomData and we don't need to create MyBaseListRequest, we also don't need to update parameter for Endpoint/Repository List method, therefore we will write less code


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 = $("<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>");
        private _originalPager = $(".s-SlickPager");
        private _pagingMode: ('full' | 'next-previous-only');
        private _btnSwitch: JQuery;

        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';
            $(".slick-pg-in").hide();

            this._originalPager.find(".slick-pg-in").append(this._customPager);

            var btnSwitch = this._btnSwitch = $('<input type="checkbox" title="Full Pager" class="paging-mode-switch pull-right" style="margin-right: 5px" ' + (options.pagingMode == "full" ? ' checked' : '') + '/>')
                .appendTo(dg.element.find(".slick-pg-in"));

            btnSwitch.change((evt) => {

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

                // update current page number
                if (!isFullMode) {
                    this._customPagerCurrentPage = parseInt($(".slick-pg-current").val());
                    this._originalPager.find(".custompager-curpage").text($(".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'] = $(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 = $(btnSwitch).is(":checked") ? 'full' : 'next-previous-only';

                if (viewPagerMode != currentViewPagerMode) {
                    $(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();
            }

            $(".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, MyBaseListRequest request)
{
    return new MyRepository().List(connection, request);
}

Repository.cs

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

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

        if (Request is 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 MyBaseListRequest;
    request.EnableOnlyNextPreviousMode = this._pagerMixin.getCurrentPagerMode() == 'next-previous-only';
    return true;
}

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

Feel free to ask any question 💖

@JohnRanger
Copy link

JohnRanger commented Jun 14, 2019

@minhhungit ,

I have allowed myself to change the title of your wiki entry to: Grid: .... (categorized it under Grid for being better findable)

Maybe you want to update your link in this post to: https://github.com/volkanceylan/Serenity/wiki/Grid:-Custom-grid-&-grid-pager-without-counting-total-records

And if you don't mind: Put also your mixin code into your wiki - as you say there that it is beter to do a mixin. :-)

With kind regards,

John

@minhhungit
Copy link
Contributor Author

@JohnRanger I will update wiki soon, thank you !

@minhhungit
Copy link
Contributor Author

I updated wiki, here is link: Grid Mixin Custom grid pager without counting total records

@JohnRanger
Copy link

@minhhungit - thanks very much :-)

John

@kilroyFR
Copy link

Just noticed one issue.
image

When i click on Edit button (form mode) then switch back to grid, then an additional set of previous/next buttons are displayed. On this screenshot, i switched several times and got this screen (all buttons act the same but are duplicated).
When clicking on button i am getting a :
image

Did i miss something ?

@minhhungit
Copy link
Contributor Author

@kilroyFR I can not reproduce it, I tried some times on some my projects with I don't your problem.
Can you provide your code, in private mode if you want, I might can help

@kilroyFR
Copy link

Issue occurs when you have others GRIDS in tabs (looks like the grids in the tabs inherit from the main grid)

@minhhungit
Copy link
Contributor Author

@kilroyFR I can reproduce the issue now, will try to fix

@minhhungit
Copy link
Contributor Author

minhhungit commented Nov 21, 2019

@minhhungit
Copy link
Contributor Author

sorry I was quite busy so I couldn't reply you sooner

@kilroyFR
Copy link

Wow Much Awesome, looks like it works like a charm. Many thanks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants