Event OnDrop for PrimeNG Table

ffex - Feb 6 '23 - - Dev Community

Introduction

Hei there!
I was working in a transport and logistic management system, in particular on a form to manage the delivery: which truck has to do delivery, which driver, when, which deliveries, etc.
I used for it a PrimeNG table with the reorder property to assign deliveries to the trip but there are a few conditions that the PrimeNg table does not handle: some deliveries cannot be reordered.
The order is important in a delivery trip and I cannot put a delivery not yet delivered before a delivered one.
So I had to control the onDrop event of the table and can set up conditions, reject the reorder or accept it. The default PrimeNG table does not have this possibility and after a little research on the internet the only thing useful that I found was a comment in a PrimeNG forum: "play with the source code". So I play with that and I found a way!

PrimeNG Table

<p-table [value]="products" [columns]="cols" [reorderableColumns]="true" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th style="width:3rem"></th>
            <th *ngFor="let col of columns" pReorderableColumn>
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns" let-index="rowIndex">
        <tr [pReorderableRow]="index">
            <td>
                <span class="pi pi-bars" pReorderableRowHandle></span>
            </td>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>
Enter fullscreen mode Exit fullscreen mode

This is a p-table with the reorder row drag&drop property active.
The [pReorderableRow]="index" turn on the order ability of the table.
The<span class="pi pi-bars" pReorderableRowHandle></span> put a handle icon to perform the drag and drop.

drag and drop
So I started to search in PrimeNG code and I found a directive that implements this function on the table: ReorderableRow.

How to use it

Ok, ok too long story, but how to use it?
It's here! It's enough to copy this directive that extends the ReorderableRow.

import { ElementRef, EventEmitter, Input, NgZone, Output } from '@angular/core';
import { Directive, HostListener } from '@angular/core';
import { ReorderableRow, Table } from 'primeng/table';


@Directive({
  selector: '[pReordableRowDrop]'
})
export class ReordableRowDrop extends ReorderableRow {
  @Input() set pReordableRowDrop(value: number) {
    this.index = value;
  }

  @Output() dropRow = new EventEmitter<any>();
  override index = 0;
  tmsEventDrop: any;
  dtDraggedRowIndex?: number;
  dtDroppedRowIndex?: number;

  constructor(public override dt: Table, public override el: ElementRef, public override zone: NgZone) {
    super(dt, el, zone);
  }

  override onDrop(event: any): void {

    if (this.isEnabled() && this.dt.rowDragging) {
      this.dtDraggedRowIndex = this.dt.draggedRowIndex;
      this.dtDroppedRowIndex = this.dt.droppedRowIndex;
      const dropIndex = this.dt.draggedRowIndex > this.dt.droppedRowIndex ? this.dt.droppedRowIndex : this.dt.droppedRowIndex === 0 ? 0 : this.dt.droppedRowIndex - 1;

      this.tmsEventDrop = event;

      const eventArgs = {
        sender: this,
        draggedRowIndex: this.dt.draggedRowIndex,
        droppedRowIndex: dropIndex
      }

      this.dropRow.emit(eventArgs);


    }

    event.preventDefault();

  }

  public acceptDrop(): void {
    this.dt.draggedRowIndex = this.dtDraggedRowIndex!;
    this.dt.droppedRowIndex = this.dtDroppedRowIndex!;
    this.dt.onRowDrop(this.tmsEventDrop, this.el.nativeElement);
  }

  public rejectDrop(): void {
    //cleanup
    this.tmsEventDrop = null;
    this.dtDraggedRowIndex = undefined;
    this.dtDroppedRowIndex = undefined;
    this.dt.onRowDragLeave(this.tmsEventDrop, this.el.nativeElement);
    this.dt.onRowDragEnd(this.tmsEventDrop);
  }

}
Enter fullscreen mode Exit fullscreen mode

This directive intercepts the OnDrop event of the table and implements an EventEmitter that raises an event at the exterior.
acceptDrop() and rejectDrop() are methods that continue the normal flow of a reorder, the first one is to accept the reorder, and the second one is to reject because there are some mandatory operations to do.

Copy this directive into your projects and add it to the declarations in the module.

Now overwrite the p-table, in particulary replace [pReorderableRow]="index" with [pReordableRowDrop]="index" (dropRow)="onDropRow($event)".

 <p-table [value]="products" [columns]="cols" [reorderableColumns]="true" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header" let-columns>
      <tr>
        <th style="width:3rem"></th>
        <th style="width:3rem">#</th>
        <th *ngFor="let col of columns" pReorderableColumn>
          {{col.header}}
        </th>
      </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns" let-index="rowIndex">
      <tr [pReordableRowDrop]="index" (dropRow)="onDropRow($event)">
        <td>
          <span class="pi pi-bars" pReorderableRowHandle></span>
        </td>
        <td>
          {{index}}
        </td>
        <td *ngFor="let col of columns">
          {{rowData[col.field]}}
        </td>
      </tr>
    </ng-template>
  </p-table>
Enter fullscreen mode Exit fullscreen mode

and finally manage the onDropRow($event):

  onDropRow(e: any): void {
    if (this.products[e.draggedRowIndex].deliveryStatusCode! < 50 && this.products[e.droppedRowIndex].deliveryStatusCode! < 50) {
      e.sender.acceptDrop();
      this.logs.push(`SUCCESS: Reordered ${this.products[e.draggedRowIndex].name!} in position ${e.droppedRowIndex}.`);
    } else {
      e.sender.rejectDrop();
      this.logs.push(`FAILED: Impossibile move ${this.products[e.draggedRowIndex].name!} in position ${e.droppedRowIndex}. One of those items was delivered yet. :(`);
    }
  }
Enter fullscreen mode Exit fullscreen mode

IMPORTANT: it is very mandatory to add an e.sender.acceptDrop(); or e.sender.rejectDrop(); otherwise the table have some bugs.

Conclusion

This is a reference of a complete example project to use this directive:

PrimeNg Table Drop Event Example

This project is an example of how to override the OnDrop function in a PrimeNg table; the new onDrop raises an event so you can, in every component you want, put conditions to accept or reject the reorder.

This project was generated with Angular CLI version 15.1.3 and PrimeNG version 15.1.1.

How to use in your Project

  1. You can use this in a Angular with PrimeNg project.
  2. The only file you need is reordable-row-drop.directive.ts, copy that in your angular project
    • NOTE: Add it in the module declarations
  3. replace the PrimeNg default [pReorderableRow]="index" with the custom one [pReordableRowDrop]="index" (dropRow)="onDropRow($event)". Example:
  <p-table [value]="products" [columns]="cols" [reorderableColumns]="true" [tableStyle]="{'min-width': '50rem'}"&gt
    <ng-template pTemplate="header" let-columns>
      <tr>
        <th style="width:3rem"></th>
        <th style="width:3rem">#</th>
        <th *ngFor="let col of columns" pReorderableColumn>
          {{col.header}}
        </th>
      </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns" let-index="rowIndex">
      <tr [pReordableRowDrop]="index" (dropRow)="onDropRow($event)">
        <td>
          <span class="pi pi-bars" pReorderableRowHandle></span>
        </td>
        <td>
          {{index}}
        </td>
        <td *ngFor="let col

It's all folks! Bye!

. . . . . . . .