From ccbf76a75eaee43ba81402313c14f34690c1f332 Mon Sep 17 00:00:00 2001 From: Ehsan Rezaei Date: Fri, 10 Mar 2023 22:26:15 +0100 Subject: [PATCH] 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 --- .../datatable/datatable.component.html | 4 +- .../datatable/datatable.component.spec.ts | 55 +++++++++++++------ .../datatable/datatable.component.ts | 36 +++++++++++- .../resizable/resizable.directive.spec.ts | 12 ---- .../resizable/resizable.directive.ts | 25 +-------- .../resizable/resize-handle.directive.ts | 19 ++++--- .../process-list-cloud.component.spec.ts | 16 ++---- .../task-list-cloud.component.spec.ts | 19 ++----- 8 files changed, 96 insertions(+), 90 deletions(-) diff --git a/lib/core/src/lib/datatable/components/datatable/datatable.component.html b/lib/core/src/lib/datatable/components/datatable/datatable.component.html index 7ac73519e2..83066fc6b6 100644 --- a/lib/core/src/lib/datatable/components/datatable/datatable.component.html +++ b/lib/core/src/lib/datatable/components/datatable/datatable.component.html @@ -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)}">
diff --git a/lib/core/src/lib/datatable/components/datatable/datatable.component.spec.ts b/lib/core/src/lib/datatable/components/datatable/datatable.component.spec.ts index 72dd89173a..195ad20b69 100644 --- a/lib/core/src/lib/datatable/components/datatable/datatable.component.spec.ts +++ b/lib/core/src/lib/datatable/components/datatable/datatable.component.spec.ts @@ -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(); - }); }); diff --git a/lib/core/src/lib/datatable/components/datatable/datatable.component.ts b/lib/core/src/lib/datatable/components/datatable/datatable.component.ts index f15625e116..183cd731aa 100644 --- a/lib/core/src/lib/datatable/components/datatable/datatable.component.ts +++ b/lib/core/src/lib/datatable/components/datatable/datatable.component.ts @@ -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; @@ -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); } } diff --git a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts index 5eae86cb9b..96986fae37 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.spec.ts @@ -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(); - }); }); diff --git a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts index df98f6bb1c..0abe34e3f1 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resizable.directive.ts @@ -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(); - private static MINIMUM_COLUMN_SIZE = 100; - constructor( private readonly renderer: Renderer2, private readonly element: ElementRef, @@ -118,11 +116,7 @@ export class ResizableDirective implements OnInit, OnDestroy { ngOnInit(): void { const mousedown$: Observable = merge(this.pointerDown, this.mousedown); - const mousemove$: Observable = merge(this.pointerMove, this.mousemove) - .pipe( - tap((event) => this.preventDefaultEvent(event)), - share() - ); + const mousemove$: Observable = merge(this.pointerMove, this.mousemove); const mouseup$: Observable = 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; - } } diff --git a/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.ts b/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.ts index 97396ed913..1a8c56be49 100644 --- a/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.ts +++ b/lib/core/src/lib/datatable/directives/resizable/resize-handle.directive.ts @@ -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); diff --git a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.spec.ts index cda467329a..2fd1e24389 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.spec.ts @@ -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', () => { diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.spec.ts index ac3607ece1..76a07b791f 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.spec.ts @@ -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', () => {