diff --git a/packages/pluggableWidgets/datagrid-web/src/components/GridBody.tsx b/packages/pluggableWidgets/datagrid-web/src/components/GridBody.tsx
index a8ededee80..7be72dd1fe 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/GridBody.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/GridBody.tsx
@@ -5,7 +5,7 @@ import {
useDatagridConfig,
useItemCount,
useLoaderViewModel,
- usePaginationService,
+ usePaginationVM,
useVisibleColumnsCount
} from "../model/hooks/injection-hooks";
import { useBodyScroll } from "../model/hooks/useBodyScroll";
@@ -31,7 +31,7 @@ export const GridBody = observer(function GridBody(props: PropsWithChildren): Re
const ContentGuard = observer(function ContentGuard(props: PropsWithChildren): ReactNode {
const loaderVM = useLoaderViewModel();
- const { pageSize } = usePaginationService();
+ const { pageSize } = usePaginationVM();
const config = useDatagridConfig();
const columnsCount = useVisibleColumnsCount().get();
const itemCount = useItemCount().get();
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/Pagination.tsx b/packages/pluggableWidgets/datagrid-web/src/components/Pagination.tsx
index fb8200c6d8..0068a2b798 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/Pagination.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/Pagination.tsx
@@ -1,10 +1,10 @@
import { Pagination as PaginationComponent } from "@mendix/widget-plugin-grid/components/Pagination";
import { observer } from "mobx-react-lite";
import { ReactNode } from "react";
-import { usePaginationService } from "../model/hooks/injection-hooks";
+import { usePaginationVM } from "../model/hooks/injection-hooks";
export const Pagination = observer(function Pagination(): ReactNode {
- const paging = usePaginationService();
+ const paging = usePaginationVM();
if (!paging.paginationVisible) return null;
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetFooter.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetFooter.tsx
index 4f5211c4b7..867b35f75b 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetFooter.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetFooter.tsx
@@ -3,14 +3,15 @@ import { observer } from "mobx-react-lite";
import { ReactElement } from "react";
import { SelectionCounter } from "../features/selection-counter/SelectionCounter";
import { useSelectionCounterViewModel } from "../features/selection-counter/injection-hooks";
-import { useDatagridConfig, usePaginationService, useTexts } from "../model/hooks/injection-hooks";
+import { useCustomPagination, usePaginationConfig, usePaginationVM, useTexts } from "../model/hooks/injection-hooks";
import { Pagination } from "./Pagination";
export const WidgetFooter = observer(function WidgetFooter(): ReactElement {
- const config = useDatagridConfig();
- const paging = usePaginationService();
+ const pgConfig = usePaginationConfig();
+ const paging = usePaginationVM();
const { loadMoreButtonCaption } = useTexts();
const selectionCounterVM = useSelectionCounterViewModel();
+ const customPagination = useCustomPagination();
return (
@@ -32,9 +33,10 @@ export const WidgetFooter = observer(function WidgetFooter(): ReactElement {
-
+
+ {customPagination.get()}
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx
index 390194bc0c..1d7df2783b 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx
@@ -3,11 +3,11 @@ import { observer } from "mobx-react-lite";
import { ReactElement } from "react";
import { SelectionCounter } from "../features/selection-counter/SelectionCounter";
import { useSelectionCounterViewModel } from "../features/selection-counter/injection-hooks";
-import { useDatagridConfig } from "../model/hooks/injection-hooks";
+import { usePaginationConfig } from "../model/hooks/injection-hooks";
import { Pagination } from "./Pagination";
export const WidgetTopBar = observer(function WidgetTopBar(): ReactElement {
- const config = useDatagridConfig();
+ const pgConfig = usePaginationConfig();
const selectionCounter = useSelectionCounterViewModel();
return (
@@ -19,7 +19,7 @@ export const WidgetTopBar = observer(function WidgetTopBar(): ReactElement {
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/pagination/DynamicPagination.feature.ts b/packages/pluggableWidgets/datagrid-web/src/features/pagination/DynamicPagination.feature.ts
new file mode 100644
index 0000000000..dc71cd4b18
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/pagination/DynamicPagination.feature.ts
@@ -0,0 +1,54 @@
+import { ComputedAtom, disposeBatch, SetupComponent, SetupComponentHost } from "@mendix/widget-plugin-mobx-kit/main";
+import { autorun, reaction } from "mobx";
+import { GridPageControl } from "./GridPageControl";
+
+export class DynamicPaginationFeature implements SetupComponent {
+ id = "DynamicPaginationFeature";
+ constructor(
+ host: SetupComponentHost,
+ private config: { dynamicPageSizeEnabled: boolean; dynamicPageEnabled: boolean },
+ private dynamicPage: ComputedAtom,
+ private dynamicPageSize: ComputedAtom,
+ private totalCount: ComputedAtom,
+ private service: GridPageControl
+ ) {
+ host.add(this);
+ }
+
+ setup(): () => void {
+ const [add, disposeAll] = disposeBatch();
+
+ if (this.config.dynamicPageSizeEnabled) {
+ add(
+ reaction(
+ () => this.dynamicPageSize.get(),
+ pageSize => {
+ if (pageSize < 0) return;
+ this.service.setPageSize(pageSize);
+ },
+ { delay: 250 }
+ )
+ );
+ }
+
+ if (this.config.dynamicPageEnabled) {
+ add(
+ reaction(
+ () => this.dynamicPage.get(),
+ page => {
+ if (page < 0) return;
+ this.service.setPage(page);
+ },
+ { delay: 250 }
+ )
+ );
+ add(
+ autorun(() => {
+ this.service.setTotalCount(this.totalCount.get());
+ })
+ );
+ }
+
+ return disposeAll;
+ }
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/pagination/GridPageControl.ts b/packages/pluggableWidgets/datagrid-web/src/features/pagination/GridPageControl.ts
new file mode 100644
index 0000000000..27008c6d17
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/pagination/GridPageControl.ts
@@ -0,0 +1,5 @@
+export interface GridPageControl {
+ setPage(page: number): void;
+ setPageSize(pageSize: number): void;
+ setTotalCount(totalCount: number): void;
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/pagination/PageControl.service.ts b/packages/pluggableWidgets/datagrid-web/src/features/pagination/PageControl.service.ts
new file mode 100644
index 0000000000..69c7cf9a3c
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/pagination/PageControl.service.ts
@@ -0,0 +1,29 @@
+import { SetPageAction, SetPageSizeAction } from "@mendix/widget-plugin-grid/main";
+import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/main";
+import { Big } from "big.js";
+import { EditableValue } from "mendix";
+import { GridPageControl } from "./GridPageControl";
+
+export class PageControlService implements GridPageControl {
+ constructor(
+ private gate: DerivedPropsGate<{
+ totalCountValue?: EditableValue;
+ }>,
+ private setPageSizeAction: SetPageSizeAction,
+ private setPageAction: SetPageAction
+ ) {}
+
+ setPageSize(pageSize: number): void {
+ this.setPageSizeAction(pageSize);
+ }
+
+ setPage(page: number): void {
+ this.setPageAction(page);
+ }
+
+ setTotalCount(count: number): void {
+ const value = this.gate.props.totalCountValue;
+ if (!value || value.readOnly) return;
+ value.setValue(new Big(count));
+ }
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/pagination/Pagination.viewModel.ts b/packages/pluggableWidgets/datagrid-web/src/features/pagination/Pagination.viewModel.ts
new file mode 100644
index 0000000000..73dbacb148
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/pagination/Pagination.viewModel.ts
@@ -0,0 +1,65 @@
+import { QueryService, SetPageAction } from "@mendix/widget-plugin-grid/main";
+import { ComputedAtom } from "@mendix/widget-plugin-mobx-kit/main";
+import { computed, makeObservable } from "mobx";
+import { PaginationEnum, ShowPagingButtonsEnum } from "../../../typings/DatagridProps";
+import { PaginationConfig } from "./pagination.config";
+
+export class PaginationViewModel {
+ readonly pagination: PaginationEnum;
+ readonly showPagingButtons: ShowPagingButtonsEnum;
+
+ constructor(
+ private config: PaginationConfig,
+ private query: QueryService,
+ private currentPageAtom: ComputedAtom,
+ private pageSizeAtom: ComputedAtom,
+ private setPageAction: SetPageAction
+ ) {
+ this.pagination = config.pagination;
+ this.showPagingButtons = config.showPagingButtons;
+
+ makeObservable(this, {
+ pageSize: computed,
+ currentPage: computed,
+ paginationVisible: computed,
+ hasMoreItems: computed,
+ totalCount: computed
+ });
+ }
+
+ get pageSize(): number {
+ return this.pageSizeAtom.get();
+ }
+
+ get currentPage(): number {
+ return this.currentPageAtom.get();
+ }
+
+ get paginationVisible(): boolean {
+ switch (this.config.paginationKind) {
+ case "buttons.always":
+ return true;
+ case "buttons.auto": {
+ const { totalCount = -1 } = this.query;
+ return totalCount > this.query.limit;
+ }
+ case "custom": {
+ return false;
+ }
+ default:
+ return this.config.showNumberOfRows;
+ }
+ }
+
+ get hasMoreItems(): boolean {
+ return this.query.hasMoreItems;
+ }
+
+ get totalCount(): number | undefined {
+ return this.query.totalCount;
+ }
+
+ setPage: SetPageAction = value => {
+ this.setPageAction(value);
+ };
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/pagination/pagination.config.ts b/packages/pluggableWidgets/datagrid-web/src/features/pagination/pagination.config.ts
new file mode 100644
index 0000000000..9b66fc4efa
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/pagination/pagination.config.ts
@@ -0,0 +1,60 @@
+import { PaginationEnum, PagingPositionEnum, ShowPagingButtonsEnum } from "../../../typings/DatagridProps";
+import { MainGateProps } from "../../../typings/MainGateProps";
+
+export interface PaginationConfig {
+ pagination: PaginationEnum;
+ pagingPosition: PagingPositionEnum;
+ paginationKind: PaginationKind;
+ showPagingButtons: ShowPagingButtonsEnum;
+ showNumberOfRows: boolean;
+ constPageSize: number;
+ isLimitBased: boolean;
+ dynamicPageSizeEnabled: boolean;
+ dynamicPageEnabled: boolean;
+ customPaginationEnabled: boolean;
+ requestTotalCount: boolean;
+}
+
+export type PaginationKind = `${PaginationEnum}.${ShowPagingButtonsEnum}` | "custom";
+
+export function paginationConfig(props: MainGateProps): PaginationConfig {
+ const config: PaginationConfig = {
+ pagination: props.pagination,
+ showPagingButtons: props.showPagingButtons,
+ showNumberOfRows: props.showNumberOfRows,
+ constPageSize: props.pageSize,
+ isLimitBased: isLimitBased(props),
+ paginationKind: paginationKind(props),
+ dynamicPageSizeEnabled: dynamicPageSizeEnabled(props),
+ dynamicPageEnabled: dynamicPageEnabled(props),
+ customPaginationEnabled: props.useCustomPagination,
+ pagingPosition: props.pagingPosition,
+ requestTotalCount: requestTotalCount(props)
+ };
+
+ return Object.freeze(config);
+}
+
+export function paginationKind(props: MainGateProps): PaginationKind {
+ if (props.useCustomPagination) {
+ return "custom";
+ }
+
+ return `${props.pagination}.${props.showPagingButtons}`;
+}
+
+export function dynamicPageSizeEnabled(props: MainGateProps): boolean {
+ return props.dynamicPageSize !== undefined && !isLimitBased(props);
+}
+
+export function dynamicPageEnabled(props: MainGateProps): boolean {
+ return props.dynamicPage !== undefined && !isLimitBased(props);
+}
+
+function isLimitBased(props: MainGateProps): boolean {
+ return props.pagination === "virtualScrolling" || props.pagination === "loadMore";
+}
+
+function requestTotalCount(props: MainGateProps): boolean {
+ return props.pagination === "buttons" || props.showNumberOfRows;
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/features/pagination/pagination.model.ts b/packages/pluggableWidgets/datagrid-web/src/features/pagination/pagination.model.ts
new file mode 100644
index 0000000000..7d19cf3143
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/features/pagination/pagination.model.ts
@@ -0,0 +1,32 @@
+import { boundPageSize } from "@mendix/widget-plugin-grid/main";
+import { ComputedAtom, DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/main";
+import { computed } from "mobx";
+import { ReactNode } from "react";
+
+/** Atom for the dynamic page index provided by the widget's props. */
+export function dynamicPageAtom(
+ gate: DerivedPropsGate<{ dynamicPage?: { value?: Big } }>,
+ config: { isLimitBased: boolean }
+): ComputedAtom {
+ return computed(() => {
+ const page = gate.props.dynamicPage?.value?.toNumber() ?? -1;
+ if (config.isLimitBased) {
+ return Math.max(page, -1);
+ }
+ // Switch to zero-based index for offset-based pagination
+ return Math.max(page - 1, -1);
+ });
+}
+
+/** Atom for the dynamic page size. */
+export function dynamicPageSizeAtom(
+ gate: DerivedPropsGate<{ dynamicPageSize?: { value?: Big } }>
+): ComputedAtom {
+ return boundPageSize(() => gate.props.dynamicPageSize?.value?.toNumber() ?? -1);
+}
+
+export function customPaginationAtom(
+ gate: DerivedPropsGate<{ customPagination?: ReactNode }>
+): ComputedAtom {
+ return computed(() => gate.props.customPagination);
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/configs/Datagrid.config.ts b/packages/pluggableWidgets/datagrid-web/src/model/configs/Datagrid.config.ts
index 1ce8da4b69..4933b2380c 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/configs/Datagrid.config.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/configs/Datagrid.config.ts
@@ -1,6 +1,6 @@
import { SelectionMode, SelectionType } from "@mendix/widget-plugin-grid/selection";
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
-import { DatagridContainerProps, LoadingTypeEnum, PagingPositionEnum } from "../../../typings/DatagridProps";
+import { DatagridContainerProps, LoadingTypeEnum } from "../../../typings/DatagridProps";
import { type SelectionMethod } from "../../features/row-interaction/base";
/** Config for static values that don't change at runtime. */
@@ -19,7 +19,6 @@ export interface DatagridConfig {
settingsStorageEnabled: boolean;
enableSelectAll: boolean;
keepSelection: boolean;
- pagingPosition: PagingPositionEnum;
multiselectable: true | undefined;
loadingType: LoadingTypeEnum;
columnsDraggable: boolean;
@@ -48,7 +47,6 @@ export function datagridConfig(props: DatagridContainerProps): DatagridConfig {
settingsStorageEnabled: isSettingsStorageEnabled(props),
enableSelectAll: props.enableSelectAll,
keepSelection: props.keepSelection,
- pagingPosition: props.pagingPosition,
multiselectable: isMultiselectable(props),
loadingType: props.loadingType,
columnsHidable: props.columnsHidable,
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/configs/__tests__/config.spec.ts b/packages/pluggableWidgets/datagrid-web/src/model/configs/__tests__/config.spec.ts
index 207fc69827..1133f5163d 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/configs/__tests__/config.spec.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/configs/__tests__/config.spec.ts
@@ -20,7 +20,6 @@ describe("datagridConfig", () => {
loadingType: "spinner",
multiselectable: undefined,
name: "datagrid2_1",
- pagingPosition: "bottom",
refreshIntervalMs: 0,
selectAllCheckboxEnabled: true,
selectionEnabled: false,
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts b/packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts
index fd99730e77..58431651d9 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts
@@ -6,8 +6,12 @@ import {
createClickActionHelper,
createFocusController,
createSelectionHelper,
+ createSetPageAction,
+ createSetPageSizeAction,
+ currentPageAtom,
DatasourceService,
layoutAtom,
+ pageSizeAtom,
SelectActionsProvider,
TaskProgressService
} from "@mendix/widget-plugin-grid/main";
@@ -18,6 +22,11 @@ import { Container, injected } from "brandi";
import { MainGateProps } from "../../../typings/MainGateProps";
import { WidgetRootViewModel } from "../../features/base/WidgetRoot.viewModel";
import { EmptyPlaceholderViewModel } from "../../features/empty-message/EmptyPlaceholder.viewModel";
+import { DynamicPaginationFeature } from "../../features/pagination/DynamicPagination.feature";
+import { PageControlService } from "../../features/pagination/PageControl.service";
+import { paginationConfig } from "../../features/pagination/pagination.config";
+import { customPaginationAtom, dynamicPageAtom, dynamicPageSizeAtom } from "../../features/pagination/pagination.model";
+import { PaginationViewModel } from "../../features/pagination/Pagination.viewModel";
import { createCellEventsController } from "../../features/row-interaction/CellEventsController";
import { creteCheckboxEventsController } from "../../features/row-interaction/CheckboxEventsController";
import { SelectAllModule } from "../../features/select-all/SelectAllModule.container";
@@ -29,7 +38,6 @@ import { gridStyleAtom } from "../models/grid.model";
import { rowClassProvider } from "../models/rows.model";
import { DatasourceParamsController } from "../services/DatasourceParamsController";
import { DerivedLoaderController } from "../services/DerivedLoaderController";
-import { PaginationController } from "../services/PaginationController";
import { SelectionGate } from "../services/SelectionGate.service";
import { CORE_TOKENS as CORE, DG_TOKENS as DG, SA_TOKENS } from "../tokens";
@@ -37,10 +45,29 @@ import { CORE_TOKENS as CORE, DG_TOKENS as DG, SA_TOKENS } from "../tokens";
injected(ColumnGroupStore, CORE.setupService, CORE.mainGate, CORE.config, DG.filterHost);
injected(DatasourceParamsController, CORE.setupService, DG.query, DG.combinedFilter, CORE.columnsStore);
injected(DatasourceService, CORE.setupService, DG.queryGate, DG.refreshInterval.optional);
-injected(PaginationController, CORE.setupService, DG.paginationConfig, DG.query);
injected(GridBasicData, CORE.mainGate);
injected(WidgetRootViewModel, CORE.mainGate, CORE.config, DG.exportProgressService, SA_TOKENS.selectionDialogVM);
+/** Pagination **/
+injected(createSetPageAction, DG.query, DG.paginationConfig, DG.currentPage, DG.pageSize);
+injected(createSetPageSizeAction, DG.query, DG.paginationConfig, DG.currentPage, CORE.pageSizeStore, DG.setPageAction);
+injected(currentPageAtom, DG.query, DG.pageSize, DG.paginationConfig);
+injected(dynamicPageAtom, CORE.mainGate, DG.paginationConfig);
+injected(dynamicPageSizeAtom, CORE.mainGate);
+injected(PageControlService, CORE.mainGate, DG.setPageSizeAction, DG.setPageAction);
+injected(pageSizeAtom, CORE.pageSizeStore);
+injected(PaginationViewModel, DG.paginationConfig, DG.query, DG.currentPage, DG.pageSize, DG.setPageAction);
+injected(
+ DynamicPaginationFeature,
+ CORE.setupService,
+ DG.paginationConfig,
+ DG.dynamicPage,
+ DG.dynamicPageSize,
+ CORE.atoms.totalCount,
+ DG.pageControl
+);
+injected(customPaginationAtom, CORE.mainGate);
+
// loader
injected(DerivedLoaderController, DG.query, DG.exportProgressService, CORE.columnsStore, DG.loaderConfig);
@@ -64,17 +91,10 @@ injected(rowClassProvider, CORE.mainGate);
// row-interaction
injected(SelectActionsProvider, DG.selectionType, DG.selectionHelper);
injected(createFocusController, CORE.setupService, DG.virtualLayout);
-injected(creteCheckboxEventsController, CORE.config, DG.selectActions, DG.focusService, CORE.atoms.pageSize);
-injected(layoutAtom, CORE.atoms.itemCount, CORE.atoms.columnCount, CORE.atoms.pageSize);
+injected(creteCheckboxEventsController, CORE.config, DG.selectActions, DG.focusService, DG.pageSize);
+injected(layoutAtom, CORE.atoms.itemCount, CORE.atoms.columnCount, DG.pageSize);
injected(createClickActionHelper, CORE.setupService, CORE.mainGate);
-injected(
- createCellEventsController,
- CORE.config,
- DG.selectActions,
- DG.focusService,
- DG.clickActionHelper,
- CORE.atoms.pageSize
-);
+injected(createCellEventsController, CORE.config, DG.selectActions, DG.focusService, DG.clickActionHelper, DG.pageSize);
// selection counter
injected(
@@ -96,8 +116,6 @@ export class DatagridContainer extends Container {
this.bind(CORE.columnsStore).toInstance(ColumnGroupStore).inSingletonScope();
// Query service
this.bind(DG.query).toInstance(DatasourceService).inSingletonScope();
- // Pagination service
- this.bind(DG.paginationService).toInstance(PaginationController).inSingletonScope();
// Datasource params service
this.bind(DG.paramsService).toInstance(DatasourceParamsController).inSingletonScope();
// FilterAPI
@@ -118,6 +136,18 @@ export class DatagridContainer extends Container {
// Grid columns style
this.bind(DG.gridColumnsStyle).toInstance(gridStyleAtom).inTransientScope();
+ /** Pagination **/
+ this.bind(DG.currentPage).toInstance(currentPageAtom).inTransientScope();
+ this.bind(DG.customPagination).toInstance(customPaginationAtom).inTransientScope();
+ this.bind(DG.dynamicPage).toInstance(dynamicPageAtom).inTransientScope();
+ this.bind(DG.dynamicPageSize).toInstance(dynamicPageSizeAtom).inTransientScope();
+ this.bind(DG.dynamicPagination).toInstance(DynamicPaginationFeature).inSingletonScope();
+ this.bind(DG.pageSize).toInstance(pageSizeAtom).inTransientScope();
+ this.bind(DG.pageControl).toInstance(PageControlService).inSingletonScope();
+ this.bind(DG.paginationVM).toInstance(PaginationViewModel).inSingletonScope();
+ this.bind(DG.setPageAction).toInstance(createSetPageAction).inSingletonScope();
+ this.bind(DG.setPageSizeAction).toInstance(createSetPageSizeAction).inSingletonScope();
+
// Selection gate
this.bind(DG.selectionGate).toInstance(SelectionGate).inTransientScope();
// Selection helper
@@ -188,12 +218,8 @@ export class DatagridContainer extends Container {
});
// Bind pagination config
- this.bind(DG.paginationConfig).toConstant({
- pagination: props.pagination,
- showPagingButtons: props.showPagingButtons,
- showNumberOfRows: props.showNumberOfRows,
- pageSize: props.pageSize
- });
+
+ this.bind(DG.paginationConfig).toConstant(paginationConfig(props));
// Bind init page size
this.bind(CORE.initPageSize).toConstant(props.pageSize);
@@ -212,8 +238,13 @@ export class DatagridContainer extends Container {
/** Post init hook for final configuration. */
private postInit(props: MainGateProps, config: DatagridConfig): void {
// Make sure essential services are created upfront
- this.get(DG.paramsService);
- this.get(DG.paginationService);
+ this.get(DG.paramsService); // Enable sort & filtering
+ this.get(DG.dynamicPagination); // Enable dynamic pagination feature
+
+ const query = this.get(DG.query);
+ const pgConfig = this.get(DG.paginationConfig);
+ query.requestTotalCount(pgConfig.requestTotalCount);
+ query.setBaseLimit(pgConfig.constPageSize);
if (config.settingsStorageEnabled) {
this.get(DG.personalizationService);
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/containers/Root.container.ts b/packages/pluggableWidgets/datagrid-web/src/model/containers/Root.container.ts
index 30ee19b4b9..d62d242228 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/containers/Root.container.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/containers/Root.container.ts
@@ -14,8 +14,8 @@ import {
} from "@mendix/widget-plugin-grid/core/models/selection.model";
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
import { Container, injected } from "brandi";
+
import { columnCount, visibleColumnsCountAtom } from "../models/columns.model";
-import { pageSizeAtom } from "../models/paging.model";
import { rowsAtom } from "../models/rows.model";
import { DatagridSetupService } from "../services/DatagridSetup.service";
import { TextsService } from "../services/Texts.service";
@@ -31,7 +31,6 @@ injected(hasMoreItemsAtom, CORE.mainGate);
injected(visibleColumnsCountAtom, CORE.columnsStore);
injected(isAllItemsPresentAtom, CORE.atoms.offset, CORE.atoms.hasMoreItems);
injected(rowsAtom, CORE.mainGate);
-injected(pageSizeAtom, CORE.pageSizeStore);
injected(columnCount, CORE.atoms.visibleColumnsCount, CORE.config);
// selection
@@ -84,7 +83,6 @@ export class RootContainer extends Container {
this.bind(CORE.texts).toInstance(TextsService).inTransientScope();
// paging
- this.bind(CORE.atoms.pageSize).toInstance(pageSizeAtom).inTransientScope();
this.bind(CORE.pageSizeStore).toInstance(PageSizeStore).inSingletonScope();
}
}
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts b/packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts
index 337a03637f..0886f8c1f6 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/hooks/injection-hooks.ts
@@ -8,7 +8,7 @@ export const [useDatagridFilterAPI] = createInjectionHooks(DG.filterAPI);
export const [useExportProgressService] = createInjectionHooks(DG.exportProgressService);
export const [useLoaderViewModel] = createInjectionHooks(DG.loaderVM);
export const [useMainGate] = createInjectionHooks(CORE.mainGate);
-export const [usePaginationService] = createInjectionHooks(DG.paginationService);
+export const [usePaginationVM] = createInjectionHooks(DG.paginationVM);
export const [useSelectionHelper] = createInjectionHooks(DG.selectionHelper);
export const [useGridStyle] = createInjectionHooks(DG.gridColumnsStyle);
export const [useQueryService] = createInjectionHooks(DG.query);
@@ -24,3 +24,5 @@ export const [useClickActionHelper] = createInjectionHooks(DG.clickActionHelper)
export const [useFocusService] = createInjectionHooks(DG.focusService);
export const [useCheckboxEventsHandler] = createInjectionHooks(DG.checkboxEventsHandler);
export const [useCellEventsHandler] = createInjectionHooks(DG.cellEventsHandler);
+export const [useCustomPagination] = createInjectionHooks(DG.customPagination);
+export const [usePaginationConfig] = createInjectionHooks(DG.paginationConfig);
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/hooks/useBodyScroll.ts b/packages/pluggableWidgets/datagrid-web/src/model/hooks/useBodyScroll.ts
index e5fd2eed3a..9ecc7b8c41 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/hooks/useBodyScroll.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/hooks/useBodyScroll.ts
@@ -1,6 +1,6 @@
import { useInfiniteControl } from "@mendix/widget-plugin-grid/components/InfiniteBody";
import { RefObject, UIEventHandler, useCallback } from "react";
-import { usePaginationService } from "./injection-hooks";
+import { usePaginationVM } from "./injection-hooks";
export function useBodyScroll(): {
handleScroll: UIEventHandler | undefined;
@@ -8,7 +8,7 @@ export function useBodyScroll(): {
containerRef: RefObject;
isInfinite: boolean;
} {
- const paging = usePaginationService();
+ const paging = usePaginationVM();
const setPage = useCallback((cb: (n: number) => number) => paging.setPage(cb), [paging]);
const isInfinite = paging.pagination === "virtualScrolling";
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/models/paging.model.ts b/packages/pluggableWidgets/datagrid-web/src/model/models/paging.model.ts
deleted file mode 100644
index b3c003f2d1..0000000000
--- a/packages/pluggableWidgets/datagrid-web/src/model/models/paging.model.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { ComputedAtom } from "@mendix/widget-plugin-mobx-kit/main";
-import { computed } from "mobx";
-
-export function pageSizeAtom(source: { pageSize: number }): ComputedAtom {
- return computed(() => source.pageSize);
-}
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/services/PaginationController.ts b/packages/pluggableWidgets/datagrid-web/src/model/services/PaginationController.ts
deleted file mode 100644
index 8f2baff57c..0000000000
--- a/packages/pluggableWidgets/datagrid-web/src/model/services/PaginationController.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { QueryService } from "@mendix/widget-plugin-grid/main";
-import { SetupComponent, SetupComponentHost } from "@mendix/widget-plugin-mobx-kit/main";
-import { PaginationEnum, ShowPagingButtonsEnum } from "../../../typings/DatagridProps";
-
-export interface PaginationConfig {
- pagination: PaginationEnum;
- showPagingButtons: ShowPagingButtonsEnum;
- showNumberOfRows: boolean;
- pageSize: number;
-}
-
-type PaginationKind = `${PaginationEnum}.${ShowPagingButtonsEnum}`;
-
-export class PaginationController implements SetupComponent {
- readonly pagination: PaginationEnum;
- readonly paginationKind: PaginationKind;
- readonly showPagingButtons: ShowPagingButtonsEnum;
-
- constructor(
- host: SetupComponentHost,
- private config: PaginationConfig,
- private query: QueryService
- ) {
- host.add(this);
- this.pagination = config.pagination;
- this.paginationKind = `${this.pagination}.${config.showPagingButtons}`;
- this.showPagingButtons = config.showPagingButtons;
- this.setInitParams();
- }
-
- get isLimitBased(): boolean {
- return this.pagination === "virtualScrolling" || this.pagination === "loadMore";
- }
-
- get pageSize(): number {
- return this.config.pageSize;
- }
-
- get currentPage(): number {
- const {
- query: { limit, offset },
- pageSize
- } = this;
- return this.isLimitBased ? limit / pageSize : offset / pageSize;
- }
-
- get paginationVisible(): boolean {
- switch (this.paginationKind) {
- case "buttons.always":
- return true;
- case "buttons.auto": {
- const { totalCount = -1 } = this.query;
- return totalCount > this.query.limit;
- }
- default:
- return this.config.showNumberOfRows;
- }
- }
-
- get hasMoreItems(): boolean {
- return this.query.hasMoreItems;
- }
-
- get totalCount(): number | undefined {
- return this.query.totalCount;
- }
-
- private setInitParams(): void {
- if (this.pagination === "buttons" || this.config.showNumberOfRows) {
- this.query.requestTotalCount(true);
- }
-
- this.query.setBaseLimit(this.pageSize);
- }
-
- setup(): void {}
-
- setPage = (computePage: ((prevPage: number) => number) | number): void => {
- const newPage = typeof computePage === "function" ? computePage(this.currentPage) : computePage;
- if (this.isLimitBased) {
- this.query.setLimit(newPage * this.pageSize);
- } else {
- this.query.setOffset(newPage * this.pageSize);
- }
- };
-}
diff --git a/packages/pluggableWidgets/datagrid-web/src/model/tokens.ts b/packages/pluggableWidgets/datagrid-web/src/model/tokens.ts
index 26b3e38201..9b1f61e366 100644
--- a/packages/pluggableWidgets/datagrid-web/src/model/tokens.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/model/tokens.ts
@@ -10,6 +10,7 @@ import {
SelectAllService,
SelectionDynamicProps,
SelectionHelperService,
+ SetPageAction,
TaskProgressService
} from "@mendix/widget-plugin-grid/main";
import { SelectAllFeature } from "@mendix/widget-plugin-grid/select-all/select-all.feature";
@@ -26,6 +27,10 @@ import { CSSProperties, ReactNode } from "react";
import { MainGateProps } from "../../typings/MainGateProps";
import { WidgetRootViewModel } from "../features/base/WidgetRoot.viewModel";
import { EmptyPlaceholderViewModel } from "../features/empty-message/EmptyPlaceholder.viewModel";
+import { DynamicPaginationFeature } from "../features/pagination/DynamicPagination.feature";
+import { GridPageControl } from "../features/pagination/GridPageControl";
+import { PaginationViewModel } from "../features/pagination/Pagination.viewModel";
+import { PaginationConfig } from "../features/pagination/pagination.config";
import { CellEventsController } from "../features/row-interaction/CellEventsController";
import { CheckboxEventsController } from "../features/row-interaction/CheckboxEventsController";
import { SelectAllBarViewModel } from "../features/select-all/SelectAllBar.viewModel";
@@ -39,7 +44,6 @@ import { DatagridConfig } from "./configs/Datagrid.config";
import { RowClassProvider } from "./models/rows.model";
import { DatagridSetupService } from "./services/DatagridSetup.service";
import { DerivedLoaderController, DerivedLoaderControllerConfig } from "./services/DerivedLoaderController";
-import { PaginationConfig, PaginationController } from "./services/PaginationController";
import { TextsService } from "./services/Texts.service";
import { PageSizeStore } from "./stores/PageSize.store";
@@ -55,7 +59,6 @@ export const CORE_TOKENS = {
totalCount: token>("@computed:totalCount"),
visibleColumnsCount: token>("@computed:visibleColumnsCount"),
isAllItemsPresent: token>("@computed:isAllItemsPresent"),
- pageSize: token>("@computed:pageSize"),
columnCount: token>("@computed:columnCount")
},
columnsStore: token("ColumnGroupStore"),
@@ -101,8 +104,17 @@ export const DG_TOKENS = {
loaderConfig: token("DatagridLoaderConfig"),
loaderVM: token("DatagridLoaderViewModel"),
- paginationConfig: token("PaginationConfig"),
- paginationService: token("PaginationService"),
+ currentPage: token>("@computed:currentPage"),
+ customPagination: token>("@computed:customPagination"),
+ dynamicPage: token>("@computed:dynamicPage"),
+ dynamicPageSize: token>("@computed:dynamicPageSize"),
+ dynamicPagination: token("@feature:DynamicPaginationFeature"),
+ pageControl: token("@service:GridPageControl"),
+ pageSize: token>("@computed:pageSize"),
+ paginationConfig: token("@config:PaginationConfig"),
+ paginationVM: token("@viewModel:PaginationService"),
+ setPageAction: token("@action:setPage"),
+ setPageSizeAction: token("@action:setPageSize"),
parentChannelName: token("parentChannelName"),
refreshInterval: token("refreshInterval"),
diff --git a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx
index e8bc5eb194..5890c5cfcb 100644
--- a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx
@@ -84,6 +84,8 @@ export function mockContainerProps(overrides?: Partial):
selectAllText: dynamic("Select all items"),
selectAllTemplate: dynamic("Select all %d items"),
allSelectedText: dynamic("All items selected"),
+ useCustomPagination: false,
+ customPagination: undefined,
...overrides
};
}
diff --git a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts
index 056c487def..ca9c2c6235 100644
--- a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts
@@ -117,10 +117,15 @@ export interface DatagridContainerProps {
refreshIndicator: boolean;
pageSize: number;
pagination: PaginationEnum;
+ useCustomPagination: boolean;
+ customPagination?: ReactNode;
showPagingButtons: ShowPagingButtonsEnum;
showNumberOfRows: boolean;
pagingPosition: PagingPositionEnum;
loadMoreButtonCaption?: DynamicValue;
+ dynamicPageSize?: EditableValue;
+ dynamicPage?: EditableValue;
+ totalCountValue?: EditableValue;
showEmptyPlaceholder: ShowEmptyPlaceholderEnum;
emptyPlaceholder?: ReactNode;
rowClass?: ListExpressionValue;
@@ -176,10 +181,15 @@ export interface DatagridPreviewProps {
refreshIndicator: boolean;
pageSize: number | null;
pagination: PaginationEnum;
+ useCustomPagination: boolean;
+ customPagination: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
showPagingButtons: ShowPagingButtonsEnum;
showNumberOfRows: boolean;
pagingPosition: PagingPositionEnum;
loadMoreButtonCaption: string;
+ dynamicPageSize: string;
+ dynamicPage: string;
+ totalCountValue: string;
showEmptyPlaceholder: ShowEmptyPlaceholderEnum;
emptyPlaceholder: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
rowClass: string;
diff --git a/packages/pluggableWidgets/datagrid-web/typings/MainGateProps.ts b/packages/pluggableWidgets/datagrid-web/typings/MainGateProps.ts
index 5e93b89aea..94460b547b 100644
--- a/packages/pluggableWidgets/datagrid-web/typings/MainGateProps.ts
+++ b/packages/pluggableWidgets/datagrid-web/typings/MainGateProps.ts
@@ -12,6 +12,8 @@ export type MainGateProps = Pick<
| "configurationAttribute"
| "configurationStorageType"
| "datasource"
+ | "dynamicPage"
+ | "dynamicPageSize"
| "emptyPlaceholder"
| "enableSelectAll"
| "exportDialogLabel"
@@ -25,6 +27,7 @@ export type MainGateProps = Pick<
| "onSelectionChange"
| "pageSize"
| "pagination"
+ | "pagingPosition"
| "refreshIndicator"
| "refreshInterval"
| "rowClass"
@@ -37,4 +40,7 @@ export type MainGateProps = Pick<
| "showPagingButtons"
| "storeFiltersInPersonalization"
| "style"
+ | "totalCountValue"
+ | "useCustomPagination"
+ | "customPagination"
>;
diff --git a/packages/shared/widget-plugin-grid/src/main.ts b/packages/shared/widget-plugin-grid/src/main.ts
index 7bb034257b..07ddcf6a6c 100644
--- a/packages/shared/widget-plugin-grid/src/main.ts
+++ b/packages/shared/widget-plugin-grid/src/main.ts
@@ -8,6 +8,8 @@ export { type SelectionHelperService } from "./interfaces/SelectionHelperService
export type { TaskProgressService } from "./interfaces/TaskProgressService";
export { createFocusController } from "./keyboard-navigation/createFocusController";
export { layoutAtom } from "./keyboard-navigation/layout.model";
+export { PageSizeStore } from "./pagination/PageSize.store";
+export * from "./pagination/pagination.model";
export { SelectAllService } from "./select-all/SelectAll.service";
export { SelectionCounterViewModel } from "./selection-counter/SelectionCounter.viewModel";
export * from "./selection/context";
diff --git a/packages/shared/widget-plugin-grid/src/pagination/PageSize.store.ts b/packages/shared/widget-plugin-grid/src/pagination/PageSize.store.ts
new file mode 100644
index 0000000000..763d63d0cb
--- /dev/null
+++ b/packages/shared/widget-plugin-grid/src/pagination/PageSize.store.ts
@@ -0,0 +1,14 @@
+import { action, makeAutoObservable } from "mobx";
+
+export class PageSizeStore {
+ pageSize: number;
+
+ constructor(initSize = 0) {
+ this.pageSize = initSize;
+ makeAutoObservable(this, { setPageSize: action.bound });
+ }
+
+ setPageSize(size: number): void {
+ this.pageSize = size;
+ }
+}
diff --git a/packages/shared/widget-plugin-grid/src/pagination/pagination.model.ts b/packages/shared/widget-plugin-grid/src/pagination/pagination.model.ts
new file mode 100644
index 0000000000..82afcf7392
--- /dev/null
+++ b/packages/shared/widget-plugin-grid/src/pagination/pagination.model.ts
@@ -0,0 +1,77 @@
+import { ComputedAtom } from "@mendix/widget-plugin-mobx-kit/main";
+import { action, computed } from "mobx";
+import { QueryService } from "../main";
+
+/**
+ * Return observable atom holding page size. Value -1 means no meaningful page size is set.
+ * @injectable
+ */
+export function boundPageSize(get: () => number): ComputedAtom {
+ return computed(() => Math.max(get(), -1));
+}
+
+/**
+ * Atom that computes the current page based on query parameters.
+ * @injectable
+ * @remark
+ * When pagination is limit-based, the atom value is the number
+ * of loaded pages instead of the current page index.
+ * In the case of offset-based pagination, the atom value is the zero-based page index.
+ */
+export function currentPageAtom(
+ query: QueryService,
+ pageSize: ComputedAtom,
+ config: { isLimitBased: boolean }
+): ComputedAtom {
+ return computed(() => {
+ const size = pageSize.get();
+ const { limit, offset } = query;
+ return Math.floor(config.isLimitBased ? limit / size : offset / size);
+ });
+}
+
+/** Main atom for the page size. */
+export function pageSizeAtom(store: { pageSize: number }): ComputedAtom {
+ return computed(() => store.pageSize);
+}
+
+export type SetPageAction = (value: ((prevPage: number) => number) | number) => void;
+
+/** Main action to change page. */
+export function createSetPageAction(
+ query: QueryService,
+ config: { isLimitBased: boolean },
+ currentPage: ComputedAtom,
+ pageSize: ComputedAtom
+): SetPageAction {
+ return action(function setPageAction(value): void {
+ const newPage = typeof value === "function" ? value(currentPage.get()) : value;
+ if (config.isLimitBased) {
+ query.setLimit(newPage * pageSize.get());
+ } else {
+ query.setOffset(newPage * pageSize.get());
+ }
+ });
+}
+
+export type SetPageSizeAction = (newSize: number) => void;
+
+/** Main action to change page size. */
+export function createSetPageSizeAction(
+ query: QueryService,
+ config: { isLimitBased: boolean },
+ currentPage: ComputedAtom,
+ pageSizeStore: { setPageSize: (n: number) => void },
+ setPageAction: SetPageAction
+): SetPageSizeAction {
+ return action(function setPageSizeAction(newSize: number): void {
+ const currentPageIndex = currentPage.get();
+
+ // Update limit in case of offset-based pagination
+ if (!config.isLimitBased) {
+ query.setBaseLimit(newSize);
+ }
+ pageSizeStore.setPageSize(newSize);
+ setPageAction(currentPageIndex);
+ });
+}