Skip to content

Commit 16ae85c

Browse files
committed
refactor: split select-all feature
1 parent b7be089 commit 16ae85c

21 files changed

Lines changed: 466 additions & 408 deletions
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { createInjectionHooks } from "brandi-react";
2-
import { TOKENS } from "../../model/tokens";
2+
import { DG_TOKENS as DG } from "../../model/tokens";
33

4-
export const [useEmptyPlaceholderVM] = createInjectionHooks(TOKENS.emptyPlaceholderVM);
4+
export const [useEmptyPlaceholderVM] = createInjectionHooks(DG.emptyPlaceholderVM);
Lines changed: 25 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,54 @@
1-
import { DerivedPropsGate, SetupComponent, SetupComponentHost } from "@mendix/widget-plugin-mobx-kit/main";
2-
import { DynamicValue, ListValue, SelectionMultiValue, SelectionSingleValue } from "mendix";
3-
import { action, makeAutoObservable, reaction } from "mobx";
4-
5-
type DynamicProps = {
6-
datasource: ListValue;
7-
selectAllTemplate?: DynamicValue<string>;
8-
selectAllText?: DynamicValue<string>;
9-
itemSelection?: SelectionSingleValue | SelectionMultiValue;
10-
allSelectedText?: DynamicValue<string>;
11-
};
12-
13-
interface SelectService {
14-
selectAllPages(): Promise<{ success: boolean }> | { success: boolean };
15-
clearSelection(): void;
16-
}
17-
18-
interface CounterService {
19-
selectedCount: number;
20-
selectedCountText: string;
21-
clearButtonLabel: string;
22-
}
1+
import { SelectAllEvents } from "@mendix/widget-plugin-grid/select-all/select-all.model";
2+
import { Emitter } from "@mendix/widget-plugin-mobx-kit/main";
3+
import { makeAutoObservable } from "mobx";
234

245
/** @injectable */
25-
export class SelectAllBarViewModel implements SetupComponent {
26-
private barVisible = false;
27-
private clearVisible = false;
28-
29-
pending = false;
30-
6+
export class SelectAllBarViewModel {
317
constructor(
32-
host: SetupComponentHost,
33-
private readonly gate: DerivedPropsGate<DynamicProps>,
34-
private readonly selectService: SelectService,
35-
private readonly count: CounterService,
36-
private readonly enableSelectAll: boolean
8+
private emitter: Emitter<SelectAllEvents>,
9+
private state: { pending: boolean; visible: boolean; clearBtnVisible: boolean },
10+
private selectionTexts: {
11+
clearSelectionButtonLabel: string;
12+
selectedCountText: string;
13+
},
14+
private selectAllTexts: {
15+
selectAllLabel: string;
16+
selectionStatus: string;
17+
},
18+
private enableSelectAll: boolean
3719
) {
38-
host.add(this);
39-
type PrivateMembers = "setClearVisible" | "setPending" | "hideBar" | "showBar";
40-
makeAutoObservable<this, PrivateMembers>(this, {
41-
setClearVisible: action,
42-
setPending: action,
43-
hideBar: action,
44-
showBar: action
45-
});
46-
}
47-
48-
private get props(): DynamicProps {
49-
return this.gate.props;
50-
}
51-
52-
private setClearVisible(value: boolean): void {
53-
this.clearVisible = value;
54-
}
55-
56-
private setPending(value: boolean): void {
57-
this.pending = value;
58-
}
59-
60-
private hideBar(): void {
61-
this.barVisible = false;
62-
this.clearVisible = false;
63-
}
64-
65-
private showBar(): void {
66-
this.barVisible = true;
67-
}
68-
69-
private get total(): number {
70-
return this.props.datasource.totalCount ?? 0;
71-
}
72-
73-
private get selectAllFormat(): string {
74-
return this.props.selectAllTemplate?.value ?? "Select all %d rows in the data source";
75-
}
76-
77-
private get selectAllText(): string {
78-
return this.props.selectAllText?.value ?? "Select all rows in the data source";
79-
}
80-
81-
private get allSelectedText(): string {
82-
const str = this.props.allSelectedText?.value ?? "All %d rows selected.";
83-
return str.replace("%d", `${this.count.selectedCount}`);
84-
}
85-
86-
private get isCurrentPageSelected(): boolean {
87-
const selection = this.props.itemSelection;
88-
89-
if (!selection || selection.type === "Single") return false;
90-
91-
const pageIds = new Set(this.props.datasource.items?.map(item => item.id) ?? []);
92-
const selectionSubArray = selection.selection.filter(item => pageIds.has(item.id));
93-
return selectionSubArray.length === pageIds.size && pageIds.size > 0;
94-
}
95-
96-
private get isAllItemsSelected(): boolean {
97-
if (this.total > 0) return this.total === this.count.selectedCount;
98-
99-
const { offset, limit, items = [], hasMoreItems } = this.gate.props.datasource;
100-
const noMoreItems = typeof hasMoreItems === "boolean" && hasMoreItems === false;
101-
const fullyLoaded = offset === 0 && limit >= items.length;
102-
103-
return fullyLoaded && noMoreItems && items.length === this.count.selectedCount;
20+
makeAutoObservable(this);
10421
}
10522

10623
get selectAllLabel(): string {
107-
if (this.total > 0) return this.selectAllFormat.replace("%d", `${this.total}`);
108-
return this.selectAllText;
24+
return this.selectAllTexts.selectAllLabel;
10925
}
11026

11127
get clearSelectionLabel(): string {
112-
return this.count.clearButtonLabel;
28+
return this.selectionTexts.clearSelectionButtonLabel;
11329
}
11430

11531
get selectionStatus(): string {
116-
if (this.isAllItemsSelected) return this.allSelectedText;
117-
return this.count.selectedCountText;
32+
return this.selectAllTexts.selectionStatus;
11833
}
11934

12035
get isBarVisible(): boolean {
121-
return this.enableSelectAll && this.barVisible;
36+
return this.enableSelectAll && this.state.visible;
12237
}
12338

12439
get isClearVisible(): boolean {
125-
return this.clearVisible;
40+
return this.state.clearBtnVisible;
12641
}
12742

12843
get isSelectAllDisabled(): boolean {
129-
return this.pending;
130-
}
131-
132-
setup(): (() => void) | void {
133-
if (!this.enableSelectAll) return;
134-
135-
return reaction(
136-
() => this.isCurrentPageSelected,
137-
isCurrentPageSelected => {
138-
if (isCurrentPageSelected === false) {
139-
this.hideBar();
140-
} else if (this.isAllItemsSelected === false) {
141-
this.showBar();
142-
}
143-
}
144-
);
44+
return this.state.pending;
14545
}
14646

14747
onClear(): void {
148-
this.selectService.clearSelection();
48+
this.emitter.emit("clear");
14949
}
15050

151-
async onSelectAll(): Promise<void> {
152-
this.setPending(true);
153-
try {
154-
const { success } = await this.selectService.selectAllPages();
155-
this.setClearVisible(success);
156-
} finally {
157-
this.setPending(false);
158-
}
51+
onSelectAll(): void {
52+
this.emitter.emit("startSelecting");
15953
}
16054
}

packages/pluggableWidgets/datagrid-web/src/features/select-all/SelectAllGateProps.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,105 @@
1-
import { DatasourceService, ProgressService, SelectAllService } from "@mendix/widget-plugin-grid/main";
1+
import { DatasourceService, SelectAllService, TaskProgressService } from "@mendix/widget-plugin-grid/main";
22
import { GateProvider } from "@mendix/widget-plugin-mobx-kit/GateProvider";
33
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
4-
import { Container } from "brandi";
5-
import { TOKENS } from "../../model/tokens";
6-
import { SelectAllGateProps } from "./SelectAllGateProps";
4+
import { Container, injected } from "brandi";
5+
6+
import { SelectAllFeature } from "@mendix/widget-plugin-grid/select-all/select-all.feature";
7+
import { selectAllEmitter, selectAllTextsStore } from "@mendix/widget-plugin-grid/select-all/select-all.model";
8+
import { SelectAllBarStore } from "@mendix/widget-plugin-grid/select-all/SelectAllBar.store";
9+
import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/main";
10+
import { MainGateProps } from "../../../typings/MainGateProps";
11+
import { DatagridConfig } from "../../model/configs/Datagrid.config";
12+
import { CORE_TOKENS as CORE, DG_TOKENS as DG, SA_TOKENS } from "../../model/tokens";
13+
import { SelectAllBarViewModel } from "./SelectAllBar.viewModel";
14+
import { SelectionProgressDialogViewModel } from "./SelectionProgressDialog.viewModel";
15+
16+
injected(
17+
selectAllTextsStore,
18+
SA_TOKENS.gate,
19+
CORE.selection.selectedCount,
20+
CORE.selection.selectedCounterTextsStore,
21+
CORE.atoms.totalCount,
22+
CORE.selection.isAllItemsSelected
23+
);
24+
25+
injected(
26+
SelectAllBarViewModel,
27+
SA_TOKENS.emitter,
28+
SA_TOKENS.barStore,
29+
CORE.selection.selectedCounterTextsStore,
30+
SA_TOKENS.selectAllTextsStore,
31+
SA_TOKENS.enableSelectAll
32+
);
33+
34+
injected(
35+
SelectionProgressDialogViewModel,
36+
CORE.setupService,
37+
SA_TOKENS.gate,
38+
SA_TOKENS.progressService,
39+
SA_TOKENS.selectAllService
40+
);
41+
42+
injected(
43+
SelectAllFeature,
44+
CORE.setupService,
45+
SA_TOKENS.emitter,
46+
SA_TOKENS.selectAllService,
47+
SA_TOKENS.barStore,
48+
SA_TOKENS.progressService,
49+
CORE.selection.isCurrentPageSelected,
50+
CORE.selection.isAllItemsSelected
51+
);
52+
53+
injected(SelectAllService, SA_TOKENS.gate, DG.query, SA_TOKENS.emitter);
754

855
export class SelectAllModule extends Container {
956
id = `SelectAllModule@${generateUUID()}`;
1057

11-
init(props: SelectAllGateProps, root: Container): SelectAllModule {
58+
constructor(root: Container) {
59+
super();
1260
this.extend(root);
61+
this.bind(SA_TOKENS.barStore).toInstance(SelectAllBarStore).inSingletonScope();
62+
this.bind(SA_TOKENS.emitter).toInstance(selectAllEmitter).inSingletonScope();
63+
this.bind(DG.query).toInstance(DatasourceService).inSingletonScope();
64+
this.bind(SA_TOKENS.selectAllService).toInstance(SelectAllService).inSingletonScope();
65+
this.bind(SA_TOKENS.selectAllTextsStore).toInstance(selectAllTextsStore).inSingletonScope();
66+
this.bind(SA_TOKENS.selectAllBarVM).toInstance(SelectAllBarViewModel).inSingletonScope();
67+
this.bind(SA_TOKENS.selectionDialogVM).toInstance(SelectionProgressDialogViewModel).inSingletonScope();
68+
this.bind(SA_TOKENS.feature).toInstance(SelectAllFeature).inSingletonScope();
69+
}
70+
71+
init(dependencies: {
72+
props: MainGateProps;
73+
mainGate: DerivedPropsGate<MainGateProps>;
74+
progressSrv: TaskProgressService;
75+
config: DatagridConfig;
76+
}): SelectAllModule {
77+
const { props, config, mainGate, progressSrv } = dependencies;
1378

14-
const gateProvider = new GateProvider<SelectAllGateProps>(props);
15-
this.setProps = props => gateProvider.setProps(props);
79+
const ownGate = new GateProvider<MainGateProps>(props);
80+
this.setProps = props => ownGate.setProps(props);
1681

17-
// Bind service deps
18-
this.bind(TOKENS.selectAllGate).toConstant(gateProvider.gate);
19-
this.bind(TOKENS.queryGate).toConstant(gateProvider.gate);
20-
this.bind(TOKENS.query).toInstance(DatasourceService).inSingletonScope();
21-
this.bind(TOKENS.selectAllProgressService).toInstance(ProgressService).inSingletonScope();
82+
this.bind(CORE.config).toConstant(config);
83+
// Bind main gate from main provider.
84+
this.bind(CORE.mainGate).toConstant(mainGate);
85+
this.bind(SA_TOKENS.progressService).toConstant(progressSrv);
86+
this.bind(SA_TOKENS.gate).toConstant(ownGate.gate);
87+
this.bind(DG.queryGate).toConstant(ownGate.gate);
88+
this.bind(SA_TOKENS.enableSelectAll).toConstant(config.enableSelectAll);
2289

23-
// Finally bind select all service
24-
this.bind(TOKENS.selectAllService).toInstance(SelectAllService).inSingletonScope();
90+
this.postInit();
2591

2692
return this;
2793
}
2894

29-
setProps = (_props: SelectAllGateProps): void => {
95+
postInit(): void {
96+
// Initialize feature
97+
if (this.get(SA_TOKENS.enableSelectAll)) {
98+
this.get(SA_TOKENS.feature);
99+
}
100+
}
101+
102+
setProps = (_props: MainGateProps): void => {
30103
throw new Error(`${this.id} is not initialized yet`);
31104
};
32105
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createInjectionHooks } from "brandi-react";
2-
import { TOKENS } from "../../model/tokens";
2+
import { SA_TOKENS } from "../../model/tokens";
33

4-
export const [useSelectAllBarViewModel] = createInjectionHooks(TOKENS.selectAllBarVM);
5-
export const [useSelectionDialogViewModel] = createInjectionHooks(TOKENS.selectionDialogVM);
4+
export const [useSelectAllBarViewModel] = createInjectionHooks(SA_TOKENS.selectAllBarVM);
5+
export const [useSelectionDialogViewModel] = createInjectionHooks(SA_TOKENS.selectionDialogVM);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { createInjectionHooks } from "brandi-react";
2-
import { TOKENS } from "../../model/tokens";
2+
import { DG_TOKENS } from "../../model/tokens";
33

4-
export const [useSelectionCounterViewModel] = createInjectionHooks(TOKENS.selectionCounterVM);
4+
export const [useSelectionCounterViewModel] = createInjectionHooks(DG_TOKENS.selectionCounterVM);

packages/pluggableWidgets/datagrid-web/src/model/configs/Datagrid.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface DatagridConfig {
1212
selectionEnabled: boolean;
1313
selectorColumnEnabled: boolean;
1414
settingsStorageEnabled: boolean;
15+
enableSelectAll: boolean;
1516
}
1617

1718
export function datagridConfig(props: DatagridContainerProps): DatagridConfig {
@@ -26,7 +27,8 @@ export function datagridConfig(props: DatagridContainerProps): DatagridConfig {
2627
selectAllCheckboxEnabled: props.showSelectAllToggle,
2728
selectionEnabled: isSelectionEnabled(props),
2829
selectorColumnEnabled: props.columnsHidable,
29-
settingsStorageEnabled: isSettingsStorageEnabled(props)
30+
settingsStorageEnabled: isSettingsStorageEnabled(props),
31+
enableSelectAll: props.enableSelectAll
3032
});
3133
}
3234

0 commit comments

Comments
 (0)