Skip to content

Commit e83be09

Browse files
committed
feat: add views for select all
1 parent a799e34 commit e83be09

File tree

14 files changed

+500
-135
lines changed

14 files changed

+500
-135
lines changed

packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,8 @@ $root: ".widget-datagrid";
428428
align-items: center;
429429
}
430430

431-
&-exporting {
431+
&-exporting,
432+
&-selecting-all-pages {
432433
.widget-datagrid-top-bar,
433434
.widget-datagrid-header,
434435
.widget-datagrid-content,
@@ -574,22 +575,31 @@ $root: ".widget-datagrid";
574575
align-items: center;
575576
}
576577

577-
#{$root}-clear-selection {
578+
#{$root}-btn-link {
578579
cursor: pointer;
579580
background: transparent;
580581
border: none;
581-
text-decoration: underline;
582582
color: var(--link-color);
583-
padding: 0;
583+
padding: 0.3em 0.5em;
584+
border-radius: 6px;
584585
display: inline-block;
585586

586-
&:focus:not(:focus-visible) {
587-
outline: none;
587+
&:hover,
588+
&:focus-visible {
589+
background-color: var(--brand-primary-50, #e6e7f2);
588590
}
591+
}
589592

590-
&:focus-visible {
591-
outline: 1px solid var(--brand-primary, $brand-primary);
592-
outline-offset: 2px;
593+
:where(#{$root}-select-all-bar) {
594+
grid-column: 1 / -1;
595+
background-color: #f0f1f2;
596+
display: flex;
597+
flex-flow: row nowrap;
598+
align-items: center;
599+
padding: var(--spacing-smaller, 8px) var(--spacing-medium, 16px);
600+
601+
#{$root}-spinner {
602+
padding: 6.2px;
593603
}
594604
}
595605

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

Lines changed: 152 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { GridBasicData } from "./helpers/state/GridBasicData";
2727
import { GridPersonalizationStore } from "./helpers/state/GridPersonalizationStore";
2828
import { DatagridSetupService } from "./services/DatagridSetupService";
2929
import { StaticInfo } from "./typings/static-info";
30+
import { SelectAllBarViewModel } from "./view-models/SelectAllBarViewModel";
31+
import { SelectionProgressDialogViewModel } from "./view-models/SelectionProgressDialogViewModel";
3032

3133
/** Type to declare props available through main gate. */
3234
type MainGateProps = Pick<
@@ -45,6 +47,15 @@ type MainGateProps = Pick<
4547
| "showPagingButtons"
4648
| "showNumberOfRows"
4749
| "clearSelectionButtonLabel"
50+
| "selectAllTemplate"
51+
| "selectAllText"
52+
| "itemSelection"
53+
| "datasource"
54+
| "allSelectedText"
55+
| "selectAllRowsLabel"
56+
| "cancelSelectionLabel"
57+
| "selectionCounterPosition"
58+
| "enableSelectAll"
4859
>;
4960

5061
/** Tokens to resolve dependencies from the container. */
@@ -65,15 +76,18 @@ export const TOKENS = {
6576
parentChannelName: token<string>("parentChannelName"),
6677
personalizationService: token<GridPersonalizationStore>("GridPersonalizationStore"),
6778
query: token<QueryService>("QueryService"),
79+
queryGate: token<DerivedPropsGate<Pick<DatagridContainerProps, "datasource">>>("GateForQuery"),
6880
refreshInterval: token<number>("refreshInterval"),
6981
selectionCounter: token<SelectionCounterViewModel>("SelectionCounterViewModel"),
7082
selectionCounterPosition: token<SelectionCounterPositionEnum>("SelectionCounterPositionEnum"),
7183
setupService: token<SetupComponentHost>("DatagridSetupHost"),
7284
staticInfo: token<StaticInfo>("StaticInfo"),
85+
enableSelectAll: token<boolean>("enableSelectAll"),
7386
selectAllProgressService: token<TaskProgressService>("SelectAllProgressService"),
7487
selectAllGate: token<DerivedPropsGate<SelectAllGateProps>>("SelectAllGateForProps"),
75-
selectAllQuery: token<QueryService>("SelectAllQueryService"),
76-
SelectAllService: token<SelectAllService>("SelectAllService")
88+
selectAllService: token<SelectAllService>("SelectAllService"),
89+
selectAllBar: token<SelectAllBarViewModel>("SelectAllBarViewModel"),
90+
selectionDialogViewModel: token<SelectionProgressDialogViewModel>("SelectionProgressDialogViewModel")
7791
};
7892

7993
/** Deps injections */
@@ -84,22 +98,64 @@ injected(WidgetFilterAPI, TOKENS.parentChannelName, TOKENS.filterHost);
8498
injected(DatasourceParamsController, TOKENS.setupService, TOKENS.query, TOKENS.combinedFilter, TOKENS.columnsStore);
8599
injected(GridPersonalizationStore, TOKENS.setupService, TOKENS.mainGate, TOKENS.columnsStore, TOKENS.filterHost);
86100
injected(PaginationController, TOKENS.setupService, TOKENS.paginationConfig, TOKENS.query);
87-
injected(DatasourceService, TOKENS.setupService, TOKENS.mainGate, TOKENS.refreshInterval.optional);
101+
injected(DatasourceService, TOKENS.setupService, TOKENS.queryGate, TOKENS.refreshInterval.optional);
88102
injected(DerivedLoaderController, TOKENS.query, TOKENS.exportProgressService, TOKENS.columnsStore, TOKENS.loaderConfig);
89103
injected(SelectionCounterViewModel, TOKENS.mainGate, TOKENS.selectionCounterPosition);
104+
injected(SelectAllService, TOKENS.setupService, TOKENS.selectAllGate, TOKENS.query, TOKENS.selectAllProgressService);
90105
injected(
91-
SelectAllService,
106+
SelectAllBarViewModel,
92107
TOKENS.setupService,
93-
TOKENS.selectAllGate,
94-
TOKENS.selectAllQuery,
95-
TOKENS.selectAllProgressService
108+
TOKENS.mainGate,
109+
TOKENS.selectAllService,
110+
TOKENS.selectionCounter,
111+
TOKENS.enableSelectAll
112+
);
113+
injected(
114+
SelectionProgressDialogViewModel,
115+
TOKENS.setupService,
116+
TOKENS.mainGate,
117+
TOKENS.selectAllProgressService,
118+
TOKENS.selectAllService
96119
);
97120

98-
class DatagridContainer extends Container {
121+
/**
122+
* Root container for bindings that can be shared down the hierarchy.
123+
* Use only for bindings that needs to be shared across multiple containers.
124+
* @remark Don't bind things that depend on props here.
125+
*/
126+
class RootContainer extends Container {
127+
id = `DatagridRootContainer@${generateUUID()}`;
128+
99129
constructor() {
100130
super();
131+
this.bind(TOKENS.setupService).toInstance(DatagridSetupService).inSingletonScope();
132+
this.bind(TOKENS.exportProgressService).toInstance(ProgressService).inSingletonScope();
133+
this.bind(TOKENS.selectAllProgressService).toInstance(ProgressService).inSingletonScope();
134+
}
135+
}
101136

102-
// Column store
137+
class DatagridContainer extends Container {
138+
id = `DatagridContainer@${generateUUID()}`;
139+
/**
140+
* Setup container bindings.
141+
* @remark Make sure not to bind things that already exist in root container.
142+
*/
143+
init(props: MainGateProps, root: RootContainer, selectAllModule: DependencyModule): DatagridContainer {
144+
this.extend(root);
145+
146+
const exportProgress = this.get(TOKENS.exportProgressService);
147+
const selectAllProgress = this.get(TOKENS.selectAllProgressService);
148+
const gateProvider = new ClosableGateProvider<MainGateProps>(props, function isLocked() {
149+
return exportProgress.inProgress || selectAllProgress.inProgress;
150+
});
151+
152+
this.setProps = props => gateProvider.setProps(props);
153+
154+
// Bind main gate
155+
this.bind(TOKENS.mainGate).toConstant(gateProvider.gate);
156+
this.bind(TOKENS.queryGate).toConstant(gateProvider.gate);
157+
158+
// Columns store
103159
this.bind(TOKENS.columnsStore).toInstance(ColumnGroupStore).inSingletonScope();
104160

105161
// Basic data store
@@ -111,9 +167,6 @@ class DatagridContainer extends Container {
111167
// Export progress
112168
this.bind(TOKENS.exportProgressService).toInstance(ProgressService).inSingletonScope();
113169

114-
// Select all progress
115-
this.bind(TOKENS.selectAllProgressService).toInstance(ProgressService).inSingletonScope();
116-
117170
// FilterAPI
118171
this.bind(TOKENS.filterAPI).toInstance(WidgetFilterAPI).inSingletonScope();
119172

@@ -145,84 +198,100 @@ class DatagridContainer extends Container {
145198

146199
// Selection counter view model
147200
this.bind(TOKENS.selectionCounter).toInstance(SelectionCounterViewModel).inSingletonScope();
201+
202+
// Select all bar view model
203+
this.bind(TOKENS.selectAllBar).toInstance(SelectAllBarViewModel).inSingletonScope();
204+
205+
// Selection progress dialog view model
206+
this.bind(TOKENS.selectionDialogViewModel).toInstance(SelectionProgressDialogViewModel).inSingletonScope();
207+
208+
// Bind static info
209+
this.bind(TOKENS.staticInfo).toConstant({
210+
name: props.name,
211+
filtersChannelName: this.get(TOKENS.parentChannelName)
212+
});
213+
214+
// Bind refresh interval
215+
this.bind(TOKENS.refreshInterval).toConstant(props.refreshInterval * 1000);
216+
217+
// Bind combined filter config
218+
this.bind(TOKENS.combinedFilterConfig).toConstant({
219+
stableKey: props.name,
220+
inputs: [this.get(TOKENS.filterHost), this.get(TOKENS.columnsStore)]
221+
});
222+
223+
// Bind loader config
224+
this.bind(TOKENS.loaderConfig).toConstant({
225+
showSilentRefresh: props.refreshInterval > 1,
226+
refreshIndicator: props.refreshIndicator
227+
});
228+
229+
// Bind pagination config
230+
this.bind(TOKENS.paginationConfig).toConstant({
231+
pagination: props.pagination,
232+
showPagingButtons: props.showPagingButtons,
233+
showNumberOfRows: props.showNumberOfRows,
234+
pageSize: props.pageSize
235+
});
236+
237+
// Bind selection counter position
238+
this.bind(TOKENS.selectionCounterPosition).toConstant(props.selectionCounterPosition);
239+
240+
// Bind select all enabled flag
241+
this.bind(TOKENS.enableSelectAll).toConstant(props.enableSelectAll);
242+
243+
// Make sure essential services are created upfront
244+
this.get(TOKENS.paramsService);
245+
this.get(TOKENS.paginationService);
246+
247+
// Hydrate filters from props
248+
this.get(TOKENS.combinedFilter).hydrate(props.datasource.filter);
249+
250+
// Bind select all service
251+
this.use(TOKENS.selectAllService).from(selectAllModule);
252+
253+
return this;
148254
}
255+
256+
setProps = (_props: MainGateProps): void => {
257+
throw new Error(`${this.id} is not initialized yet`);
258+
};
149259
}
150260

151261
type SelectAllGateProps = Pick<DatagridContainerProps, "itemSelection" | "datasource">;
262+
263+
/** Module used to bind independent query and gate for select all service */
152264
class SelectAllModule extends DependencyModule {
153-
selectAllGateProvider: GateProvider<SelectAllGateProps>;
154-
constructor(props: SelectAllGateProps) {
155-
super();
156-
this.selectAllGateProvider = new GateProvider<SelectAllGateProps>(props);
157-
// Bind gate
158-
this.bind(TOKENS.selectAllGate).toConstant(this.selectAllGateProvider.gate);
159-
// Bind query
160-
this.bind(TOKENS.selectAllQuery).toInstance(DatasourceService).inSingletonScope();
161-
// Bind progress
162-
this.bind(TOKENS.selectAllProgressService).toInstance(ProgressService).inSingletonScope();
163-
// Bind service
164-
this.bind(TOKENS.SelectAllService).toInstance(SelectAllService).inSingletonScope();
265+
id = `SelectAllModule@${generateUUID()}`;
266+
/** Method for bindings that depend on props. */
267+
init(props: SelectAllGateProps, root: RootContainer): SelectAllModule {
268+
const setupService = root.get(TOKENS.setupService);
269+
const progress = root.get(TOKENS.selectAllProgressService);
270+
const gateProvider = new GateProvider<SelectAllGateProps>(props);
271+
const query = new DatasourceService(setupService, gateProvider.gate);
272+
const selectService = new SelectAllService(setupService, gateProvider.gate, query, progress);
273+
274+
this.setProps = props => gateProvider.setProps(props);
275+
// Finally bind select all service
276+
this.bind(TOKENS.selectAllService).toConstant(selectService);
277+
278+
return this;
165279
}
280+
281+
setProps = (_props: SelectAllGateProps): void => {
282+
throw new Error(`${this.id} is not initialized yet`);
283+
};
166284
}
167285

168286
export function useDatagridDepsContainer(props: DatagridContainerProps): Container {
169-
const [container, mainGateHost, selectAllGateHost] = useConst(
170-
/** Function to clone container and setup prop dependant bindings. */
171-
function init(): [Container, GateProvider<MainGateProps>, GateProvider<SelectAllGateProps>] {
172-
const container = new DatagridContainer();
173-
const selectAllModule = new SelectAllModule(props);
174-
175-
container.use(TOKENS.selectAllProgressService).from(selectAllModule);
176-
container.use(TOKENS.SelectAllService).from(selectAllModule);
177-
178-
const exportProgress = container.get(TOKENS.exportProgressService);
179-
const selectAllProgress = container.get(TOKENS.selectAllProgressService);
180-
const gateProvider = new ClosableGateProvider<MainGateProps>(props, function isLocked() {
181-
return exportProgress.inProgress || selectAllProgress.inProgress;
182-
});
183-
184-
// Bind main gate
185-
container.bind(TOKENS.mainGate).toConstant(gateProvider.gate);
186-
187-
// Bind static info
188-
container.bind(TOKENS.staticInfo).toConstant({
189-
name: props.name,
190-
filtersChannelName: container.get(TOKENS.parentChannelName)
191-
});
192-
193-
container.bind(TOKENS.refreshInterval).toConstant(props.refreshInterval * 1000);
194-
195-
// Bind combined filter config
196-
container.bind(TOKENS.combinedFilterConfig).toConstant({
197-
stableKey: props.name,
198-
inputs: [container.get(TOKENS.filterHost), container.get(TOKENS.columnsStore)]
199-
});
200-
201-
// Bind loader config
202-
container.bind(TOKENS.loaderConfig).toConstant({
203-
showSilentRefresh: props.refreshInterval > 1,
204-
refreshIndicator: props.refreshIndicator
205-
});
206-
207-
// Bind pagination config
208-
container.bind(TOKENS.paginationConfig).toConstant({
209-
pagination: props.pagination,
210-
showPagingButtons: props.showPagingButtons,
211-
showNumberOfRows: props.showNumberOfRows,
212-
pageSize: props.pageSize
213-
});
214-
215-
// Bind selection counter position
216-
container.bind(TOKENS.selectionCounterPosition).toConstant(props.selectionCounterPosition);
217-
218-
// Make sure essential services are created upfront
219-
container.get(TOKENS.paramsService);
220-
container.get(TOKENS.paginationService);
221-
222-
// Hydrate filters from props
223-
container.get(TOKENS.combinedFilter).hydrate(props.datasource.filter);
224-
225-
return [container, gateProvider, selectAllModule.selectAllGateProvider];
287+
const [container, selectAllModule] = useConst(
288+
/** Function to create main container and setup prop dependant bindings. */
289+
function init(): [DatagridContainer, SelectAllModule] {
290+
const root = new RootContainer();
291+
const selectAllModule = new SelectAllModule().init(props, root);
292+
const container = new DatagridContainer().init(props, root, selectAllModule);
293+
294+
return [container, selectAllModule];
226295
}
227296
);
228297

@@ -231,8 +300,8 @@ export function useDatagridDepsContainer(props: DatagridContainerProps): Contain
231300

232301
// Push props through the gates
233302
useEffect(() => {
234-
mainGateHost.setProps(props);
235-
selectAllGateHost.setProps(props);
303+
container.setProps(props);
304+
selectAllModule.setProps(props);
236305
});
237306

238307
return container;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ const DatagridRoot = observer((props: DatagridContainerProps): ReactElement => {
3636
const [abortExport] = useDataExport(props, columnsStore, exportProgress);
3737

3838
const selectionHelper = useSelectionHelper(
39-
props.itemSelection,
40-
props.datasource,
39+
gate.props.itemSelection,
40+
gate.props.datasource,
4141
props.onSelectionChange,
4242
props.keepSelection ? "always keep" : "always clear"
4343
);

0 commit comments

Comments
 (0)