Relate to some issues at here https://github.com/volkanceylan/Serenity/issues/4250 , https://github.com/volkanceylan/Serenity/issues/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

Thanks @minhhungit, this is really useful!
I'm also interested to see how/where you are tracking user actions?
@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
in next days I will update this tutorial, here is trailer for the updating

Hi everyone, here is new code, at this version we implement it as a "mixin"
btw, I am waiting this pull request https://github.com/volkanceylan/Serenity/pull/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 馃挅
@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
@JohnRanger I will update wiki soon, thank you !
I updated wiki, here is link: Grid Mixin Custom grid pager without counting total records
@minhhungit - thanks very much :-)
John
Just noticed one issue.

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 :

Did i miss something ?
@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
Issue occurs when you have others GRIDS in tabs (looks like the grids in the tabs inherit from the main grid)
@kilroyFR I can reproduce the issue now, will try to fix
@kilroyFR I fixed and updated wiki, pls check it
sorry I was quite busy so I couldn't reply you sooner
Wow Much Awesome, looks like it works like a charm. Many thanks !
Most helpful comment
Hi everyone, here is new code, at this version we implement it as a "mixin"
btw, I am waiting this pull request https://github.com/volkanceylan/Serenity/pull/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 codeMyBaseListRequest.cs
CustomPagerWithOnlyNextPreviousMixin.ts
Endpoint.cs
Repository.cs
Grid.ts
Feel free to ask any question 馃挅