Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { GridBase } from '@js/common/grids';
import type { DxPromise } from '@js/core/utils/deferred';
import { PagerModel } from '@ts/pagination/__tests__/__mock__/model/pager';

import { GridCoreModel } from './grid_core';

export abstract class DataGridBaseModel<
TInstance extends GridBase = GridBase,
> extends GridCoreModel<TInstance> {
public getPager(): PagerModel {
return new PagerModel(this.root, `.${this.addWidgetPrefix('pager')}`);
}

public apiColumnOption(id: string, name?: string, value?: any): any {
const instance = this.getInstance();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { normalizeWidth } from '../views/columns_view';
import type { RowsView } from '../views/rows_view';
import type { RowsViewScrollEvent } from '../views/types';
import { CLASSES } from './const';
import type { ColumnFixingRowsViewExtension } from './types';

const FIRST_CELL_CLASS = 'dx-first-cell';
const LAST_CELL_CLASS = 'dx-last-cell';
Expand Down Expand Up @@ -91,7 +92,7 @@ const normalizeColumnWidths = function (fixedColumns, widths: number[], fixedWid

// View
const baseFixedColumns = <T extends ModuleType<ColumnsView>>(Base: T) => class BaseFixedColumnsExtender extends Base {
protected _isFixedTableRendering!: boolean;
public _isFixedTableRendering!: boolean;

protected _isFixedColumns!: boolean;

Expand Down Expand Up @@ -681,7 +682,11 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH
}
};

const rowsView = (Base: ModuleType<RowsView>) => class RowsViewFixedColumnsExtender extends baseFixedColumns(Base) {
const rowsView = (
Base: ModuleType<RowsView>,
): ModuleType<
RowsView & ColumnFixingRowsViewExtension
> => class RowsViewFixedColumnsExtender extends baseFixedColumns(Base) {
protected _fixedScrollTimeout: any;

public dispose() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { RowsView } from '@ts/grids/grid_core/views/rows_view';

export interface ColumnFixingRowsViewExtension {
_isFixedTableRendering: boolean;
}

export type ColumnFixingRowsView = RowsView & ColumnFixingRowsViewExtension;
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ import {

const ITEMS = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }];

interface Pagination {
option: (name: string) => unknown;
}

const getPagination = (instance: DataGridInstance): Pagination => (
instance.getView('pagerView') as unknown as { getPager: () => Pagination }
).getPager();

const isPagerVisible = (instance: DataGridInstance): boolean => instance
.getView('pagerView')
.isVisible();
Expand All @@ -29,13 +21,14 @@ describe('PagerView', () => {

describe('without a data source', () => {
it('reports one page and no items', async () => {
const { instance } = await createDataGrid({
const { component } = await createDataGrid({
pager: { visible: true },
});
const pagination = component.getPager().getInstance();

expect(getPagination(instance).option('pageCount')).toBe(1);
expect(getPagination(instance).option('itemCount')).toBe(0);
expect(getPagination(instance).option('hasKnownLastPage')).toBe(true);
expect(pagination.option('pageCount')).toBe(1);
expect(pagination.option('itemCount')).toBe(0);
expect(pagination.option('hasKnownLastPage')).toBe(true);
});

it('is hidden in auto mode', async () => {
Expand All @@ -49,15 +42,16 @@ describe('PagerView', () => {

describe('with a data source', () => {
it('reports the page count and the item count', async () => {
const { instance } = await createDataGrid({
const { component } = await createDataGrid({
dataSource: ITEMS,
paging: { pageSize: 2 },
pager: { visible: true },
});
const pagination = component.getPager().getInstance();

expect(getPagination(instance).option('pageCount')).toBe(3);
expect(getPagination(instance).option('itemCount')).toBe(5);
expect(getPagination(instance).option('hasKnownLastPage')).toBe(true);
expect(pagination.option('pageCount')).toBe(3);
expect(pagination.option('itemCount')).toBe(5);
expect(pagination.option('hasKnownLastPage')).toBe(true);
});

it('is visible in auto mode when there is more than one page', async () => {
Expand Down
150 changes: 76 additions & 74 deletions packages/devextreme/js/__internal/grids/grid_core/pager/m_pager.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
import messageLocalization from '@js/common/core/localization/message';
import { isDefined } from '@js/core/utils/type';
import { hasWindow } from '@js/core/utils/window';
import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller';
import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller';
import type { KeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_keyboard_navigation';
import type { InternalGridOptions, OptionChanged } from '@ts/grids/grid_core/types';
import type { PaginationProps } from '@ts/pagination/common/pagination_props';
import Pagination from '@ts/pagination/wrappers/pagination';

import modules from '../modules/modules';
import type { PageSizes } from './types';

const PAGER_CLASS = 'pager';
export const MAX_PAGES_COUNT = 10;

const getPageIndex = function (dataController) {
// eslint-disable-next-line radix
return 1 + (parseInt(dataController.pageIndex()) || 0);
};

// TODO getController
export class PagerView extends modules.View {
private _pager: any;
private _pager?: Pagination | null;

private _pageSizes?: PageSizes | null;

private _pageSizes: any;
private dataController!: DataController;

private dataSourceController!: DataSourceController;

public init() {
const dataController = this.getController('data');
private keyboardNavigationController!: KeyboardNavigationController;

public init(): void {
this.dataController = this.getController('data');
this.dataSourceController = this.getController('dataSource');
this.keyboardNavigationController = this.getController('keyboardNavigation');

dataController.changed.add((e) => {
this.dataController.changed.add((e) => {
if (e && e.repaintChangesOnly) {
const pager = this._pager;
if (pager) {
pager.option({
pageIndex: getPageIndex(dataController),
pageSize: dataController.pageSize(),
pageIndex: this.getPageIndex(),
pageSize: this.dataController.pageSize(),
pageCount: this.dataSourceController.pageCount(),
itemCount: this.dataSourceController.totalCount(),
hasKnownLastPage: this.dataSourceController.hasKnownLastPage(),
Expand All @@ -48,22 +52,20 @@ export class PagerView extends modules.View {
});
}

public dispose() {
public dispose(): void {
this._pager = null;
}

public optionChanged(args) {
const { name } = args;
const isPager = name === 'pager';
const isPaging = name === 'paging';
const isDataSource = name === 'dataSource';
const isScrolling = name === 'scrolling';
const dataController = this.getController('data');
public optionChanged(args: OptionChanged): void {
const isPager = args.name === 'pager';
const isPaging = args.name === 'paging';
const isDataSource = args.name === 'dataSource';
const isScrolling = args.name === 'scrolling';

if (isPager || isPaging || isScrolling || isDataSource) {
args.handled = true;

if (dataController.skipProcessingPagingChange(args.fullName)) {
if (this.dataController.skipProcessingPagingChange(args.fullName)) {
return;
}

Expand All @@ -75,47 +77,51 @@ export class PagerView extends modules.View {
this._pager = null;
this._invalidate();
if (hasWindow() && isPager && this.component) {
// @ts-expect-error
// @ts-expect-error the resizing controller's publicMethods() add resize()
this.component.resize();
}
}
}
}

protected _renderCore() {
const that = this;
// @ts-expect-error the view is rendered here
const $element = that.element().addClass(that.addWidgetPrefix(PAGER_CLASS));
const pagerOptions = that.option('pager') ?? {};
const dataController = that.getController('data');
const keyboardController = that.getController('keyboardNavigation');
const options: any = {
protected _renderCore(): void {
const $element = this.element();

if (!$element) {
return;
}

$element.addClass(this.addWidgetPrefix(PAGER_CLASS));
const pagerOptions = this.option('pager') ?? {};
const options: Partial<PaginationProps> = {
maxPagesCount: MAX_PAGES_COUNT,
pageIndex: getPageIndex(dataController),
pageCount: that.dataSourceController.pageCount(),
pageSize: dataController.pageSize(),
pageIndex: this.getPageIndex(),
pageCount: this.dataSourceController.pageCount(),
pageSize: this.dataController.pageSize(),
showPageSizeSelector: pagerOptions.showPageSizeSelector,
showInfo: pagerOptions.showInfo,
displayMode: pagerOptions.displayMode,
pagesNavigatorVisible: pagerOptions.visible,
showNavigationButtons: pagerOptions.showNavigationButtons,
label: pagerOptions.label,
allowedPageSizes: that.getPageSizes(),
itemCount: that.dataSourceController.totalCount(),
hasKnownLastPage: that.dataSourceController.hasKnownLastPage(),
rtlEnabled: that.option('rtlEnabled'),
// @ts-expect-error the grid's page sizes allow 'auto', Pagination's don't
allowedPageSizes: this.getPageSizes(),
itemCount: this.dataSourceController.totalCount(),
hasKnownLastPage: this.dataSourceController.hasKnownLastPage(),
rtlEnabled: this.option('rtlEnabled'),
isGridCompatibilityMode: true,
_getParentComponentRootNode: () => this.component.element(),
_skipValidation: true,
pageIndexChanged(pageIndex) {
if (dataController.pageIndex() !== pageIndex - 1) {
dataController.pageIndex(pageIndex - 1);
pageIndexChanged: (pageIndex) => {
if (this.dataController.pageIndex() !== pageIndex - 1) {
this.dataController.pageIndex(pageIndex - 1);
}
},
pageSizeChanged(pageSize) {
dataController.pageSize(pageSize);
pageSizeChanged: (pageSize) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- fire-and-forget
this.dataController.pageSize(pageSize);
},
onKeyDown: (e) => keyboardController && keyboardController.executeAction('onKeyDown', e),
onKeyDown: (e) => this.keyboardNavigationController?.executeAction('onKeyDown', e),
};

if (isDefined(pagerOptions.infoText)) {
Expand All @@ -128,62 +134,58 @@ export class PagerView extends modules.View {
}

if (hasWindow()) {
this._pager = that._createComponent($element, Pagination, options);
this._pager = this._createComponent($element, Pagination, options);
} else {
$element
.addClass('dx-pager')
.html('<div class="dx-pages"><div class="dx-page"></div></div>');
}
}

private getPager() {

@anna-shakhova anna-shakhova Oct 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was used in tests only. Replaced with getInstance in tests

return this._pager;
private getPageIndex(): number {
return 1 + (parseInt(String(this.dataController.pageIndex()), 10) || 0);
}

private getPageSizes() {
const that = this;
const dataController = that.getController('data');
const pagerOptions = that.option('pager');
const allowedPageSizes = pagerOptions && pagerOptions.allowedPageSizes;
private getPageSizes(): PageSizes {
// state storing calls this before init() if the data loads synchronously
const dataController = this.getController('data');
const pagerOptions = this.option('pager');
const allowedPageSizes = pagerOptions?.allowedPageSizes;
const pageSize = dataController.pageSize();

if (!isDefined(that._pageSizes) || !that._pageSizes.includes(pageSize)) {
that._pageSizes = [];
if (!isDefined(this._pageSizes) || !this._pageSizes.includes(pageSize)) {
this._pageSizes = [];
if (pagerOptions) {
if (Array.isArray(allowedPageSizes)) {
that._pageSizes = allowedPageSizes;
this._pageSizes = allowedPageSizes;
} else if (allowedPageSizes && pageSize > 1) {
that._pageSizes = [Math.floor(pageSize / 2), pageSize, pageSize * 2];
this._pageSizes = [Math.floor(pageSize / 2), pageSize, pageSize * 2];
}
}
}
return that._pageSizes;
return this._pageSizes;
}

public isVisible() {
const dataController = this.getController('data');
const pagerOptions = this.option('pager');
let pagerVisible = pagerOptions && pagerOptions.visible;
const scrolling = this.option('scrolling');

if (pagerVisible === 'auto') {
if (scrolling && (scrolling.mode === 'virtual' || scrolling.mode === 'infinite')) {
pagerVisible = false;
} else {
pagerVisible = this.dataSourceController.pageCount() > 1
|| (dataController.isLoaded() && !this.dataSourceController.hasKnownLastPage());
}
public isVisible(): boolean {
const pagerVisible = this.option('pager')?.visible;

if (pagerVisible !== 'auto') {
return !!pagerVisible;
}

const scrollingMode = this.option('scrolling')?.mode;

if (scrollingMode === 'virtual' || scrollingMode === 'infinite') {
return false;
}
return !!pagerVisible;
}

private getHeight() {
return this.getElementHeight();
return this.dataSourceController.pageCount() > 1
|| (this.dataController.isLoaded() && !this.dataSourceController.hasKnownLastPage());
}
}

export const pagerModule = {
defaultOptions() {
defaultOptions(): Pick<InternalGridOptions, 'pager'> {
return {
pager: {
visible: 'auto',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { PagerPageSize } from '@js/common/grids';

export type PageSizes = (number | PagerPageSize)[];
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
/* eslint-disable consistent-return */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import $ from '@js/core/renderer';
import gridCoreUtils from '@ts/grids/grid_core/utils';

// eslint-disable-next-line @typescript-eslint/no-restricted-imports,
import gridCoreUtils from '../utils';
import { CLASSES } from './const';
import type { HandleCellTemplate } from './types';

const createHandleTemplateFunc = (addWidgetPrefix) => (container, options) => {
const createHandleTemplateFunc = (
addWidgetPrefix: (className: string) => string,
): HandleCellTemplate => (container, options) => {
const $container = $(container);

if (options.rowType === 'data') {
Expand Down
Loading
Loading