From 5a24eecf56bf33ef4fae75feca8360cfeaee441f Mon Sep 17 00:00:00 2001 From: Shlomi Assaf Date: Thu, 17 Dec 2020 01:01:20 +0200 Subject: [PATCH] fix(ngrid/drag): support row reordering in virtual scroll --- .../features/grid/row-ordering/row-reorder.md | 3 -- .../drag/src/lib/drag-and-drop/core/drag.ts | 4 +- .../src/lib/drag-and-drop/core/drop-list.ts | 3 +- .../src/lib/drag-and-drop/row/row-drag.ts | 8 ++-- .../drag-and-drop/row/row-drop-list-ref.ts | 48 +++++++++++++++++++ .../drag-and-drop/row/row-reorder-plugin.ts | 17 +++++++ ...ngrid-cached-row-view-repeater-strategy.ts | 4 ++ .../src/lib/grid/row/base-row.component.ts | 37 +++++++++++++- libs/ngrid/src/lib/grid/row/rows-api.ts | 11 +++-- 9 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 libs/ngrid/drag/src/lib/drag-and-drop/row/row-drop-list-ref.ts diff --git a/apps/ngrid-docs-app/content/features/grid/row-ordering/row-reorder.md b/apps/ngrid-docs-app/content/features/grid/row-ordering/row-reorder.md index 759d4f5c9..5b82702df 100644 --- a/apps/ngrid-docs-app/content/features/grid/row-ordering/row-reorder.md +++ b/apps/ngrid-docs-app/content/features/grid/row-ordering/row-reorder.md @@ -7,7 +7,4 @@ tags: drag, drop, target-events --- # Row Ordering -W> Row ordering is currently supported only when virtual scroll is turned off. This is because of lack of supported from the `@angular/cdk` framework. -A workaround might be setup in the future. -
diff --git a/libs/ngrid/drag/src/lib/drag-and-drop/core/drag.ts b/libs/ngrid/drag/src/lib/drag-and-drop/core/drag.ts index 958dba19e..fafb4aeea 100644 --- a/libs/ngrid/drag/src/lib/drag-and-drop/core/drag.ts +++ b/libs/ngrid/drag/src/lib/drag-and-drop/core/drag.ts @@ -98,9 +98,7 @@ export class CdkLazyDrag = CdkLazyDropList } ngOnDestroy(): void { - if (this.cdkDropList) { - this.cdkDropList.removeDrag(this); - } + this.cdkDropList?.removeDrag(this); super.ngOnDestroy(); } diff --git a/libs/ngrid/drag/src/lib/drag-and-drop/core/drop-list.ts b/libs/ngrid/drag/src/lib/drag-and-drop/core/drop-list.ts index 52d464e7d..ec2b81ebd 100644 --- a/libs/ngrid/drag/src/lib/drag-and-drop/core/drop-list.ts +++ b/libs/ngrid/drag/src/lib/drag-and-drop/core/drop-list.ts @@ -74,7 +74,6 @@ export class CdkLazyDropList extends CdkDropList impleme if (!(this.pblDropListRef instanceof PblDropListRef)) { throw new Error('Invalid `DropListRef` injection, the ref is not an instance of PblDropListRef') } - this.initDropListRef(); // This is a workaround for https://github.com/angular/material2/pull/14153 // Working around the missing capability for selecting a container element that is not the drop container host. @@ -83,6 +82,8 @@ export class CdkLazyDropList extends CdkDropList impleme if (grid) { this.updateGrid(grid); } + + this.initDropListRef(); } ngOnInit(): void { diff --git a/libs/ngrid/drag/src/lib/drag-and-drop/row/row-drag.ts b/libs/ngrid/drag/src/lib/drag-and-drop/row/row-drag.ts index a7fab1155..68553f319 100644 --- a/libs/ngrid/drag/src/lib/drag-and-drop/row/row-drag.ts +++ b/libs/ngrid/drag/src/lib/drag-and-drop/row/row-drag.ts @@ -20,15 +20,15 @@ import { PblNgridRowReorderPluginDirective, ROW_REORDER_PLUGIN_KEY } from './row export class PblNgridRowDragDirective extends CdkLazyDrag> implements OnInit { rootElementSelector = 'pbl-ngrid-row'; - get context(): Pick, 'col' | 'grid'> & Partial, 'row' | 'value'>> { + get context(): PblNgridCellContext { return this._context; } - @Input('pblNgridRowDrag') set context(value: Pick, 'col' | 'grid'> & Partial, 'row' | 'value'>>) { + @Input('pblNgridRowDrag') set context(value: PblNgridCellContext) { this._context = value; const pluginCtrl = this.pluginCtrl = value && PblNgridPluginController.find(value.grid); - const plugin = pluginCtrl && pluginCtrl.getPlugin(ROW_REORDER_PLUGIN_KEY); + const plugin = pluginCtrl?.getPlugin(ROW_REORDER_PLUGIN_KEY); this.cdkDropList = plugin || undefined; } @@ -43,7 +43,7 @@ export class PblNgridRowDragDirective extends CdkLazyDrag, 'col' | 'grid'> & Partial, 'row' | 'value'>>; + private _context: PblNgridCellContext; private _draggedContext: Pick, 'col' | 'grid'> & Partial, 'row' | 'value'>>; private pluginCtrl: PblNgridPluginController; diff --git a/libs/ngrid/drag/src/lib/drag-and-drop/row/row-drop-list-ref.ts b/libs/ngrid/drag/src/lib/drag-and-drop/row/row-drop-list-ref.ts new file mode 100644 index 000000000..567016d79 --- /dev/null +++ b/libs/ngrid/drag/src/lib/drag-and-drop/row/row-drop-list-ref.ts @@ -0,0 +1,48 @@ +import { PblNgridExtensionApi } from '@pebula/ngrid'; +import { PblDropListRef } from '../core/drop-list-ref'; +import { PblNgridRowReorderPluginDirective } from './row-reorder-plugin'; +import { takeUntil } from 'rxjs/operators'; +import { Type } from '@angular/core'; + +export type _PblDropListRef = Omit>, '_getItemIndexFromPointerPosition' | 'start'> & { + start(): void; + _getItemIndexFromPointerPosition(item: PblRowDropListRef, pointerX: number, pointerY: number, delta?: {x: number, y: number}): number +}; + +export const _PblDropListRef = () => { return PblDropListRef as any as Type<_PblDropListRef>; }; + +export class PblRowDropListRef extends _PblDropListRef() { + + gridApi: PblNgridExtensionApi; + + private scrollDif = 0; + + _getItemIndexFromPointerPosition(item: PblRowDropListRef, pointerX: number, pointerY: number, delta?: {x: number, y: number}): number { + return super._getItemIndexFromPointerPosition(item, pointerX, pointerY - this.scrollDif, delta); + } + + start(): void { + super.start(); + this.scrollDif = 0; + if (this.gridApi.grid.viewport.enabled) { + const initialTop = this.gridApi.grid.viewport.measureScrollOffset(); + this.gridApi.grid.viewport.elementScrolled() + .pipe(takeUntil(this.dropped)) + .subscribe(() => { + this.scrollDif = this.gridApi.grid.viewport.measureScrollOffset() - initialTop; + }); + } + } + +} + +export function patchDropListRef(dropListRef: PblDropListRef>, gridApi: PblNgridExtensionApi) { + try { + Object.setPrototypeOf(dropListRef, PblRowDropListRef.prototype); + } catch (err) { + (dropListRef as any)._getItemIndexFromPointerPosition = PblRowDropListRef.prototype._getItemIndexFromPointerPosition; + dropListRef.start = PblRowDropListRef.prototype.start; + } + + (dropListRef as unknown as PblRowDropListRef).gridApi = gridApi; +} diff --git a/libs/ngrid/drag/src/lib/drag-and-drop/row/row-reorder-plugin.ts b/libs/ngrid/drag/src/lib/drag-and-drop/row/row-reorder-plugin.ts index 095387c8c..b73b19eb5 100644 --- a/libs/ngrid/drag/src/lib/drag-and-drop/row/row-reorder-plugin.ts +++ b/libs/ngrid/drag/src/lib/drag-and-drop/row/row-reorder-plugin.ts @@ -10,6 +10,7 @@ import { import { PblNgridComponent } from '@pebula/ngrid'; import { PblDragDrop, CdkLazyDropList } from '../core/index'; import { PblNgridRowDragDirective } from './row-drag'; +import { patchDropListRef } from './row-drop-list-ref'; declare module '@pebula/ngrid/lib/ext/types' { interface PblNgridPluginExtension { @@ -55,8 +56,24 @@ export class PblNgridRowReorderPluginDirective extends CdkLazyDropList< this._removePlugin(this.grid); } + getSortedItems() { + const { rowsApi } = this.gridApi; + // The CdkTable has a view repeater that cache view's for performance (only when virtual scroll enabled) + // A cached view is not showing but still "living" so it's CdkDrag element is still up in the air + // We need to filter them out + // An alternative will be to catch the events of the rows attached/detached and add/remove them from the drop list. + return (super.getSortedItems() as PblNgridRowDragDirective[]).filter( item => { + return rowsApi.findRowByElement(item.getRootElement())?.attached; + }); + } + + protected initDropListRef(): void { + patchDropListRef(this.pblDropListRef as any, this.gridApi); + } + protected gridChanged() { this._removePlugin = this.gridApi.pluginCtrl.setPlugin(ROW_REORDER_PLUGIN_KEY, this); + this.directContainerElement = '.pbl-ngrid-scroll-container'; this.dropped.subscribe( (event: CdkDragDrop) => { const item = event.item as PblNgridRowDragDirective; diff --git a/libs/ngrid/src/lib/grid/pbl-cdk-table/ngrid-cached-row-view-repeater-strategy.ts b/libs/ngrid/src/lib/grid/pbl-cdk-table/ngrid-cached-row-view-repeater-strategy.ts index 67017bfbc..09984d1e5 100644 --- a/libs/ngrid/src/lib/grid/pbl-cdk-table/ngrid-cached-row-view-repeater-strategy.ts +++ b/libs/ngrid/src/lib/grid/pbl-cdk-table/ngrid-cached-row-view-repeater-strategy.ts @@ -73,6 +73,8 @@ export class PblNgridCachedRowViewRepeaterStrategy, C private _maybeCacheView(view: EmbeddedViewRef, viewContainerRef: ViewContainerRef) { if (this._viewCache.length < this.viewCacheSize) { this._viewCache.push(view); + this.extApi.rowsApi.findRowByElement(view.rootNodes[0]).detach(); + // Notify this row is not part of the view, its cached (however, the component and any child component is not destroyed) } else { const index = viewContainerRef.indexOf(view); @@ -92,6 +94,8 @@ export class PblNgridCachedRowViewRepeaterStrategy, C private _insertViewFromCache(index: number, viewContainerRef: ViewContainerRef): EmbeddedViewRef | null { const cachedView = this._viewCache.pop(); if (cachedView) { + // Notify that the items is not longer cached, now live and playing the game + this.extApi.rowsApi.findRowByElement(cachedView.rootNodes[0]).attach(); viewContainerRef.insert(cachedView, index); } return cachedView || null; diff --git a/libs/ngrid/src/lib/grid/row/base-row.component.ts b/libs/ngrid/src/lib/grid/row/base-row.component.ts index 1d122fc09..6a96447b5 100644 --- a/libs/ngrid/src/lib/grid/row/base-row.component.ts +++ b/libs/ngrid/src/lib/grid/row/base-row.component.ts @@ -37,6 +37,12 @@ export abstract class PblNgridBaseRowComponent, readonly cdRef: ChangeDetectorRef, elementRef: ElementRef) { @@ -73,7 +81,7 @@ export abstract class PblNgridBaseRowComponent, atIndex?: number) { if (!this.canCreateCell || this.canCreateCell(column, atIndex)) { const cell = this.createComponent(column, atIndex); diff --git a/libs/ngrid/src/lib/grid/row/rows-api.ts b/libs/ngrid/src/lib/grid/row/rows-api.ts index c263f4c69..4aa9a56e7 100644 --- a/libs/ngrid/src/lib/grid/row/rows-api.ts +++ b/libs/ngrid/src/lib/grid/row/rows-api.ts @@ -17,7 +17,9 @@ export interface RowsApi { findDataRowByIndex(index: number): PblNgridRowComponent | undefined; findDataRowByDsIndex(index: number): PblNgridRowComponent | undefined; - findDataRowByIdentity(identity: string): PblNgridRowComponent | undefined; + findDataRowByIdentity(identity: string | number): PblNgridRowComponent | undefined; + findRowByElement(element: Element): PblNgridBaseRowComponent | undefined; + } function isPblNgridRowComponent(row: PblNgridBaseRowComponent): row is PblNgridRowComponent { @@ -231,11 +233,14 @@ export class PblRowsApi implements RowsApi { } } - dataRows() { return Array.from(this.rows.get('data')) as PblNgridRowComponent[]; } + findRowByElement(element: Element): PblNgridBaseRowComponent | undefined { + return this.allByElement.get(element); + } + findDataRowByDsIndex(index: number): PblNgridRowComponent | undefined { for (const r of this.dataRows()) { if (r.context?.dsIndex === index) { @@ -252,7 +257,7 @@ export class PblRowsApi implements RowsApi { } } - findDataRowByIdentity(identity: string): PblNgridRowComponent | undefined { + findDataRowByIdentity(identity: string | number): PblNgridRowComponent | undefined { for (const r of this.dataRows()) { if (r.context?.identity === identity) { return r as PblNgridRowComponent;