Skip to content

Commit 831cb09

Browse files
Abhinegi2sleidigtomwwinter
authored
fix(bulk actions): use shift key to select a range of records (#2451)
closes #2290 --------- Co-authored-by: Sebastian <[email protected]> Co-authored-by: Tom Winter <[email protected]>
1 parent 10c5e72 commit 831cb09

File tree

5 files changed

+113
-19
lines changed

5 files changed

+113
-19
lines changed

src/app/core/common-components/entities-table/entities-table.component.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,19 @@
4646
BULK SELECT
4747
-->
4848
<ng-container [matColumnDef]="ACTIONCOLUMN_SELECT">
49-
<th mat-header-cell *matHeaderCellDef style="width: 0"></th>
49+
<th mat-header-cell *matHeaderCellDef style="width: 0">
50+
<mat-checkbox
51+
(change)="selectAllRows($event)"
52+
[checked]="isAllSelected()"
53+
[indeterminate]="isIndeterminate()"
54+
></mat-checkbox>
55+
</th>
5056

5157
<td mat-cell *matCellDef="let row">
5258
<mat-checkbox
59+
(change)="onRowSelect($event, row)"
5360
[checked]="selectedRecords?.includes(row.record)"
61+
(click)="$event.stopPropagation()"
5462
></mat-checkbox>
5563
</td>
5664
</ng-container>
@@ -81,7 +89,7 @@
8189
[class.inactive-row]="!row.record.isActive"
8290
[style.background-color]="getBackgroundColor?.(row.record)"
8391
class="table-row"
84-
(click)="onRowClick(row)"
92+
(mousedown)="onRowMouseDown($event, row)"
8593
style="cursor: pointer"
8694
></tr>
8795
</table>

src/app/core/common-components/entities-table/entities-table.component.ts

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import { EntityFieldEditComponent } from "../entity-field-edit/entity-field-edit
1010
import { EntityFieldLabelComponent } from "../entity-field-label/entity-field-label.component";
1111
import { EntityFieldViewComponent } from "../entity-field-view/entity-field-view.component";
1212
import { ListPaginatorComponent } from "./list-paginator/list-paginator.component";
13-
import { MatCheckboxModule } from "@angular/material/checkbox";
13+
import {
14+
MatCheckboxChange,
15+
MatCheckboxModule,
16+
} from "@angular/material/checkbox";
1417
import { MatProgressBarModule } from "@angular/material/progress-bar";
1518
import { MatSlideToggleModule } from "@angular/material/slide-toggle";
1619
import {
@@ -77,6 +80,8 @@ export class EntitiesTableComponent<T extends Entity> {
7780
this.updateFilteredData();
7881
this.isLoading = false;
7982
}
83+
private lastSelectedIndex: number = null;
84+
private lastSelection: boolean = null;
8085
_records: T[] = [];
8186
/** data displayed in the template's table */
8287
recordsDataSource: MatTableDataSource<TableRow<T>>;
@@ -235,7 +240,6 @@ export class EntitiesTableComponent<T extends Entity> {
235240
this.selectedRecords.splice(index, 1);
236241
}
237242
}
238-
239243
this.selectedRecordsChange.emit(this.selectedRecords);
240244
}
241245

@@ -263,7 +267,6 @@ export class EntitiesTableComponent<T extends Entity> {
263267
if (row.formGroup && !row.formGroup.disabled) {
264268
return;
265269
}
266-
267270
if (this._selectable) {
268271
this.selectRow(row, !this.selectedRecords?.includes(row.record));
269272
return;
@@ -273,6 +276,79 @@ export class EntitiesTableComponent<T extends Entity> {
273276
this.entityClick.emit(row.record);
274277
}
275278

279+
onRowMouseDown(event: MouseEvent, row: TableRow<T>) {
280+
if (!this._selectable) {
281+
this.onRowClick(row);
282+
return;
283+
}
284+
285+
// Find the index of the row in the sorted and filtered data
286+
const sortedData = this.recordsDataSource.sortData(
287+
this.recordsDataSource.data,
288+
this.recordsDataSource.sort,
289+
);
290+
const currentIndex = sortedData.indexOf(row);
291+
292+
const isCheckboxClick =
293+
event.target instanceof HTMLInputElement &&
294+
event.target.type === "checkbox";
295+
296+
if (event.shiftKey && this.lastSelectedIndex !== null) {
297+
const start = Math.min(this.lastSelectedIndex, currentIndex);
298+
const end = Math.max(this.lastSelectedIndex, currentIndex);
299+
const shouldCheck =
300+
this.lastSelection !== null
301+
? !this.lastSelection
302+
: !this.selectedRecords.includes(row.record);
303+
304+
for (let i = start; i <= end; i++) {
305+
const rowToSelect = sortedData[i];
306+
const isSelected = this.selectedRecords.includes(rowToSelect.record);
307+
308+
if (shouldCheck && !isSelected) {
309+
this.selectedRecords.push(rowToSelect.record);
310+
} else if (!shouldCheck && isSelected) {
311+
this.selectedRecords = this.selectedRecords.filter(
312+
(record) => record !== rowToSelect.record,
313+
);
314+
}
315+
}
316+
this.selectedRecordsChange.emit(this.selectedRecords);
317+
} else {
318+
const isSelected = this.selectedRecords.includes(row.record);
319+
this.selectRow(row, !isSelected);
320+
this.lastSelectedIndex = currentIndex;
321+
this.lastSelection = isSelected;
322+
}
323+
324+
if (isCheckboxClick) {
325+
this.onRowClick(row);
326+
}
327+
}
328+
329+
onRowSelect(event: MatCheckboxChange, row: TableRow<T>) {
330+
this.selectRow(row, event.checked);
331+
}
332+
333+
selectAllRows(event: MatCheckboxChange) {
334+
if (event.checked) {
335+
this.selectedRecords = this.recordsDataSource.data.map(
336+
(row) => row.record,
337+
);
338+
} else {
339+
this.selectedRecords = [];
340+
}
341+
this.selectedRecordsChange.emit(this.selectedRecords);
342+
}
343+
344+
isAllSelected() {
345+
return this.selectedRecords.length === this.recordsDataSource.data.length;
346+
}
347+
348+
isIndeterminate() {
349+
return this.selectedRecords.length > 0 && !this.isAllSelected();
350+
}
351+
276352
showEntity(entity: T) {
277353
switch (this.clickMode) {
278354
case "popup":

src/app/core/entity-list/entity-list/entity-list.component.html

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
<app-view-actions>
99
<div class="flex-row gap-regular">
10-
<ng-container *ngTemplateOutlet="bulkActions"></ng-container>
11-
1210
<app-entity-create-button
1311
[entityType]="entityConstructor"
1412
(entityCreate)="addNew()"
@@ -35,6 +33,12 @@
3533
></app-filter>
3634
</div>
3735

36+
<!-- Bulk Actions -->
37+
38+
<ng-container *ngTemplateOutlet="bulkActions"></ng-container>
39+
40+
<!-- Tab Groups-->
41+
3842
<div class="mat-elevation-z1">
3943
<div *ngIf="groups.length > 1">
4044
<mat-tab-group
@@ -57,17 +61,23 @@
5761

5862
<!-- Mobile Version -->
5963
<div *ngIf="!isDesktop">
60-
<h2>{{ title }}</h2>
64+
<app-view-title [disableBackButton]="true">
65+
<h2>{{ title }}</h2>
66+
</app-view-title>
6167

62-
<div class="flex-row">
63-
<div *ngTemplateOutlet="filterDialog"></div>
68+
<app-view-actions>
69+
<div class="flex-row full-width">
70+
<div *ngTemplateOutlet="filterDialog"></div>
6471

65-
<button mat-icon-button color="primary" [matMenuTriggerFor]="additional">
66-
<fa-icon icon="ellipsis-v"></fa-icon>
67-
</button>
68-
</div>
72+
<button mat-icon-button color="primary" [matMenuTriggerFor]="additional">
73+
<fa-icon icon="ellipsis-v"></fa-icon>
74+
</button>
75+
</div>
76+
</app-view-actions>
6977

70-
<ng-container *ngTemplateOutlet="bulkActions"></ng-container>
78+
<div *ngIf="selectedRows" class="bulk-action-spacing">
79+
<ng-container *ngTemplateOutlet="bulkActions"></ng-container>
80+
</div>
7181

7282
<ng-container *ngTemplateOutlet="subrecord"></ng-container>
7383
</div>
@@ -239,7 +249,9 @@ <h2>{{ title }}</h2>
239249

240250
<ng-template #bulkActions>
241251
<div *ngIf="!!selectedRows" class="bulk-action-button">
242-
<div i18n>Actions on selected records:</div>
252+
<div i18n>
253+
Actions on <b>{{ selectedRows.length }}</b> selected records:
254+
</div>
243255

244256
<div
245257
class="flex-row gap-small bulk-action-button"

src/app/core/entity-list/entity-list/entity-list.component.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
}
1616

1717
.bulk-action-button {
18-
position: fixed;
1918
right: sizes.$large;
20-
z-index: 999;
2119

2220
padding: sizes.$regular;
2321
background-color: colors.$background;

src/app/core/ui/routed-view/routed-view.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="flex-row">
1+
<div class="flex-row flex-wrap">
22
<div class="flex-grow">
33
<ng-container
44
*ngTemplateOutlet="viewContext?.title?.template"

0 commit comments

Comments
 (0)