Skip to content

Commit 6afea60

Browse files
fix: auto sizing grid columns would create tiny columns when too many columns (#2057)
--------- Co-authored-by: Antoine Hurard <[email protected]>
1 parent c207ccc commit 6afea60

File tree

2 files changed

+101
-60
lines changed

2 files changed

+101
-60
lines changed

libs/shared/src/lib/components/ui/core-grid/grid/grid.component.ts

Lines changed: 72 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
Component,
44
ElementRef,
55
EventEmitter,
6-
HostListener,
76
Inject,
87
Input,
98
OnChanges,
@@ -54,6 +53,12 @@ import { UnsubscribeComponent } from '../../../utils/unsubscribe/unsubscribe.com
5453
import { DOCUMENT } from '@angular/common';
5554
import { WidgetComponent } from '../../../widget/widget.component';
5655
import { DatePipe } from '../../../../pipes/date/date.pipe';
56+
import { ResizeObservable } from '../../../../utils/rxjs/resize-observable.util';
57+
58+
/** Minimum column width */
59+
const MIN_COLUMN_WIDTH = 100;
60+
/** Maximum column width */
61+
const MAX_COLUMN_WIDTH = 250;
5762

5863
/**
5964
* Test if an element match a css selector
@@ -283,14 +288,6 @@ export class GridComponent
283288
};
284289
}
285290

286-
/**
287-
* Listen to window resize event in order to trigger the column automatic width update
288-
*/
289-
@HostListener('window:resize', ['$event'])
290-
onWindowResize() {
291-
this.setColumnsWidth();
292-
}
293-
294291
/**
295292
* Constructor of the grid component
296293
*
@@ -304,6 +301,7 @@ export class GridComponent
304301
* @param dashboardService Dashboard service
305302
* @param translate The translate service
306303
* @param snackBar The snackbar service
304+
* @param el Ref to html element
307305
* @param document document
308306
*/
309307
constructor(
@@ -317,6 +315,7 @@ export class GridComponent
317315
private dashboardService: DashboardService,
318316
private translate: TranslateService,
319317
private snackBar: SnackbarService,
318+
private el: ElementRef,
320319
@Inject(DOCUMENT) private document: Document
321320
) {
322321
super();
@@ -373,6 +372,9 @@ export class GridComponent
373372
500
374373
);
375374
});
375+
new ResizeObservable(this.gridRef.nativeElement)
376+
.pipe(debounceTime(100), takeUntil(this.destroy$))
377+
.subscribe(() => this.setColumnsWidth());
376378
}
377379

378380
override ngOnDestroy(): void {
@@ -752,7 +754,6 @@ export class GridComponent
752754
this.action.emit({ action: 'cancel' });
753755
}
754756

755-
// === EXPORT ===
756757
/**
757758
* Downloads file of record.
758759
*
@@ -789,7 +790,6 @@ export class GridComponent
789790
});
790791
}
791792

792-
// === UTILITIES ===
793793
/**
794794
* Checks if element overflows
795795
*
@@ -912,7 +912,7 @@ export class GridComponent
912912
}
913913

914914
/**
915-
* Calls layout format from utils.ts to get the formated fields
915+
* Calls layout format from utils.ts to get the formatted fields
916916
*
917917
* @param name Content of the field as a string
918918
* @param field Field data
@@ -978,14 +978,15 @@ export class GridComponent
978978
* Automatically set the width of each column
979979
*/
980980
private setColumnsWidth() {
981+
console.log(this.layout);
981982
const gridElement = this.gridRef.nativeElement;
982983
const gridTotalWidth = gridElement.offsetWidth;
983984

984985
// Stores the columns width percentage
985986
const activeColumns: { [key: string]: number } = {};
986987

987988
// Verify what kind of field is and deal with this logic
988-
const typesFields: any = [];
989+
const typesFields: { field: string; type: string; title: string }[] = [];
989990
this.fields.forEach((field: any) => {
990991
typesFields.push({
991992
field: field.name,
@@ -1017,34 +1018,23 @@ export class GridComponent
10171018
activeColumns[type.field] < data[type.field].length) ||
10181019
(type.title && activeColumns[type.field] < type.title.length)
10191020
) {
1020-
let defaultLengthValue = type.title.length;
1021+
const titleSize = type.title.length;
1022+
let contentSize = 0;
10211023
switch (type.type) {
10221024
case 'time':
10231025
case 'datetime-local':
10241026
case 'datetime':
10251027
case 'date': {
1026-
const contentDefaultValue = (
1027-
this.datePipe.transform(data[type.field]) || ''
1028-
).length;
1029-
if (contentDefaultValue > defaultLengthValue) {
1030-
defaultLengthValue = contentDefaultValue;
1031-
}
1032-
activeColumns[type.field] = defaultLengthValue;
1028+
contentSize = (this.datePipe.transform(data[type.field]) || '')
1029+
.length;
10331030
break;
10341031
}
10351032
case 'file': {
1036-
if (data[type.field][0]?.name?.length > defaultLengthValue) {
1037-
defaultLengthValue = data[type.field][0]?.name.length;
1038-
}
1039-
activeColumns[type.field] = defaultLengthValue;
1033+
contentSize = data[type.field][0]?.name?.length || 0;
10401034
break;
10411035
}
10421036
case 'numeric': {
1043-
const contentDefaultValue = data[type.field]?.toString()?.length;
1044-
if (contentDefaultValue > defaultLengthValue) {
1045-
defaultLengthValue = contentDefaultValue;
1046-
}
1047-
activeColumns[type.field] = defaultLengthValue;
1037+
contentSize = data[type.field]?.toString()?.length;
10481038
break;
10491039
}
10501040
case 'checkbox':
@@ -1053,30 +1043,46 @@ export class GridComponent
10531043
(data[type.field] || []).forEach((obj: any) => {
10541044
checkboxLength += obj.length;
10551045
});
1056-
if (checkboxLength > defaultLengthValue) {
1057-
defaultLengthValue = checkboxLength;
1058-
}
1059-
activeColumns[type.field] = defaultLengthValue;
1046+
contentSize = checkboxLength;
10601047
break;
10611048
}
10621049
case 'boolean':
10631050
case 'color': {
10641051
//min size
1065-
activeColumns[type.field] = type.title;
1052+
contentSize = 0;
10661053
break;
10671054
}
10681055
default: {
1069-
const contentDefaultValue = (data[type.field] || '').length;
1070-
if (contentDefaultValue > defaultLengthValue) {
1071-
defaultLengthValue = contentDefaultValue;
1072-
}
1073-
activeColumns[type.field] = defaultLengthValue;
1056+
contentSize = (data[type.field] || '').length;
10741057
}
10751058
}
1059+
1060+
activeColumns[type.field] = Math.max(titleSize, contentSize);
10761061
}
10771062
});
10781063
});
10791064

1065+
const avgPixelPerCol = gridTotalWidth / typesFields.length;
1066+
1067+
// If there are too many columns, we can't do the calculations by percentage
1068+
// Instead, clamp the columns to the min and max width
1069+
if (avgPixelPerCol < MIN_COLUMN_WIDTH * 1.1) {
1070+
this.columns.forEach((column) => {
1071+
const colWidth = activeColumns[column.field];
1072+
if (colWidth) {
1073+
column.width = Math.min(
1074+
Math.max(colWidth * pixelWidthPerCharacter, MIN_COLUMN_WIDTH),
1075+
MAX_COLUMN_WIDTH
1076+
);
1077+
}
1078+
1079+
// Make sure that every column has a width set
1080+
if (column.width <= 0) {
1081+
column.width = MIN_COLUMN_WIDTH;
1082+
}
1083+
});
1084+
return;
1085+
}
10801086
// Calculates the widest column in character number
10811087
const maxCharacterToDisplay = Math.floor(
10821088
maxPixelsPerColumn / pixelWidthPerCharacter
@@ -1119,27 +1125,29 @@ export class GridComponent
11191125
// Order the values from thinner to wider column element
11201126
arrayColumns.sort((a, b) => a.value - b.value);
11211127

1122-
const widestColumnIndex = arrayColumns.length - 1;
1123-
// if the value of the smallest element is 4x times smaller than the widest one
1124-
// or the total percentage did not reach 100% after all conversions
1125-
// we adjust the overall percentages set for columns
1126-
while (
1127-
arrayColumns[0].value < 0.25 * arrayColumns[widestColumnIndex].value ||
1128-
total_percentage < 100
1129-
) {
1130-
// Add the percentage available
1131-
if (total_percentage < 100) {
1132-
activeColumns[arrayColumns[0].key] += 1;
1133-
total_percentage += 1;
1134-
arrayColumns[0].value += 1;
1135-
} else {
1136-
// Remove percentage from the biggest and put in the smallest
1137-
activeColumns[arrayColumns[0].key] += 1;
1138-
activeColumns[arrayColumns[widestColumnIndex].key] -= 1;
1139-
arrayColumns[0].value += 1;
1140-
arrayColumns[widestColumnIndex].value -= 1;
1128+
if (arrayColumns.length > 0) {
1129+
const widestColumnIndex = arrayColumns.length - 1;
1130+
// if the value of the smallest element is 4x times smaller than the widest one
1131+
// or the total percentage did not reach 100% after all conversions
1132+
// we adjust the overall percentages set for columns
1133+
while (
1134+
arrayColumns[0].value < 0.25 * arrayColumns[widestColumnIndex].value ||
1135+
total_percentage < 100
1136+
) {
1137+
// Add the percentage available
1138+
if (total_percentage < 100) {
1139+
activeColumns[arrayColumns[0].key] += 1;
1140+
total_percentage += 1;
1141+
arrayColumns[0].value += 1;
1142+
} else {
1143+
// Remove percentage from the biggest and put in the smallest
1144+
activeColumns[arrayColumns[0].key] += 1;
1145+
activeColumns[arrayColumns[widestColumnIndex].key] -= 1;
1146+
arrayColumns[0].value += 1;
1147+
arrayColumns[widestColumnIndex].value -= 1;
1148+
}
1149+
arrayColumns.sort((a, b) => a.value - b.value);
11411150
}
1142-
arrayColumns.sort((a, b) => a.value - b.value);
11431151
}
11441152

11451153
// Finally, resize the columns
@@ -1157,6 +1165,10 @@ export class GridComponent
11571165
column.width = Math.floor((min_percentage * gridTotalWidth) / 100);
11581166
}
11591167
}
1168+
// Make sure that every column has a width set
1169+
if (column.width <= 0) {
1170+
column.width = MIN_COLUMN_WIDTH;
1171+
}
11601172
});
11611173
}
11621174
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Observable } from 'rxjs';
2+
3+
/**
4+
* Resize observer as observable.
5+
* Allow to use debounceTime before calling callback.
6+
*/
7+
export class ResizeObservable extends Observable<ResizeObserverEntry[]> {
8+
/**
9+
* Resize observer as observable.
10+
* Allow to use debounceTime before calling callback.
11+
*
12+
* @param elem HTML element to observe
13+
*/
14+
constructor(elem: HTMLElement) {
15+
super((subscriber) => {
16+
const ro = new ResizeObserver((entries) => {
17+
subscriber.next(entries);
18+
});
19+
20+
// Observe one or multiple elements
21+
ro.observe(elem);
22+
23+
return function unsubscribe() {
24+
ro.unobserve(elem);
25+
ro.disconnect();
26+
};
27+
});
28+
}
29+
}

0 commit comments

Comments
 (0)