AAE-12844: Column Resizing Improvements (#8340)

* AAE-12844: Column Resizing Improvements

* AAE-12844: Added extra check on column header query selector

* AAE-12844: Fixed lint issue

* AAE-12844: Fixed unit tests

* AAE-122844: Code improvement

* AAE-12844: Added missing if condition
This commit is contained in:
Ehsan Rezaei 2023-03-10 22:26:15 +01:00 committed by GitHub
parent d1fa1a3cd7
commit ccbf76a75e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 96 additions and 90 deletions

View File

@ -36,7 +36,7 @@
'adf-datatable__cursor--pointer': !isResizing,
'adf-datatable__header--sorted-asc': isColumnSorted(col, 'asc'),
'adf-datatable__header--sorted-desc': isColumnSorted(col, 'desc')}"
[ngStyle]="(col.width) && {'flex': '0 1 ' + col.width + 'px' }"
[ngStyle]="(col.width) && {'flex': getFlexValue(col)}"
[attr.aria-label]="col.title | translate"
(click)="onColumnHeaderClick(col, $event)"
(keyup.enter)="onColumnHeaderClick(col, $event)"
@ -214,7 +214,7 @@
[adf-context-menu]="getContextMenuActions(row, col)"
[adf-context-menu-enabled]="contextMenu"
adf-drop-zone dropTarget="cell" [dropColumn]="col" [dropRow]="row"
[ngStyle]="(col.width) && {'flex': '0 1 ' + col.width + 'px' }">
[ngStyle]="(col.width) && {'flex': getFlexValue(col)}">
<div *ngIf="!col.template" class="adf-datatable-cell-container">
<ng-container [ngSwitch]="data.getColumnType(row, col)">
<div *ngSwitchCase="'image'" class="adf-cell-value">

View File

@ -1928,37 +1928,61 @@ describe('Column Resizing', () => {
expect(tableBody.classList).toContain('adf-blur-datatable-body');
});
it('should set column width on resizing', () => {
it('should set column width on resizing', fakeAsync(() => {
const adapter = dataTable.data;
spyOn(adapter, 'setColumns').and.callThrough();
dataTable.onResizing({ rectangle: { top: 0, bottom: 10, left: 0, right: 20, width: 65 } }, 0);
fixture.detectChanges();
const columns = dataTable.data.getColumns();
tick();
const columns = dataTable.data.getColumns();
expect(columns[0].width).toBe(65);
expect(adapter.setColumns).toHaveBeenCalledWith(columns);
});
}));
it('should set the column header style on resizing', () => {
dataTable.onResizing({ rectangle: { top: 0, bottom: 10, left: 0, right: 20, width: 65 } }, 0);
it('should set the column header style on resizing', fakeAsync(() => {
dataTable.onResizing({ rectangle: { top: 0, bottom: 10, left: 0, right: 20, width: 125 } }, 0);
tick();
fixture.detectChanges();
const headerColumns: HTMLElement[] = fixture.debugElement.nativeElement.querySelectorAll('.adf-datatable-cell-header');
expect(headerColumns[0].style.flex).toBe('0 1 125px');
}));
expect(headerColumns[0].style.flex).toBe('0 1 65px');
});
it('should set the column header to 100px on resizing when its width goes below 100', fakeAsync(() => {
dataTable.onResizing({ rectangle: { top: 0, bottom: 10, left: 0, right: 20, width: 85 } }, 0);
tick();
fixture.detectChanges();
it('should set the style of all the table cells under the resizing header on resizing', () => {
dataTable.onResizing({ rectangle: { top: 0, bottom: 10, left: 0, right: 20, width: 65 } }, 0);
const headerColumns: HTMLElement[] = fixture.debugElement.nativeElement.querySelectorAll('.adf-datatable-cell-header');
expect(headerColumns[0].style.flex).toBe('0 1 100px');
}));
it('should set the style of all the table cells under the resizing header on resizing', fakeAsync(() => {
dataTable.onResizing({ rectangle: { top: 0, bottom: 10, left: 0, right: 20, width: 130 } }, 0);
tick();
fixture.detectChanges();
const tableBody = fixture.debugElement.nativeElement.querySelector('.adf-datatable-body');
const firstCell: HTMLElement = tableBody.querySelector('[data-automation-id="name1"]');
const secondCell: HTMLElement = tableBody.querySelector('[data-automation-id="name2"]');
expect(firstCell.style.flex).toBe('0 1 65px');
expect(secondCell.style.flex).toBe('0 1 65px');
});
expect(firstCell.style.flex).toBe('0 1 130px');
expect(secondCell.style.flex).toBe('0 1 130px');
}));
it('should set the style of all the table cells under the resizing header to 100px on resizing when its width goes below 100', fakeAsync(() => {
dataTable.onResizing({ rectangle: { top: 0, bottom: 10, left: 0, right: 20, width: 85 } }, 0);
tick();
fixture.detectChanges();
const tableBody = fixture.debugElement.nativeElement.querySelector('.adf-datatable-body');
const firstCell: HTMLElement = tableBody.querySelector('[data-automation-id="name1"]');
const secondCell: HTMLElement = tableBody.querySelector('[data-automation-id="name2"]');
expect(firstCell.style.flex).toBe('0 1 100px');
expect(secondCell.style.flex).toBe('0 1 100px');
}));
it('should unblur the body and set the resizing to false upon resizing ends', () => {
dataTable.isResizingEnabled = true;
@ -1976,7 +2000,7 @@ describe('Column Resizing', () => {
resizeHandle.dispatchEvent(new MouseEvent('mousemove'));
fixture.detectChanges();
resizeHandle.dispatchEvent(new MouseEvent('mouseup'));
document.dispatchEvent(new MouseEvent('mouseup'));
fixture.detectChanges();
expect(dataTable.isResizing).toBeFalse();
@ -1998,12 +2022,11 @@ describe('Column Resizing', () => {
resizeHandle.dispatchEvent(new MouseEvent('mousemove'));
fixture.detectChanges();
resizeHandle.dispatchEvent(new MouseEvent('mouseup'));
document.dispatchEvent(new MouseEvent('mouseup'));
fixture.detectChanges();
expect(dataTable.isResizing).toBeFalse();
expect(dataTable.columnsWidthChanged.emit).toHaveBeenCalled();
});
});

View File

@ -66,6 +66,7 @@ export enum ShowHeaderMode {
host: { class: 'adf-datatable' }
})
export class DataTableComponent implements OnInit, AfterContentInit, OnChanges, DoCheck, OnDestroy, AfterViewInit {
private static MINIMUM_COLUMN_SIZE = 100;
@ViewChildren(DataTableRowComponent)
rowsList: QueryList<DataTableRowComponent>;
@ -934,14 +935,43 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
}
onResizing({ rectangle: { width } }: ResizeEvent, colIndex: number): void {
const allColumns = this.data.getColumns();
allColumns[colIndex].width = width;
this.data.setColumns(allColumns);
const timeoutId = setTimeout(() => {
const allColumns = this.data.getColumns();
allColumns[colIndex].width = width;
this.data.setColumns(allColumns);
if (!this.isResizing) {
clearTimeout(timeoutId);
}
});
}
onResizingEnd(): void {
this.isResizing = false;
this.updateColumnsWidths();
}
getFlexValue({ width = 0 }: DataColumn): string {
return `0 1 ${width < DataTableComponent.MINIMUM_COLUMN_SIZE ? DataTableComponent.MINIMUM_COLUMN_SIZE : width}px`;
}
private updateColumnsWidths(): void {
const allColumns = this.data.getColumns();
const headerContainer: HTMLElement = document.querySelector('.adf-datatable-header');
if (headerContainer) {
const headerContainerColumns = headerContainer.querySelectorAll('.adf-datatable-cell-header');
headerContainerColumns.forEach((column: HTMLElement, index: number): void => {
if (allColumns[index]) {
allColumns[index].width = column.offsetWidth ?? DataTableComponent.MINIMUM_COLUMN_SIZE;
}
});
}
this.data.setColumns(allColumns);
this.columnsWidthChanged.emit(allColumns);
}
}

View File

@ -139,16 +139,4 @@ describe('ResizableDirective', () => {
expect(directive.resizing.emit).toHaveBeenCalledWith({ rectangle: { top: 0, left: 0, bottom: 0, right: 120, width: 120 } });
});
it('should NOT emit resizing on mousemove when movement goes under the minimum allowed size [100]', () => {
spyOn(directive.resizing, 'emit');
directive.resizing.subscribe();
const mouseDownEvent = new MouseEvent('mousedown');
const mouseMoveEvent = new MouseEvent('mousemove', { clientX: 99 });
directive.mousedown.next({ ...mouseDownEvent, resize: true });
directive.mousemove.next(mouseMoveEvent);
expect(directive.resizing.emit).not.toHaveBeenCalled();
});
});

View File

@ -17,7 +17,7 @@
import { Subject, Observable, Observer, merge } from 'rxjs';
import { BoundingRectangle, ResizeEvent, IResizeMouseEvent, ICoordinateX } from './types';
import { map, tap, take, share, filter, pairwise, mergeMap, takeUntil } from 'rxjs/operators';
import { map, take, share, filter, pairwise, mergeMap, takeUntil } from 'rxjs/operators';
import { OnInit, Output, NgZone, OnDestroy, Directive, Renderer2, ElementRef, EventEmitter } from '@angular/core';
@Directive({
@ -64,8 +64,6 @@ export class ResizableDirective implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
private static MINIMUM_COLUMN_SIZE = 100;
constructor(
private readonly renderer: Renderer2,
private readonly element: ElementRef<HTMLElement>,
@ -118,11 +116,7 @@ export class ResizableDirective implements OnInit, OnDestroy {
ngOnInit(): void {
const mousedown$: Observable<IResizeMouseEvent> = merge(this.pointerDown, this.mousedown);
const mousemove$: Observable<IResizeMouseEvent> = merge(this.pointerMove, this.mousemove)
.pipe(
tap((event) => this.preventDefaultEvent(event)),
share()
);
const mousemove$: Observable<IResizeMouseEvent> = merge(this.pointerMove, this.mousemove);
const mouseup$: Observable<IResizeMouseEvent> = merge(this.pointerUp, this.mouseup);
@ -159,9 +153,6 @@ export class ResizableDirective implements OnInit, OnDestroy {
.pipe(
map(({ clientX }) => this.getNewBoundingRectangle(this.startingRect, clientX))
)
.pipe(
filter(this.minimumAllowedSize)
)
.subscribe((rectangle: BoundingRectangle) => {
if (this.resizing.observers.length > 0) {
this.zone.run(() => {
@ -217,14 +208,8 @@ export class ResizableDirective implements OnInit, OnDestroy {
this.destroy$.next();
}
private preventDefaultEvent(event: MouseEvent): void {
if ((this.currentRect || this.startingRect) && event.cancelable) {
event.preventDefault();
}
}
private getNewBoundingRectangle({ top, bottom, left, right }: BoundingRectangle, clientX: number): BoundingRectangle {
const updatedRight = right += clientX;
const updatedRight = Math.round(right + clientX);
return {
top,
@ -250,8 +235,4 @@ export class ResizableDirective implements OnInit, OnDestroy {
scrollLeft: nativeElement.scrollLeft
};
}
private minimumAllowedSize({ width = 0 }: BoundingRectangle): boolean {
return width > ResizableDirective.MINIMUM_COLUMN_SIZE;
}
}

View File

@ -65,16 +65,19 @@ export class ResizeHandleDirective implements OnInit, OnDestroy {
if (event.cancelable) {
event.preventDefault();
}
this.unlistenMouseMove = this.renderer.listen(
this.element.nativeElement,
'mousemove',
(mouseMoveEvent: MouseEvent) => {
this.onMousemove(mouseMoveEvent);
}
);
if (!this.unlistenMouseMove) {
this.unlistenMouseMove = this.renderer.listen(
this.element.nativeElement,
'mousemove',
(mouseMoveEvent: MouseEvent) => {
this.onMousemove(mouseMoveEvent);
}
);
}
this.unlistenMouseUp = this.renderer.listen(
this.element.nativeElement,
'document',
'mouseup',
(mouseUpEvent: MouseEvent) => {
this.onMouseup(mouseUpEvent);

View File

@ -379,19 +379,11 @@ describe('ProcessListCloudComponent', () => {
component.reload();
fixture.detectChanges();
const resizeHandle: HTMLElement = fixture.debugElement.nativeElement.querySelector('.adf-datatable__resize-handle');
const newColumns = [...component.columns];
newColumns[0].width = 120;
component.onColumnsWidthChanged(newColumns);
resizeHandle.dispatchEvent(new MouseEvent('mousedown'));
resizeHandle.dispatchEvent(new MouseEvent('mousemove'));
resizeHandle.dispatchEvent(new MouseEvent('mouseup'));
const firstColumnInitialWidth = component.columns[0].width ?? 0;
resizeHandle.dispatchEvent(new MouseEvent('mousedown'));
resizeHandle.dispatchEvent(new MouseEvent('mousemove', { clientX: 25 }));
resizeHandle.dispatchEvent(new MouseEvent('mouseup'));
expect(component.columns[0].width).toBe(firstColumnInitialWidth + 25);
expect(component.columns[0].width).toBe(120);
});
it('should re-create columns when a column order gets changed', () => {

View File

@ -287,25 +287,14 @@ describe('TaskListCloudComponent', () => {
});
it('should re-create columns when a column width gets changed', () => {
component.isResizingEnabled = true;
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
component.reload();
fixture.detectChanges();
const resizeHandle: HTMLElement = fixture.debugElement.nativeElement.querySelector('.adf-datatable__resize-handle');
const newColumns = [...component.columns];
newColumns[0].width = 120;
component.onColumnsWidthChanged(newColumns);
resizeHandle.dispatchEvent(new MouseEvent('mousedown'));
resizeHandle.dispatchEvent(new MouseEvent('mousemove'));
resizeHandle.dispatchEvent(new MouseEvent('mouseup'));
const firstColumnInitialWidth = component.columns[0].width ?? 0;
resizeHandle.dispatchEvent(new MouseEvent('mousedown'));
resizeHandle.dispatchEvent(new MouseEvent('mousemove', { clientX: 25 }));
resizeHandle.dispatchEvent(new MouseEvent('mouseup'));
expect(component.columns[0].width).toBe(firstColumnInitialWidth + 25);
expect(component.columns[0].width).toBe(120);
});
it('should re-create columns when a column order gets changed', () => {