Skip to content

Commit fac03d1

Browse files
[WC-3283]: Fix custom pagination (#2116)
2 parents 94b668d + ad8a238 commit fac03d1

34 files changed

Lines changed: 1322 additions & 78 deletions

packages/pluggableWidgets/datagrid-web/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1414

1515
- We added accessibility support for column headers when single selection is enabled, making sure the purpose of the column is announced.
1616

17+
- We added a new `Loaded rows` attribute that reflects the number of rows currently loaded for virtual scrolling and load-more pagination modes.
18+
19+
- We exposed the `Page`, `Page size`, and `Total count` attributes for virtual scrolling and load-more pagination modes so they are kept in sync at all times.
20+
1721
### Fixed
1822

1923
- We fixed an issue with Data export crashing on some Android devices.
2024

25+
- We fixed an issue where the `Page` attribute was not updated when navigating pages using the default (buttons) paging controls.
26+
27+
- We fixed an issue where configuring the `Total count` attribute had no effect for virtual scrolling and load-more pagination modes.
28+
2129
## [3.8.1] - 2026-02-19
2230

2331
### Fixed

packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import AxeBuilder from "@axe-core/playwright";
2+
import { expect, test } from "@playwright/test";
13
import path from "path";
2-
import { test, expect } from "@playwright/test";
34
import * as XLSX from "xlsx";
4-
import AxeBuilder from "@axe-core/playwright";
55

66
test.afterEach("Cleanup session", async ({ page }) => {
77
// Because the test isolation that will open a new session for every test executed, and that exceeds Mendix's license limit of 5 sessions, so we need to force logout after each test.
@@ -144,7 +144,7 @@ test.describe("capabilities: hiding", () => {
144144
await page.locator(".column-selectors > li").nth(2).click();
145145
await page.locator(".column-selectors > li").nth(1).click();
146146
await expect(page.locator(".column-selectors input:checked")).toHaveCount(1);
147-
await page.locator(".column-selectors > li").nth(0).click();
147+
await page.locator(".column-selectors > li").nth(0).click({ force: true });
148148
await expect(page.locator(".column-selectors input:checked")).toHaveCount(1);
149149
// Trigger Enter keypress
150150
await page.locator(".column-selectors > li").nth(0).press("Enter");
609 Bytes
Loading

packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export function getProperties(values: DatagridPreviewProps, defaultProperties: P
6969

7070
if (values.pagination === "buttons") {
7171
hidePropertyIn(defaultProperties, values, "showNumberOfRows");
72+
hidePropertyIn(defaultProperties, values, "loadedRowsValue");
7273

7374
if (values.useCustomPagination === false) {
7475
hidePropertyIn(defaultProperties, values, "customPagination");
@@ -82,13 +83,7 @@ export function getProperties(values: DatagridPreviewProps, defaultProperties: P
8283
hidePropertyIn(defaultProperties, values, "pagingPosition");
8384
}
8485

85-
hidePropertiesIn(defaultProperties, values, [
86-
"dynamicPage",
87-
"dynamicPageSize",
88-
"useCustomPagination",
89-
"customPagination",
90-
"totalCountValue"
91-
]);
86+
hidePropertiesIn(defaultProperties, values, ["useCustomPagination", "customPagination"]);
9287
}
9388

9489
if (values.pagination !== "loadMore") {
@@ -335,13 +330,27 @@ export const getPreview = (
335330
)
336331
]
337332
: [];
333+
const customPaginationWidgets = values.useCustomPagination
334+
? [
335+
rowLayout({
336+
columnSize: "fixed",
337+
borders: true
338+
})(
339+
dropzone(
340+
dropzone.placeholder("Custom pagination: Place widgets here"),
341+
dropzone.hideDataSourceHeaderIf(canHideDataSourceHeader)
342+
)(values.customPagination)
343+
)
344+
]
345+
: [];
338346

339347
return container()(
340348
gridTitle,
341349
...(canHideDataSourceHeader ? [datasource(values.datasource)()] : []),
342350
gridHeaderWidgets,
343351
columnHeaders,
344352
...Array.from({ length: 5 }).map(() => columns),
353+
...customPaginationWidgets,
345354
...customEmptyMessageWidgets
346355
);
347356
};

packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,7 @@ function WidgetTopBar(): ReactElement {
105105
<div className={cls.topBar}>
106106
<div className={cls.pagingTop}>
107107
<div className={cls.ptStart}>{useTopCounter() ? <SelectionCounter /> : null}</div>
108-
<div className={cls.ptEnd}>
109-
{usePagingTop() ? <Pagination /> : null}
110-
{useCustomPagination("top") ? <CustomPagination /> : null}
111-
</div>
108+
<div className={cls.ptEnd}>{usePagingTop() ? <Pagination /> : null}</div>
112109
</div>
113110
</div>
114111
);
@@ -142,7 +139,7 @@ function WidgetFooter(): ReactElement {
142139
</div>
143140
<div className={cls.pbEnd}>
144141
{usePagingBot() ? <Pagination /> : null}
145-
{useCustomPagination("bottom") ? <CustomPagination /> : null}
142+
{useCustomPagination() ? <CustomPagination /> : null}
146143
</div>
147144
</div>
148145
</div>
@@ -406,7 +403,7 @@ function usePagingBot(): boolean {
406403
return visible && props.pagingPosition !== "top";
407404
}
408405

409-
function useCustomPagination(location: "top" | "bottom"): boolean {
406+
function useCustomPagination(): boolean {
410407
const props = useProps();
411-
return props.useCustomPagination && (props.pagingPosition === location || props.pagingPosition === "both");
408+
return props.useCustomPagination;
412409
}

packages/pluggableWidgets/datagrid-web/src/Datagrid.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,13 @@
342342
<attributeType name="Integer" />
343343
</attributeTypes>
344344
</property>
345+
<property key="loadedRowsValue" type="attribute" required="false">
346+
<caption>Loaded rows</caption>
347+
<description>Read-only attribute reflecting the number of rows currently loaded.</description>
348+
<attributeTypes>
349+
<attributeType name="Integer" />
350+
</attributeTypes>
351+
</property>
345352
</propertyGroup>
346353
<propertyGroup caption="Appearance">
347354
<property key="showEmptyPlaceholder" type="enumeration" defaultValue="none">

packages/pluggableWidgets/datagrid-web/src/components/WidgetFooter.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ export const WidgetFooter = observer(function WidgetFooter(): ReactElement | nul
1313
const selectionCounterVM = useSelectionCounterViewModel();
1414
const customPagination = useCustomPagination();
1515

16-
const showFooter = selectionCounterVM.isBottomCounterVisible || paging.paginationVisible || paging.loadMoreVisible;
16+
const showFooter =
17+
selectionCounterVM.isBottomCounterVisible ||
18+
paging.paginationVisible ||
19+
paging.loadMoreVisible ||
20+
pgConfig.customPaginationEnabled;
1721

1822
if (!showFooter) {
1923
return null;
@@ -39,7 +43,7 @@ export const WidgetFooter = observer(function WidgetFooter(): ReactElement | nul
3943
</div>
4044
</If>
4145
<div className="widget-datagrid-pb-end">
42-
<If condition={pgConfig.pagingPosition !== "top"}>
46+
<If condition={!pgConfig.customPaginationEnabled && pgConfig.pagingPosition !== "top"}>
4347
<Pagination />
4448
</If>
4549
<If condition={pgConfig.customPaginationEnabled}>{customPagination.get()}</If>

packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const WidgetTopBar = observer(function WidgetTopBar(): ReactElement {
1919
</If>
2020
</div>
2121
<div className="widget-datagrid-tb-end">
22-
<If condition={pgConfig.pagingPosition !== "bottom"}>
22+
<If condition={!pgConfig.customPaginationEnabled && pgConfig.pagingPosition !== "bottom"}>
2323
<Pagination />
2424
</If>
2525
</div>

packages/pluggableWidgets/datagrid-web/src/components/__tests__/ColumnSelector.spec.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ import userEvent from "@testing-library/user-event";
33
import { ColumnSelector, ColumnSelectorProps } from "../ColumnSelector";
44
import { ColumnId, GridColumn } from "../../typings/GridColumn";
55

6+
beforeAll(() => {
7+
Object.defineProperty(global, "ResizeObserver", {
8+
writable: true,
9+
value: jest.fn().mockImplementation(() => ({
10+
observe: jest.fn(),
11+
unobserve: jest.fn(),
12+
disconnect: jest.fn()
13+
}))
14+
});
15+
});
16+
617
jest.useFakeTimers();
718

819
describe("Column Selector", () => {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { MainGateProps } from "typings/MainGateProps";
2+
import {
3+
dynamicPageEnabled,
4+
dynamicPageSizeEnabled,
5+
requestTotalCount,
6+
resolveInitPageSize
7+
} from "../pagination.config";
8+
9+
function makeProps(overrides = {}): Partial<MainGateProps> {
10+
return {
11+
pagination: "buttons",
12+
showNumberOfRows: false,
13+
pageSize: 10,
14+
pagingPosition: "bottom",
15+
useCustomPagination: false,
16+
showPagingButtons: "always",
17+
refreshIndicator: false,
18+
refreshInterval: 0,
19+
datasource: undefined,
20+
columns: [],
21+
filtersPlaceholder: undefined,
22+
...overrides
23+
};
24+
}
25+
26+
describe("pagination.config helpers", () => {
27+
describe("requestTotalCount", () => {
28+
it("returns true when totalCountValue attribute is mapped regardless of pagination mode", () => {
29+
const props = makeProps({ totalCountValue: {}, pagination: "virtualScrolling" });
30+
expect(requestTotalCount(props as MainGateProps)).toBe(true);
31+
});
32+
33+
it("returns true for buttons pagination even without attribute", () => {
34+
const props = makeProps({ pagination: "buttons" });
35+
expect(requestTotalCount(props as MainGateProps)).toBe(true);
36+
});
37+
38+
it("returns true when showNumberOfRows is true", () => {
39+
const props = makeProps({ pagination: "virtualScrolling", showNumberOfRows: true });
40+
expect(requestTotalCount(props as MainGateProps)).toBe(true);
41+
});
42+
43+
it("returns false for virtual scrolling without totalCountValue or showNumberOfRows", () => {
44+
const props = makeProps({ pagination: "virtualScrolling", showNumberOfRows: false });
45+
expect(requestTotalCount(props as MainGateProps)).toBe(false);
46+
});
47+
});
48+
49+
describe("dynamicPageEnabled", () => {
50+
it("is true when dynamicPage attribute is mapped for buttons mode", () => {
51+
const props = makeProps({ dynamicPage: {}, pagination: "buttons" });
52+
expect(dynamicPageEnabled(props as MainGateProps)).toBe(true);
53+
});
54+
55+
it("is true when dynamicPage attribute is mapped for virtualScrolling mode", () => {
56+
const props = makeProps({ dynamicPage: {}, pagination: "virtualScrolling" });
57+
expect(dynamicPageEnabled(props as MainGateProps)).toBe(true);
58+
});
59+
60+
it("is true when dynamicPage attribute is mapped for loadMore mode", () => {
61+
const props = makeProps({ dynamicPage: {}, pagination: "loadMore" });
62+
expect(dynamicPageEnabled(props as MainGateProps)).toBe(true);
63+
});
64+
65+
it("is false when no dynamicPage attribute is provided", () => {
66+
const props = makeProps({ pagination: "virtualScrolling" });
67+
expect(dynamicPageEnabled(props as MainGateProps)).toBe(false);
68+
});
69+
});
70+
71+
describe("dynamicPageSizeEnabled", () => {
72+
it("is true when dynamicPageSize attribute is mapped for buttons mode", () => {
73+
const props = makeProps({ dynamicPageSize: {}, pagination: "buttons" });
74+
expect(dynamicPageSizeEnabled(props as MainGateProps)).toBe(true);
75+
});
76+
77+
it("is true when dynamicPageSize attribute is mapped for virtualScrolling mode", () => {
78+
const props = makeProps({ dynamicPageSize: {}, pagination: "virtualScrolling" });
79+
expect(dynamicPageSizeEnabled(props as MainGateProps)).toBe(true);
80+
});
81+
82+
it("is true when dynamicPageSize attribute is mapped for loadMore mode", () => {
83+
const props = makeProps({ dynamicPageSize: {}, pagination: "loadMore" });
84+
expect(dynamicPageSizeEnabled(props as MainGateProps)).toBe(true);
85+
});
86+
87+
it("is false when no dynamicPageSize attribute is provided", () => {
88+
const props = makeProps({ pagination: "loadMore" });
89+
expect(dynamicPageSizeEnabled(props as MainGateProps)).toBe(false);
90+
});
91+
});
92+
93+
describe("resolveInitPageSize", () => {
94+
it("returns 0 when dynamicPageSize attribute is set", () => {
95+
const props = makeProps({ dynamicPageSize: {} });
96+
expect(resolveInitPageSize(props as MainGateProps)).toBe(0);
97+
});
98+
99+
it("falls back to constPageSize when dynamicPageSize is not set", () => {
100+
const props = makeProps({ pageSize: 10 });
101+
expect(resolveInitPageSize(props as MainGateProps)).toBe(10);
102+
});
103+
});
104+
});

0 commit comments

Comments
 (0)