Skip to content

Commit 90c3e6f

Browse files
committed
feat: switch to DI container
1 parent 9ead46d commit 90c3e6f

File tree

17 files changed

+318
-117
lines changed

17 files changed

+318
-117
lines changed

packages/pluggableWidgets/datagrid-web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
"@mendix/widget-plugin-mobx-kit": "workspace:*",
5151
"@mendix/widget-plugin-platform": "workspace:*",
5252
"@radix-ui/react-progress": "^1.1.7",
53+
"brandi": "^5.0.0",
54+
"brandi-react": "^5.0.0",
5355
"classnames": "^2.5.1",
5456
"mobx": "6.12.3",
5557
"mobx-react-lite": "4.0.7",
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { FilterAPI, WidgetFilterAPI } from "@mendix/widget-plugin-filtering/context";
2+
import { CombinedFilter, CombinedFilterConfig } from "@mendix/widget-plugin-filtering/stores/generic/CombinedFilter";
3+
import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
4+
import { DatasourceController } from "@mendix/widget-plugin-grid/query/DatasourceController";
5+
import { QueryController } from "@mendix/widget-plugin-grid/query/query-controller";
6+
import { RefreshController } from "@mendix/widget-plugin-grid/query/RefreshController";
7+
import { SelectionCountStore } from "@mendix/widget-plugin-grid/selection/stores/SelectionCountStore";
8+
import { ClosableGateProvider } from "@mendix/widget-plugin-mobx-kit/ClosableGateProvider";
9+
import { GateProvider } from "@mendix/widget-plugin-mobx-kit/GateProvider";
10+
import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate";
11+
import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
12+
import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup";
13+
import { ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller";
14+
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
15+
import { Container, injected, token } from "brandi";
16+
import { useEffect } from "react";
17+
import { DatagridContainerProps } from "../typings/DatagridProps";
18+
import { DatasourceParamsController } from "./controllers/DatasourceParamsController";
19+
import { DerivedLoaderController, DerivedLoaderControllerConfig } from "./controllers/DerivedLoaderController";
20+
import { PaginationConfig, PaginationController } from "./controllers/PaginationController";
21+
import { ProgressStore } from "./features/data-export/ProgressStore";
22+
import { ColumnGroupStore } from "./helpers/state/ColumnGroupStore";
23+
import { GridBasicData } from "./helpers/state/GridBasicData";
24+
import { GridPersonalizationStore } from "./helpers/state/GridPersonalizationStore";
25+
import { DatagridSetupService } from "./services/DatagridSetupService";
26+
import { StaticInfo } from "./typings/static-info";
27+
28+
/** Type to declare props available through main gait. */
29+
type MainGateProps = Pick<
30+
DatagridContainerProps,
31+
| "name"
32+
| "datasource"
33+
| "refreshInterval"
34+
| "refreshIndicator"
35+
| "itemSelection"
36+
| "columns"
37+
| "configurationStorageType"
38+
| "storeFiltersInPersonalization"
39+
| "configurationAttribute"
40+
| "pageSize"
41+
| "pagination"
42+
| "showPagingButtons"
43+
| "showNumberOfRows"
44+
| "clearSelectionButtonLabel"
45+
>;
46+
47+
/** Tokens to resolve dependencies from the container. */
48+
const TOKENS = {
49+
basicDate: token<GridBasicData>("GridBasicData"),
50+
columnsStore: token<ColumnGroupStore>("ColumnGroupStore"),
51+
combinedFilter: token<CombinedFilter>("CombinedFilter"),
52+
combinedFilterConfig: token<CombinedFilterConfig>("CombinedFilterKey"),
53+
exportProgressService: token<ProgressStore>("ExportProgressService"),
54+
filterAPI: token<FilterAPI>("FilterAPI"),
55+
filterHost: token<CustomFilterHost>("FilterHost"),
56+
loaderViewModel: token<DerivedLoaderController>("DatagridLoaderViewModel"),
57+
loaderConfig: token<DerivedLoaderControllerConfig>("DatagridLoaderConfig"),
58+
mainGate: token<DerivedPropsGate<MainGateProps>>("MainGateForProps"),
59+
paginationService: token<PaginationController>("PaginationService"),
60+
paginationConfig: token<PaginationConfig>("PaginationConfig"),
61+
paramsService: token<DatasourceParamsController>("DatagridParamsService"),
62+
parentChannelName: token<string>("parentChannelName"),
63+
personalizationService: token<GridPersonalizationStore>("GridPersonalizationStore"),
64+
query: token<QueryController>("QueryService"),
65+
refreshService: token<RefreshController>("DatagridRefreshService"),
66+
refreshInterval: token<number>("refreshInterval"),
67+
selectionCounter: token<SelectionCountStore>("SelectionCountStore"),
68+
setupService: token<ReactiveControllerHost>("DatagridSetupHost"),
69+
staticInfo: token<StaticInfo>("StaticInfo")
70+
};
71+
72+
/** Root Datagrid container that resolve and inject dependencies. */
73+
export const rootContainer = new Container();
74+
75+
// Class bindings, must be unique per container.
76+
rootContainer.bind(TOKENS.columnsStore).toInstance(ColumnGroupStore).inContainerScope();
77+
rootContainer.bind(TOKENS.combinedFilter).toInstance(CombinedFilter).inContainerScope();
78+
rootContainer.bind(TOKENS.exportProgressService).toInstance(ProgressStore).inContainerScope();
79+
rootContainer.bind(TOKENS.filterAPI).toInstance(WidgetFilterAPI).inContainerScope();
80+
rootContainer.bind(TOKENS.filterHost).toInstance(CustomFilterHost).inContainerScope();
81+
rootContainer.bind(TOKENS.query).toInstance(DatasourceController).inContainerScope();
82+
rootContainer.bind(TOKENS.refreshService).toInstance(RefreshController).inContainerScope();
83+
rootContainer.bind(TOKENS.setupService).toInstance(DatagridSetupService).inContainerScope();
84+
rootContainer.bind(TOKENS.paramsService).toInstance(DatasourceParamsController).inContainerScope();
85+
rootContainer
86+
.bind(TOKENS.parentChannelName)
87+
.toInstance(() => `datagrid/${generateUUID()}`)
88+
.inContainerScope();
89+
90+
// Inject dependencies
91+
injected(SelectionCountStore, TOKENS.mainGate);
92+
injected(DatasourceController, TOKENS.setupService, TOKENS.mainGate);
93+
injected(WidgetFilterAPI, TOKENS.parentChannelName, TOKENS.filterHost);
94+
injected(ColumnGroupStore, TOKENS.mainGate, TOKENS.staticInfo, TOKENS.filterHost);
95+
injected(DerivedLoaderController, TOKENS.query, TOKENS.exportProgressService, TOKENS.columnsStore, TOKENS.loaderConfig);
96+
injected(RefreshController, TOKENS.setupService, TOKENS.query, TOKENS.refreshInterval.optional);
97+
injected(DatasourceParamsController, TOKENS.setupService, TOKENS.query, TOKENS.combinedFilter, TOKENS.columnsStore);
98+
99+
/** Create new container that inherit bindings from root container. */
100+
export function createContainer(): Container {
101+
return new Container().extend(rootContainer);
102+
}
103+
104+
export function useDatagridDepsContainer(props: DatagridContainerProps): Container {
105+
const [container, mainGateProvider] = useConst(
106+
/** Function to clone container and setup prop dependant bindings. */
107+
function init(): [Container, GateProvider<MainGateProps>] {
108+
const container = createContainer();
109+
const exportProgress = container.get(TOKENS.exportProgressService);
110+
const gateProvider = new ClosableGateProvider<MainGateProps>(props, () => exportProgress.exporting);
111+
112+
// Bind main gate
113+
container.bind(TOKENS.mainGate).toConstant(gateProvider.gate);
114+
115+
// Bind static info
116+
container.bind(TOKENS.staticInfo).toConstant({
117+
name: props.name,
118+
filtersChannelName: container.get(TOKENS.parentChannelName)
119+
});
120+
121+
container.bind(TOKENS.refreshInterval).toConstant(props.refreshInterval * 1000);
122+
123+
// Bind combined filter config
124+
container.bind(TOKENS.combinedFilterConfig).toConstant({
125+
stableKey: props.name,
126+
inputs: [container.get(TOKENS.filterHost), container.get(TOKENS.columnsStore)]
127+
});
128+
129+
// Bind loader config
130+
container.bind(TOKENS.loaderConfig).toConstant({
131+
showSilentRefresh: props.refreshInterval > 1,
132+
refreshIndicator: props.refreshIndicator
133+
});
134+
135+
// Bind pagination config
136+
container.bind(TOKENS.paginationConfig).toConstant({
137+
pagination: props.pagination,
138+
showPagingButtons: props.showPagingButtons,
139+
showNumberOfRows: props.showNumberOfRows,
140+
pageSize: props.pageSize
141+
});
142+
143+
// Create internal services
144+
container.get(TOKENS.refreshService);
145+
container.get(TOKENS.paramsService);
146+
147+
// Hydrate filters from props
148+
container.get(TOKENS.combinedFilter).hydrate(props.datasource.filter);
149+
150+
return [container, gateProvider];
151+
}
152+
);
153+
154+
// Run setup hooks on mount
155+
useSetup(() => container.get(TOKENS.setupService));
156+
157+
// Push props through the main gate
158+
useEffect(() => mainGateProvider.setProps(props));
159+
160+
return container;
161+
}

packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,14 @@ interface ObservableSortStore {
1313
sortInstructions: SortInstruction[] | undefined;
1414
}
1515

16-
type DatasourceParamsControllerSpec = {
17-
query: QueryController;
18-
filterHost: ObservableFilterStore;
19-
sortHost: ObservableSortStore;
20-
};
21-
2216
export class DatasourceParamsController implements ReactiveController {
23-
private query: QueryController;
24-
private filterHost: ObservableFilterStore;
25-
private sortHost: ObservableSortStore;
26-
27-
constructor(host: ReactiveControllerHost, spec: DatasourceParamsControllerSpec) {
17+
constructor(
18+
host: ReactiveControllerHost,
19+
private query: QueryController,
20+
private filterHost: ObservableFilterStore,
21+
private sortHost: ObservableSortStore
22+
) {
2823
host.addController(this);
29-
this.filterHost = spec.filterHost;
30-
this.sortHost = spec.sortHost;
31-
this.query = spec.query;
3224
}
3325

3426
setup(): () => void {
Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1+
import { QueryController } from "@mendix/widget-plugin-grid/query/query-controller";
12
import { computed, makeObservable } from "mobx";
3+
import { ProgressStore } from "../features/data-export/ProgressStore";
4+
import { IColumnGroupStore } from "../helpers/state/ColumnGroupStore";
25

3-
type DerivedLoaderControllerSpec = {
6+
export interface DerivedLoaderControllerConfig {
47
showSilentRefresh: boolean;
58
refreshIndicator: boolean;
6-
exp: { exporting: boolean };
7-
cols: { loaded: boolean };
8-
query: {
9-
isFetchingNextBatch: boolean;
10-
isFirstLoad: boolean;
11-
isRefreshing: boolean;
12-
isSilentRefresh: boolean;
13-
};
14-
};
9+
}
1510

1611
export class DerivedLoaderController {
17-
constructor(private spec: DerivedLoaderControllerSpec) {
12+
constructor(
13+
private query: QueryController,
14+
private exp: ProgressStore,
15+
private cols: IColumnGroupStore,
16+
private config: DerivedLoaderControllerConfig
17+
) {
1818
makeObservable(this, {
1919
isFirstLoad: computed,
2020
isFetchingNextBatch: computed,
@@ -23,7 +23,7 @@ export class DerivedLoaderController {
2323
}
2424

2525
get isFirstLoad(): boolean {
26-
const { cols, exp, query } = this.spec;
26+
const { cols, exp, query } = this;
2727
if (!cols.loaded) {
2828
return true;
2929
}
@@ -36,24 +36,20 @@ export class DerivedLoaderController {
3636
}
3737

3838
get isFetchingNextBatch(): boolean {
39-
return this.spec.query.isFetchingNextBatch;
39+
return this.query.isFetchingNextBatch;
4040
}
4141

4242
get isRefreshing(): boolean {
43-
const { isSilentRefresh, isRefreshing } = this.spec.query;
43+
const { isSilentRefresh, isRefreshing } = this.query;
4444

45-
if (this.spec.showSilentRefresh) {
45+
if (this.config.showSilentRefresh) {
4646
return isSilentRefresh || isRefreshing;
4747
}
4848

4949
return !isSilentRefresh && isRefreshing;
5050
}
5151

5252
get showRefreshIndicator(): boolean {
53-
if (!this.spec.refreshIndicator) {
54-
return false;
55-
}
56-
57-
return this.isRefreshing;
53+
return this.config.refreshIndicator ? this.isRefreshing : false;
5854
}
5955
}

packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,28 @@
11
import { QueryController } from "@mendix/widget-plugin-grid/query/query-controller";
2-
import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate";
32
import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller";
43
import { PaginationEnum, ShowPagingButtonsEnum } from "../../typings/DatagridProps";
54

6-
type Gate = DerivedPropsGate<{
7-
pageSize: number;
5+
export interface PaginationConfig {
86
pagination: PaginationEnum;
97
showPagingButtons: ShowPagingButtonsEnum;
108
showNumberOfRows: boolean;
11-
}>;
12-
13-
type PaginationControllerSpec = {
14-
gate: Gate;
15-
query: QueryController;
16-
};
9+
pageSize: number;
10+
}
1711

1812
type PaginationKind = `${PaginationEnum}.${ShowPagingButtonsEnum}`;
1913

2014
export class PaginationController implements ReactiveController {
21-
private gate: Gate;
22-
private query: QueryController;
2315
readonly pagination: PaginationEnum;
2416
readonly paginationKind: PaginationKind;
25-
readonly showPagingButtons: ShowPagingButtonsEnum;
26-
readonly showNumberOfRows: boolean;
2717

28-
constructor(host: ReactiveControllerHost, { gate, query }: PaginationControllerSpec) {
18+
constructor(
19+
host: ReactiveControllerHost,
20+
private readonly config: PaginationConfig,
21+
private query: QueryController
22+
) {
2923
host.addController(this);
30-
this.gate = gate;
31-
this.query = query;
32-
this.pagination = gate.props.pagination;
33-
this.showPagingButtons = gate.props.showPagingButtons;
34-
this.showNumberOfRows = gate.props.showNumberOfRows;
35-
this.paginationKind = `${this.pagination}.${this.showPagingButtons}`;
24+
this.pagination = config.pagination;
25+
this.paginationKind = `${this.pagination}.${config.showPagingButtons}`;
3626
this.setInitParams();
3727
}
3828

@@ -41,7 +31,7 @@ export class PaginationController implements ReactiveController {
4131
}
4232

4333
get pageSize(): number {
44-
return this.gate.props.pageSize;
34+
return this.config.pageSize;
4535
}
4636

4737
get currentPage(): number {
@@ -61,12 +51,12 @@ export class PaginationController implements ReactiveController {
6151
return totalCount > this.query.limit;
6252
}
6353
default:
64-
return this.showNumberOfRows;
54+
return this.config.showNumberOfRows;
6555
}
6656
}
6757

6858
private setInitParams(): void {
69-
if (this.pagination === "buttons" || this.showNumberOfRows) {
59+
if (this.pagination === "buttons" || this.config.showNumberOfRows) {
7060
this.query.requestTotalCount(true);
7161
}
7262

0 commit comments

Comments
 (0)