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
2 changes: 2 additions & 0 deletions packages/pluggableWidgets/datagrid-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- We added a new property for export to excel. The new property allows to set the cell export type and also the format for type number and date.

- We have introduced the "Page" and "Page Size" attributes to provide complete control over DataGrid pagination.

## [3.7.0] - 2025-11-11

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,25 @@ export function getProperties(values: DatagridPreviewProps, defaultProperties: P

if (values.pagination === "buttons") {
hidePropertyIn(defaultProperties, values, "showNumberOfRows");

if (values.useCustomPagination === false) {
hidePropertyIn(defaultProperties, values, "customPagination");
} else {
hidePropertiesIn(defaultProperties, values, ["pagingPosition", "showPagingButtons"]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to provide the way to set custom pagination position to top/bottom of the grid?

}
} else {
hidePropertyIn(defaultProperties, values, "showPagingButtons");

if (values.showNumberOfRows === false) {
hidePropertyIn(defaultProperties, values, "pagingPosition");
}

hidePropertiesIn(defaultProperties, values, [
"dynamicPage",
"dynamicPageSize",
"useCustomPagination",
"customPagination"
]);
}

if (values.pagination !== "loadMore") {
Expand Down
29 changes: 29 additions & 0 deletions packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,14 @@
<enumerationValue key="loadMore">Load more</enumerationValue>
</enumerationValues>
</property>
<property key="useCustomPagination" type="boolean" defaultValue="false">
<caption>Custom pagination</caption>
<description />
</property>
<property key="customPagination" type="widgets" required="false">
<caption>Custom pagination</caption>
<description />
</property>
<property key="showPagingButtons" type="enumeration" defaultValue="always">
<caption>Show paging buttons</caption>
<description />
Expand Down Expand Up @@ -308,6 +316,27 @@
<translation lang="en_US">Load More</translation>
</translations>
</property>
<property key="dynamicPageSize" type="attribute" required="false">
<caption>Page size attribute</caption>
<description>Attribute to set the page size dynamically.</description>
<attributeTypes>
<attributeType name="Integer" />
</attributeTypes>
</property>
<property key="dynamicPage" type="attribute" required="false">
<caption>Page attribute</caption>
<description>Attribute to set the page dynamically.</description>
<attributeTypes>
<attributeType name="Integer" />
</attributeTypes>
</property>
<property key="totalCountValue" type="attribute" required="false">
<caption>Total count</caption>
<description>Attribute to store current total count</description>
<attributeTypes>
<attributeType name="Integer" />
</attributeTypes>
</property>
</propertyGroup>
<propertyGroup caption="Appearance">
<property key="showEmptyPlaceholder" type="enumeration" defaultValue="none">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
useDatagridConfig,
useItemCount,
useLoaderViewModel,
usePaginationService,
usePaginationVM,
useVisibleColumnsCount
} from "../model/hooks/injection-hooks";
import { useBodyScroll } from "../model/hooks/useBodyScroll";
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ 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,
useDatagridConfig,
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 (
<div className="widget-datagrid-footer table-footer">
Expand All @@ -35,6 +43,7 @@ export const WidgetFooter = observer(function WidgetFooter(): ReactElement {
<If condition={config.pagingPosition !== "top"}>
<Pagination />
</If>
<If condition={pgConfig.customPaginationEnabled}>{customPagination.get()}</If>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<number>,
private dynamicPageSize: ComputedAtom<number>,
private totalCount: ComputedAtom<number>,
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface GridPageControl {
setPage(page: number): void;
setPageSize(pageSize: number): void;
setTotalCount(totalCount: number): void;
}
Original file line number Diff line number Diff line change
@@ -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<Big>;
}>,
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));
}
}
Original file line number Diff line number Diff line change
@@ -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<number>,
private pageSizeAtom: ComputedAtom<number>,
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);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { PaginationEnum, ShowPagingButtonsEnum } from "../../../typings/DatagridProps";
import { MainGateProps } from "../../../typings/MainGateProps";

export interface PaginationConfig {
pagination: PaginationEnum;
paginationKind: PaginationKind;
showPagingButtons: ShowPagingButtonsEnum;
showNumberOfRows: boolean;
pageSize: number;
isLimitBased: boolean;
dynamicPageSizeEnabled: boolean;
dynamicPageEnabled: boolean;
customPaginationEnabled: 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,
pageSize: props.pageSize,
isLimitBased: isLimitBased(props),
paginationKind: paginationKind(props),
dynamicPageSizeEnabled: dynamicPageSizeEnabled(props),
dynamicPageEnabled: dynamicPageEnabled(props),
customPaginationEnabled: props.useCustomPagination
};

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";
}
Original file line number Diff line number Diff line change
@@ -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<number> {
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<number> {
return boundPageSize(() => gate.props.dynamicPageSize?.value?.toNumber() ?? -1);
}

export function customPaginationAtom(
gate: DerivedPropsGate<{ customPagination?: ReactNode }>
): ComputedAtom<ReactNode> {
return computed(() => gate.props.customPagination);
}
Loading
Loading