Skip to content

Commit

Permalink
fix(ngrid/drag): support row reordering in virtual scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
shlomiassaf committed Dec 21, 2020
1 parent 95ea261 commit 5a24eec
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<div pbl-example-view="pbl-row-ordering-example"></div>
4 changes: 1 addition & 3 deletions libs/ngrid/drag/src/lib/drag-and-drop/core/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,7 @@ export class CdkLazyDrag<T = any, Z extends CdkLazyDropList<T> = CdkLazyDropList
}

ngOnDestroy(): void {
if (this.cdkDropList) {
this.cdkDropList.removeDrag(this);
}
this.cdkDropList?.removeDrag(this);
super.ngOnDestroy();
}

Expand Down
3 changes: 2 additions & 1 deletion libs/ngrid/drag/src/lib/drag-and-drop/core/drop-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export class CdkLazyDropList<T = any, DRef = any> extends CdkDropList<T> 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.
Expand All @@ -83,6 +82,8 @@ export class CdkLazyDropList<T = any, DRef = any> extends CdkDropList<T> impleme
if (grid) {
this.updateGrid(grid);
}

this.initDropListRef();
}

ngOnInit(): void {
Expand Down
8 changes: 4 additions & 4 deletions libs/ngrid/drag/src/lib/drag-and-drop/row/row-drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ import { PblNgridRowReorderPluginDirective, ROW_REORDER_PLUGIN_KEY } from './row
export class PblNgridRowDragDirective<T = any> extends CdkLazyDrag<T, PblNgridRowReorderPluginDirective<T>> implements OnInit {
rootElementSelector = 'pbl-ngrid-row';

get context(): Pick<PblNgridCellContext<T>, 'col' | 'grid'> & Partial<Pick<PblNgridCellContext<T>, 'row' | 'value'>> {
get context(): PblNgridCellContext<T> {
return this._context;
}

@Input('pblNgridRowDrag') set context(value: Pick<PblNgridCellContext<T>, 'col' | 'grid'> & Partial<Pick<PblNgridCellContext<T>, 'row' | 'value'>>) {
@Input('pblNgridRowDrag') set context(value: PblNgridCellContext<T>) {
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;
}

Expand All @@ -43,7 +43,7 @@ export class PblNgridRowDragDirective<T = any> extends CdkLazyDrag<T, PblNgridRo
return this._draggedContext;
}

private _context: Pick<PblNgridCellContext<T>, 'col' | 'grid'> & Partial<Pick<PblNgridCellContext<T>, 'row' | 'value'>>;
private _context: PblNgridCellContext<T>;
private _draggedContext: Pick<PblNgridCellContext<T>, 'col' | 'grid'> & Partial<Pick<PblNgridCellContext<T>, 'row' | 'value'>>;

private pluginCtrl: PblNgridPluginController;
Expand Down
48 changes: 48 additions & 0 deletions libs/ngrid/drag/src/lib/drag-and-drop/row/row-drop-list-ref.ts
Original file line number Diff line number Diff line change
@@ -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<PblDropListRef<PblNgridRowReorderPluginDirective<any>>, '_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<T = any> extends _PblDropListRef() {

gridApi: PblNgridExtensionApi<T>;

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<T = any>(dropListRef: PblDropListRef<PblNgridRowReorderPluginDirective<T>>, gridApi: PblNgridExtensionApi<T>) {
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;
}
17 changes: 17 additions & 0 deletions libs/ngrid/drag/src/lib/drag-and-drop/row/row-reorder-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -55,8 +56,24 @@ export class PblNgridRowReorderPluginDirective<T = any> 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<T>) => {
const item = event.item as PblNgridRowDragDirective<T>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export class PblNgridCachedRowViewRepeaterStrategy<T, R extends RenderRow<T>, C
private _maybeCacheView(view: EmbeddedViewRef<C>, 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);

Expand All @@ -92,6 +94,8 @@ export class PblNgridCachedRowViewRepeaterStrategy<T, R extends RenderRow<T>, C
private _insertViewFromCache(index: number, viewContainerRef: ViewContainerRef): EmbeddedViewRef<C> | 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;
Expand Down
37 changes: 36 additions & 1 deletion libs/ngrid/src/lib/grid/row/base-row.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export abstract class PblNgridBaseRowComponent<TRowType extends GridRowType, T =

get cellsLength() { return this._cells.length; }

/**
* An attached row will run change detection on it's children.
* All rows are attached by default.
*/
get attached(): boolean { return this._attached; }

abstract readonly rowType: TRowType;

abstract get rowIndex(): number;
Expand All @@ -46,6 +52,8 @@ export abstract class PblNgridBaseRowComponent<TRowType extends GridRowType, T =

protected cellInjector: Injector;

private _attached = true;

constructor(@Optional() grid: PblNgridComponent<T>,
readonly cdRef: ChangeDetectorRef,
elementRef: ElementRef<HTMLElement>) {
Expand Down Expand Up @@ -73,7 +81,7 @@ export abstract class PblNgridBaseRowComponent<TRowType extends GridRowType, T =
}

ngDoCheck(): void {
if (this.grid) {
if (this._attached && this.grid) {
this.detectChanges();
}
}
Expand All @@ -83,6 +91,33 @@ export abstract class PblNgridBaseRowComponent<TRowType extends GridRowType, T =
this._extApi?.rowsApi.removeRow(this);
}

/**
* Marks the row as attached.
* Rows are attached by default.
* An attached row takes part in the change detection process
*/
attach(): boolean {
if (!this._attached) {
this._attached = true;
return true;
}
return false;
}

/**
* Marks the row as detached.
* A detached row DOWS NOT take part in the change detection process.
*
* Usually when the rendering engine cache row elements for performance, these should be detached when cached and re-attached when returned into view.
*/
detach(): boolean {
if (this._attached) {
this._attached = false;
return true;
}
return false;
}

_createCell(column: PblRowTypeToColumnTypeMap<TRowType>, atIndex?: number) {
if (!this.canCreateCell || this.canCreateCell(column, atIndex)) {
const cell = this.createComponent(column, atIndex);
Expand Down
11 changes: 8 additions & 3 deletions libs/ngrid/src/lib/grid/row/rows-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export interface RowsApi<T = any> {

findDataRowByIndex(index: number): PblNgridRowComponent<T> | undefined;
findDataRowByDsIndex(index: number): PblNgridRowComponent<T> | undefined;
findDataRowByIdentity(identity: string): PblNgridRowComponent<T> | undefined;
findDataRowByIdentity(identity: string | number): PblNgridRowComponent<T> | undefined;
findRowByElement(element: Element): PblNgridBaseRowComponent<GridRowType, T> | undefined;

}

function isPblNgridRowComponent<T = any>(row: PblNgridBaseRowComponent<GridRowType, T>): row is PblNgridRowComponent {
Expand Down Expand Up @@ -231,11 +233,14 @@ export class PblRowsApi<T = any> implements RowsApi<T> {
}
}


dataRows() {
return Array.from(this.rows.get('data')) as PblNgridRowComponent<T>[];
}

findRowByElement(element: Element): PblNgridBaseRowComponent<GridRowType, T> | undefined {
return this.allByElement.get(element);
}

findDataRowByDsIndex(index: number): PblNgridRowComponent<T> | undefined {
for (const r of this.dataRows()) {
if (r.context?.dsIndex === index) {
Expand All @@ -252,7 +257,7 @@ export class PblRowsApi<T = any> implements RowsApi<T> {
}
}

findDataRowByIdentity(identity: string): PblNgridRowComponent<T> | undefined {
findDataRowByIdentity(identity: string | number): PblNgridRowComponent<T> | undefined {
for (const r of this.dataRows()) {
if (r.context?.identity === identity) {
return r as PblNgridRowComponent<T>;
Expand Down

0 comments on commit 5a24eec

Please sign in to comment.