Skip to content

Commit aa23430

Browse files
committed
refactor: rewrite empty placeholder
1 parent 9a0f933 commit aa23430

10 files changed

Lines changed: 142 additions & 29 deletions

File tree

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
55
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
66
import { ContainerProvider } from "brandi-react";
77
import { observer } from "mobx-react-lite";
8-
import { ReactElement, ReactNode, useCallback, useMemo } from "react";
8+
import { ReactElement, useCallback, useMemo } from "react";
99
import { DatagridContainerProps } from "../typings/DatagridProps";
1010
import { Cell } from "./components/Cell";
1111
import { Widget } from "./components/Widget";
@@ -84,11 +84,6 @@ const DatagridRoot = observer((props: DatagridContainerProps): ReactElement => {
8484
columnsResizable={props.columnsResizable}
8585
columnsSortable={props.columnsSortable}
8686
data={items}
87-
emptyPlaceholderRenderer={useCallback(
88-
(renderWrapper: (children: ReactNode) => ReactElement) =>
89-
props.showEmptyPlaceholder === "custom" ? renderWrapper(props.emptyPlaceholder) : <div />,
90-
[props.emptyPlaceholder, props.showEmptyPlaceholder]
91-
)}
9287
filterRenderer={useCallback(
9388
(renderWrapper, columnIndex) => {
9489
const columnFilter = columnsStore.columnFilters[columnIndex];

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

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { RefreshIndicator } from "@mendix/widget-plugin-component-kit/RefreshIndicator";
22
import { Pagination } from "@mendix/widget-plugin-grid/components/Pagination";
33
import { FocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/FocusTargetController";
4-
import classNames from "classnames";
54
import { ListActionValue, ObjectItem } from "mendix";
65
import { observer } from "mobx-react-lite";
76
import { CSSProperties, Fragment, ReactElement, ReactNode } from "react";
@@ -12,6 +11,7 @@ import {
1211
ShowPagingButtonsEnum
1312
} from "../../typings/DatagridProps";
1413

14+
import { EmptyPlaceholder } from "../features/empty-message/EmptyPlaceholder";
1515
import { SelectAllBar } from "../features/select-all/SelectAllBar";
1616
import { SelectionProgressDialog } from "../features/select-all/SelectionProgressDialog";
1717
import { SelectActionHelper } from "../helpers/SelectActionHelper";
@@ -38,7 +38,6 @@ export interface WidgetProps<C extends GridColumn, T extends ObjectItem = Object
3838
columnsResizable: boolean;
3939
columnsSortable: boolean;
4040
data: T[];
41-
emptyPlaceholderRenderer?: (renderWrapper: (children: ReactNode) => ReactElement) => ReactElement;
4241
exporting: boolean;
4342
filterRenderer: (renderWrapper: (children: ReactNode) => ReactElement, columnIndex: number) => ReactElement;
4443
hasMoreItems: boolean;
@@ -117,7 +116,6 @@ const Main = observer(<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
117116
CellComponent,
118117
columnsHidable,
119118
data: rows,
120-
emptyPlaceholderRenderer,
121119
hasMoreItems,
122120
headerContent,
123121
headerTitle,
@@ -128,7 +126,6 @@ const Main = observer(<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
128126
paginationType,
129127
paging,
130128
pagingPosition,
131-
preview,
132129
showRefreshIndicator,
133130
selectActionHelper,
134131
setPage,
@@ -216,23 +213,7 @@ const Main = observer(<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
216213
eventsController={props.cellEventsController}
217214
pageSize={props.pageSize}
218215
/>
219-
{(rows.length === 0 || preview) &&
220-
emptyPlaceholderRenderer &&
221-
emptyPlaceholderRenderer(children => (
222-
<div
223-
key="row-footer"
224-
className={classNames("td", { "td-borders": !preview })}
225-
style={{
226-
gridColumn: `span ${
227-
visibleColumns.length +
228-
(columnsHidable ? 1 : 0) +
229-
(selectActionHelper.showCheckboxColumn ? 1 : 0)
230-
}`
231-
}}
232-
>
233-
<div className="empty-placeholder">{children}</div>
234-
</div>
235-
))}
216+
<EmptyPlaceholder />
236217
</GridBody>
237218
</Grid>
238219
</WidgetContent>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import classNames from "classnames";
2+
import { observer } from "mobx-react-lite";
3+
import { ReactNode } from "react";
4+
import { useEmptyPlaceholderVM } from "./injection-hooks";
5+
6+
export const EmptyPlaceholder = observer(function EmptyPlaceholder(): ReactNode {
7+
const vm = useEmptyPlaceholderVM();
8+
9+
if (!vm.content) return null;
10+
11+
return (
12+
<div className={classNames("td", "td-borders")} style={vm.style}>
13+
<div className="empty-placeholder">{vm.content}</div>
14+
</div>
15+
);
16+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ComputedAtom } from "@mendix/widget-plugin-mobx-kit/main";
2+
import { makeAutoObservable } from "mobx";
3+
import { CSSProperties, ReactNode } from "react";
4+
5+
export class EmptyPlaceholderViewModel {
6+
constructor(
7+
private widgets: ComputedAtom<ReactNode>,
8+
private visibleColumnsCount: ComputedAtom<number>,
9+
private config: { checkboxColumnEnabled: boolean; selectorColumnEnabled: boolean }
10+
) {
11+
makeAutoObservable(this);
12+
}
13+
14+
get content(): ReactNode {
15+
return this.widgets.get();
16+
}
17+
18+
get span(): number {
19+
let span = this.visibleColumnsCount.get();
20+
if (this.config.checkboxColumnEnabled) {
21+
span += 1;
22+
}
23+
if (this.config.selectorColumnEnabled) {
24+
span += 1;
25+
}
26+
return Math.max(span, 1);
27+
}
28+
29+
get style(): CSSProperties {
30+
return { gridColumn: `span ${this.span}` };
31+
}
32+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { computed, observable } from "mobx";
2+
import { ReactNode } from "react";
3+
import { EmptyPlaceholderViewModel } from "../EmptyPlaceholder.viewModel";
4+
5+
describe("EmptyPlaceholderViewModel", () => {
6+
describe("style getter", () => {
7+
it("reacts to changes in visible columns count", () => {
8+
const mockWidgets = computed(() => "Empty message" as ReactNode);
9+
const columnCount = observable.box(3);
10+
const config = { checkboxColumnEnabled: false, selectorColumnEnabled: false };
11+
12+
const viewModel = new EmptyPlaceholderViewModel(mockWidgets, columnCount, config);
13+
14+
expect(viewModel.style).toEqual({ gridColumn: "span 3" });
15+
16+
columnCount.set(5);
17+
expect(viewModel.style).toEqual({ gridColumn: "span 5" });
18+
19+
columnCount.set(0);
20+
expect(viewModel.style).toEqual({ gridColumn: "span 1" });
21+
});
22+
23+
it("reacts to changes in visible columns count with config flags enabled", () => {
24+
const mockWidgets = computed(() => "Empty message" as ReactNode);
25+
const columnCount = observable.box(3);
26+
const config = { checkboxColumnEnabled: true, selectorColumnEnabled: true };
27+
28+
const viewModel = new EmptyPlaceholderViewModel(mockWidgets, columnCount, config);
29+
30+
expect(viewModel.style).toEqual({ gridColumn: "span 5" });
31+
32+
columnCount.set(5);
33+
expect(viewModel.style).toEqual({ gridColumn: "span 7" });
34+
35+
columnCount.set(0);
36+
expect(viewModel.style).toEqual({ gridColumn: "span 2" });
37+
});
38+
});
39+
40+
describe("content getter", () => {
41+
it("returns widgets from atom", () => {
42+
const message = "Empty message";
43+
const atom = computed(() => message);
44+
const columnCount = observable.box(3);
45+
const config = { checkboxColumnEnabled: false, selectorColumnEnabled: false };
46+
47+
const viewModel = new EmptyPlaceholderViewModel(atom, columnCount, config);
48+
49+
expect(viewModel.content).toBe(message);
50+
});
51+
});
52+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createInjectionHooks } from "brandi-react";
2+
import { TOKENS } from "../../model/tokens";
3+
4+
export const [useEmptyPlaceholderVM] = createInjectionHooks(TOKENS.emptyPlaceholderVM);

packages/pluggableWidgets/datagrid-web/src/model/containers/Datagrid.container.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { WidgetFilterAPI } from "@mendix/widget-plugin-filtering/context";
22
import { CombinedFilter } from "@mendix/widget-plugin-filtering/stores/generic/CombinedFilter";
33
import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
4+
import { itemCountAtom } from "@mendix/widget-plugin-grid/core/models/datasource.model";
5+
import { emptyStateWidgetsAtom } from "@mendix/widget-plugin-grid/core/models/empty-state.model";
46
import { DatasourceService, ProgressService, SelectionCounterViewModel } from "@mendix/widget-plugin-grid/main";
57
import { ClosableGateProvider } from "@mendix/widget-plugin-mobx-kit/ClosableGateProvider";
68
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
@@ -13,6 +15,7 @@ import { ColumnGroupStore } from "../../helpers/state/ColumnGroupStore";
1315
import { GridBasicData } from "../../helpers/state/GridBasicData";
1416
import { GridPersonalizationStore } from "../../helpers/state/GridPersonalizationStore";
1517
import { DatagridConfig, datagridConfig } from "../configs/Datagrid.config";
18+
import { visibleColumnsCountAtom } from "../models/columns.model";
1619
import { DatasourceParamsController } from "../services/DatasourceParamsController";
1720
import { DerivedLoaderController } from "../services/DerivedLoaderController";
1821
import { PaginationController } from "../services/PaginationController";
@@ -125,6 +128,11 @@ export class DatagridContainer extends Container {
125128
// Bind select all enabled flag
126129
this.bind(TOKENS.enableSelectAll).toConstant(props.enableSelectAll);
127130

131+
// Atoms
132+
this.bind(TOKENS.visibleColumnsCount).toInstance(visibleColumnsCountAtom).inTransientScope();
133+
this.bind(TOKENS.visibleRowCount).toInstance(itemCountAtom).inTransientScope();
134+
this.bind(TOKENS.emptyPlaceholderWidgets).toInstance(emptyStateWidgetsAtom).inTransientScope();
135+
128136
this.postInit(props, config);
129137

130138
return this;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ComputedAtom } from "@mendix/widget-plugin-mobx-kit/main";
2+
import { computed } from "mobx";
3+
4+
/** @injectable */
5+
export function visibleColumnsCountAtom(source: { visibleColumns: { length: number } }): ComputedAtom<number> {
6+
return computed(() => source.visibleColumns.length);
7+
}

packages/pluggableWidgets/datagrid-web/src/model/tokens.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import { FilterAPI, WidgetFilterAPI } from "@mendix/widget-plugin-filtering/context";
22
import { CombinedFilter, CombinedFilterConfig } from "@mendix/widget-plugin-filtering/stores/generic/CombinedFilter";
33
import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
4+
import { itemCountAtom } from "@mendix/widget-plugin-grid/core/models/datasource.model";
5+
import { emptyStateWidgetsAtom } from "@mendix/widget-plugin-grid/core/models/empty-state.model";
46
import {
57
DatasourceService,
68
QueryService,
79
SelectAllService,
810
SelectionCounterViewModel,
911
TaskProgressService
1012
} from "@mendix/widget-plugin-grid/main";
11-
import { DerivedPropsGate, SetupComponentHost } from "@mendix/widget-plugin-mobx-kit/main";
13+
import { ComputedAtom, DerivedPropsGate, SetupComponentHost } from "@mendix/widget-plugin-mobx-kit/main";
1214
import { injected, token } from "brandi";
1315
import { ListValue } from "mendix";
16+
import { ReactNode } from "react";
1417
import { SelectionCounterPositionEnum } from "../../typings/DatagridProps";
1518
import { MainGateProps } from "../../typings/MainGateProps";
19+
import { EmptyPlaceholderViewModel } from "../features/empty-message/EmptyPlaceholder.viewModel";
1620
import { SelectAllBarViewModel } from "../features/select-all/SelectAllBar.viewModel";
1721
import { SelectAllGateProps } from "../features/select-all/SelectAllGateProps";
1822
import { SelectionProgressDialogViewModel } from "../features/select-all/SelectionProgressDialog.viewModel";
@@ -21,6 +25,7 @@ import { GridBasicData } from "../helpers/state/GridBasicData";
2125
import { GridPersonalizationStore } from "../helpers/state/GridPersonalizationStore";
2226
import { DatasourceParamsController } from "../model/services/DatasourceParamsController";
2327
import { DatagridConfig } from "./configs/Datagrid.config";
28+
import { visibleColumnsCountAtom } from "./models/columns.model";
2429
import { DerivedLoaderController, DerivedLoaderControllerConfig } from "./services/DerivedLoaderController";
2530
import { PaginationConfig, PaginationController } from "./services/PaginationController";
2631

@@ -31,6 +36,8 @@ export const TOKENS = {
3136
combinedFilter: token<CombinedFilter>("CombinedFilter"),
3237
combinedFilterConfig: token<CombinedFilterConfig>("CombinedFilterKey"),
3338
config: token<DatagridConfig>("DatagridConfig"),
39+
emptyPlaceholderVM: token<EmptyPlaceholderViewModel>("EmptyPlaceholderViewModel"),
40+
emptyPlaceholderWidgets: token<ComputedAtom<ReactNode>>("@computed:emptyPlaceholder"),
3441
enableSelectAll: token<boolean>("enableSelectAll"),
3542
exportProgressService: token<TaskProgressService>("ExportProgressService"),
3643
filterAPI: token<FilterAPI>("FilterAPI"),
@@ -53,7 +60,9 @@ export const TOKENS = {
5360
selectionCounterPosition: token<SelectionCounterPositionEnum>("SelectionCounterPositionEnum"),
5461
selectionCounterVM: token<SelectionCounterViewModel>("SelectionCounterViewModel"),
5562
selectionDialogVM: token<SelectionProgressDialogViewModel>("SelectionProgressDialogViewModel"),
56-
setupService: token<SetupComponentHost>("DatagridSetupHost")
63+
setupService: token<SetupComponentHost>("DatagridSetupHost"),
64+
visibleColumnsCount: token<ComputedAtom<number>>("@computed:visibleColumnsCount"),
65+
visibleRowCount: token<ComputedAtom<number>>("@computed:visibleRowCount")
5766
};
5867

5968
/** Inject dependencies */
@@ -96,3 +105,11 @@ injected(
96105
TOKENS.selectAllProgressService,
97106
TOKENS.selectAllService
98107
);
108+
109+
injected(EmptyPlaceholderViewModel, TOKENS.emptyPlaceholderWidgets, TOKENS.visibleColumnsCount, TOKENS.config);
110+
111+
injected(visibleColumnsCountAtom, TOKENS.columnsStore);
112+
113+
injected(itemCountAtom, TOKENS.mainGate);
114+
115+
injected(emptyStateWidgetsAtom, TOKENS.mainGate, TOKENS.visibleRowCount);

packages/pluggableWidgets/datagrid-web/typings/MainGateProps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ export type MainGateProps = Pick<
2626
| "cancelSelectionLabel"
2727
| "selectionCounterPosition"
2828
| "enableSelectAll"
29+
| "emptyPlaceholder"
2930
>;

0 commit comments

Comments
 (0)