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

View File

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

View File

@ -66,6 +66,7 @@ export enum ShowHeaderMode {
host: { class: 'adf-datatable' } host: { class: 'adf-datatable' }
}) })
export class DataTableComponent implements OnInit, AfterContentInit, OnChanges, DoCheck, OnDestroy, AfterViewInit { export class DataTableComponent implements OnInit, AfterContentInit, OnChanges, DoCheck, OnDestroy, AfterViewInit {
private static MINIMUM_COLUMN_SIZE = 100;
@ViewChildren(DataTableRowComponent) @ViewChildren(DataTableRowComponent)
rowsList: QueryList<DataTableRowComponent>; rowsList: QueryList<DataTableRowComponent>;
@ -934,14 +935,43 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
} }
onResizing({ rectangle: { width } }: ResizeEvent, colIndex: number): void { onResizing({ rectangle: { width } }: ResizeEvent, colIndex: number): void {
const allColumns = this.data.getColumns(); const timeoutId = setTimeout(() => {
allColumns[colIndex].width = width; const allColumns = this.data.getColumns();
this.data.setColumns(allColumns); allColumns[colIndex].width = width;
this.data.setColumns(allColumns);
if (!this.isResizing) {
clearTimeout(timeoutId);
}
});
} }
onResizingEnd(): void { onResizingEnd(): void {
this.isResizing = false; 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 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); 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 } }); 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 { Subject, Observable, Observer, merge } from 'rxjs';
import { BoundingRectangle, ResizeEvent, IResizeMouseEvent, ICoordinateX } from './types'; 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'; import { OnInit, Output, NgZone, OnDestroy, Directive, Renderer2, ElementRef, EventEmitter } from '@angular/core';
@Directive({ @Directive({
@ -64,8 +64,6 @@ export class ResizableDirective implements OnInit, OnDestroy {
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
private static MINIMUM_COLUMN_SIZE = 100;
constructor( constructor(
private readonly renderer: Renderer2, private readonly renderer: Renderer2,
private readonly element: ElementRef<HTMLElement>, private readonly element: ElementRef<HTMLElement>,
@ -118,11 +116,7 @@ export class ResizableDirective implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
const mousedown$: Observable<IResizeMouseEvent> = merge(this.pointerDown, this.mousedown); const mousedown$: Observable<IResizeMouseEvent> = merge(this.pointerDown, this.mousedown);
const mousemove$: Observable<IResizeMouseEvent> = merge(this.pointerMove, this.mousemove) const mousemove$: Observable<IResizeMouseEvent> = merge(this.pointerMove, this.mousemove);
.pipe(
tap((event) => this.preventDefaultEvent(event)),
share()
);
const mouseup$: Observable<IResizeMouseEvent> = merge(this.pointerUp, this.mouseup); const mouseup$: Observable<IResizeMouseEvent> = merge(this.pointerUp, this.mouseup);
@ -159,9 +153,6 @@ export class ResizableDirective implements OnInit, OnDestroy {
.pipe( .pipe(
map(({ clientX }) => this.getNewBoundingRectangle(this.startingRect, clientX)) map(({ clientX }) => this.getNewBoundingRectangle(this.startingRect, clientX))
) )
.pipe(
filter(this.minimumAllowedSize)
)
.subscribe((rectangle: BoundingRectangle) => { .subscribe((rectangle: BoundingRectangle) => {
if (this.resizing.observers.length > 0) { if (this.resizing.observers.length > 0) {
this.zone.run(() => { this.zone.run(() => {
@ -217,14 +208,8 @@ export class ResizableDirective implements OnInit, OnDestroy {
this.destroy$.next(); 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 { private getNewBoundingRectangle({ top, bottom, left, right }: BoundingRectangle, clientX: number): BoundingRectangle {
const updatedRight = right += clientX; const updatedRight = Math.round(right + clientX);
return { return {
top, top,
@ -250,8 +235,4 @@ export class ResizableDirective implements OnInit, OnDestroy {
scrollLeft: nativeElement.scrollLeft 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) { if (event.cancelable) {
event.preventDefault(); event.preventDefault();
} }
this.unlistenMouseMove = this.renderer.listen(
this.element.nativeElement, if (!this.unlistenMouseMove) {
'mousemove', this.unlistenMouseMove = this.renderer.listen(
(mouseMoveEvent: MouseEvent) => { this.element.nativeElement,
this.onMousemove(mouseMoveEvent); 'mousemove',
} (mouseMoveEvent: MouseEvent) => {
); this.onMousemove(mouseMoveEvent);
}
);
}
this.unlistenMouseUp = this.renderer.listen( this.unlistenMouseUp = this.renderer.listen(
this.element.nativeElement, 'document',
'mouseup', 'mouseup',
(mouseUpEvent: MouseEvent) => { (mouseUpEvent: MouseEvent) => {
this.onMouseup(mouseUpEvent); this.onMouseup(mouseUpEvent);

View File

@ -379,19 +379,11 @@ describe('ProcessListCloudComponent', () => {
component.reload(); component.reload();
fixture.detectChanges(); 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')); expect(component.columns[0].width).toBe(120);
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);
}); });
it('should re-create columns when a column order gets changed', () => { 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', () => { it('should re-create columns when a column width gets changed', () => {
component.isResizingEnabled = true;
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
component.reload(); component.reload();
fixture.detectChanges(); 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')); expect(component.columns[0].width).toBe(120);
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);
}); });
it('should re-create columns when a column order gets changed', () => { it('should re-create columns when a column order gets changed', () => {