---
title: "Row Dragging Between Grids"
framework: javascript
version: "36.1.0"
---

# Row Dragging Between Grids

Row Drag Between Grids is concerned with seamless integration among different grids, allowing records to be dragged from one grid and dropped at a specific index on another grid.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `addRowDropZone` | `Function` |  |  | Adds a drop zone outside of the grid where rows can be dropped. Module: [`RowDragModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |
| `removeRowDropZone` | `Function` |  |  | Removes an external drop zone added by `addRowDropZone`. Module: [`RowDragModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |
| `getRowDropZoneParams` | `Function` |  |  | Returns the `RowDropZoneParams` to be used by another grid's `addRowDropZone` method. Module: [`RowDragModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |

## Adding a Grid as Target

To allow adding a grid as DropZone, the `getRowDropZoneParams` API method should be used in the target grid and the `addRowDropZone` in the source grid.

```js
const dropZoneParams = targetGridApi.getRowDropZoneParams({
    onDragStop: function() {
        alert('Record Dropped!');
    }
});

if (dropZoneParams) {
    sourceGridApi.addRowDropZone(dropZoneParams);

    // when the DropZone above is no longer needed
    sourceGridApi.removeRowDropZone(dropZoneParams);
}
```

In the example below, note the following:

- When you drag from one grid to another, a line will appear indicating where the row will be placed.
- Rows can be dragged from one grid to the other grid. When the row is received, it is **not** removed from the first grid. This is the choice of the example. The example could equally have removed from the other grid.
- Rows can be removed from both grids by dragging the row to the 'Trash' drop zone.
- New rows can be created by clicking on the red, green and blue buttons.

#### Two Grids with Drop Position

```ts
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowApiModule,
  RowDragModule,
  RowDropZoneParams,
  RowStyleModule,
  TextFilterModule,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";

// Register the required feature modules with the Grid
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  RowApiModule,
  TextFilterModule,
  RowDragModule,
  RowStyleModule,
  ClientSideRowModelModule,
]);

let rowIdSequence = 100;

const leftColumnDefs: ColDef[] = [
  { field: "id", rowDrag: true },
  { field: "color" },
  { field: "value1" },
  { field: "value2" },
];

const rightColumnDefs: ColDef[] = [
  { field: "id", rowDrag: true },
  { field: "color" },
  { field: "value1" },
  { field: "value2" },
];
let leftApi: GridApi;
const leftGridOptions: GridOptions = {
  defaultColDef: {
    flex: 1,
    minWidth: 100,
    filter: true,
  },
  rowClassRules: {
    "red-row": 'data.color == "Red"',
    "green-row": 'data.color == "Green"',
    "blue-row": 'data.color == "Blue"',
  },
  getRowId: (params: GetRowIdParams) => {
    return String(params.data.id);
  },
  rowData: createRowBlock(2),
  rowDragManaged: true,
  suppressMoveWhenRowDragging: true,
  columnDefs: leftColumnDefs,
  onGridReady: (params) => {
    addBinZone(params);
    addGridDropZone(params, "Right");
  },
};
let rightApi: GridApi;
const rightGridOptions: GridOptions = {
  defaultColDef: {
    flex: 1,
    minWidth: 100,
    filter: true,
  },
  rowClassRules: {
    "red-row": 'data.color == "Red"',
    "green-row": 'data.color == "Green"',
    "blue-row": 'data.color == "Blue"',
  },
  getRowId: (params: GetRowIdParams) => {
    return String(params.data.id);
  },
  rowData: createRowBlock(2),
  rowDragManaged: true,
  suppressMoveWhenRowDragging: true,
  columnDefs: rightColumnDefs,
  onGridReady: (params) => {
    addBinZone(params);
    addGridDropZone(params, "Left");
  },
};

function createRowBlock(blocks: number) {
  blocks = blocks || 1;

  let output: any[] = [];

  for (let i = 0; i < blocks; i++) {
    output = output.concat(
      ["Red", "Green", "Blue"].map(function (color) {
        return createDataItem(color);
      }),
    );
  }

  return output;
}

function createDataItem(color: string) {
  return {
    id: rowIdSequence++,
    color: color,
    value1: Math.floor(window.agRandom() * 100),
    value2: Math.floor(window.agRandom() * 100),
  };
}

function addRecordToGrid(side: string, data: any) {
  // if data missing or data has no it, do nothing
  if (!data || data.id == null) {
    return;
  }

  let api = side === "left" ? leftApi : rightApi,
    // do nothing if row is already in the grid, otherwise we would have duplicates
    rowAlreadyInGrid = !!api!.getRowNode(data.id),
    transaction;

  if (rowAlreadyInGrid) {
    console.log("not adding row to avoid duplicates in the grid");
    return;
  }

  transaction = {
    add: [data],
  };

  api!.applyTransaction(transaction);
}

function onFactoryButtonClick(e: any) {
  const button = e.currentTarget,
    buttonColor = button.getAttribute("data-color"),
    side = button.getAttribute("data-side"),
    data = createDataItem(buttonColor);

  addRecordToGrid(side, data);
}

function binDrop(data: any) {
  // if data missing or data has no id, do nothing
  if (!data || data.id == null) {
    return;
  }

  const transaction = {
    remove: [data],
  };

  [leftApi, rightApi].forEach((api) => {
    const rowsInGrid = !!api!.getRowNode(data.id);

    if (rowsInGrid) {
      api!.applyTransaction(transaction);
    }
  });
}

function addBinZone(params: GridReadyEvent) {
  const eBin = document.querySelector(".bin") as any,
    icon = eBin.querySelector("i"),
    dropZone: RowDropZoneParams = {
      getContainer: () => {
        return eBin;
      },
      onDragEnter: () => {
        eBin.style.color = "blue";
        icon.style.transform = "scale(1.5)";
      },
      onDragLeave: () => {
        eBin.style = "";
        icon.style.transform = "scale(1)";
      },
      onDragStop: (params) => {
        binDrop(params.node.data);
        eBin.style = "";
        icon.style.transform = "scale(1)";
      },
    };

  params.api.addRowDropZone(dropZone);
}

function addGridDropZone(params: GridReadyEvent, side: string) {
  const api = (side === "Left" ? leftApi : rightApi)!;
  const dropZone = api.getRowDropZoneParams();

  params.api.addRowDropZone(dropZone);
}

function loadGrid(side: string) {
  const grid = document.querySelector<HTMLElement>("#e" + side + "Grid")!;
  if (side === "Left") {
    leftApi = createGrid(grid, leftGridOptions);
  } else {
    rightApi = createGrid(grid, rightGridOptions);
  }
}

const buttons = document.querySelectorAll("button.factory");

for (let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener("click", onFactoryButtonClick);
}

loadGrid("Left");
loadGrid("Right");
```

[Live example: Two Grids with Drop Position](https://www.ag-grid.com/examples/row-dragging-to-grid/two-grids-with-drop-position/typescript)

## Dragging Multiple Records Between Grids

It is possible to drag multiple records at once from one grid to another.

In the example below, note the following:

- This example enables [Multi-Row Dragging](https://www.ag-grid.com/javascript-data-grid/row-dragging-managed/#multi-row-dragging) between grids using `rowDragMultiRow`.
- When `Remove Source Rows` is selected, the rows will be removed from the **Athletes** grid once they are dropped onto the **Selected Athletes** grid.
- If `Only Deselect Source Rows` is selected, all selected rows that were copied will be deselected but will not be removed.
   Note: If some rows are selected and a row that isn't selected is copied, the selected rows will remain selected.
- If `None` is selected, the rows will be copied from one grid to another and the source grid will stay as is.

#### Multiple Records with Drop Position

```ts
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ICellRendererComp,
  ICellRendererParams,
  ModuleRegistry,
  RowDragModule,
  RowSelectionModule,
  TextFilterModule,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  TextFilterModule,
  RowDragModule,
  RowSelectionModule,
  ClientSideRowModelModule,
]);

class SportRenderer implements ICellRendererComp {
  eGui!: HTMLElement;

  init(params: ICellRendererParams) {
    this.eGui = document.createElement("i");

    this.eGui.addEventListener("click", () => {
      params.api.applyTransaction({ remove: [params.node.data] });
    });

    this.eGui.classList.add("far", "fa-trash-alt");
    this.eGui.style.cursor = "pointer";
  }

  getGui() {
    return this.eGui;
  }

  refresh(params: ICellRendererParams): boolean {
    return false;
  }
}

const leftColumnDefs: ColDef[] = [
  {
    rowDrag: true,
    maxWidth: 50,
    suppressHeaderMenuButton: true,
    suppressHeaderFilterButton: true,
    rowDragText: (params, dragItemCount) => {
      if (dragItemCount > 1) {
        return dragItemCount + " athletes";
      }
      return params.rowNode!.data.athlete;
    },
  },
  { field: "athlete" },
  { field: "sport" },
];

const rightColumnDefs: ColDef[] = [
  {
    rowDrag: true,
    maxWidth: 50,
    suppressHeaderMenuButton: true,
    suppressHeaderFilterButton: true,
    rowDragText: (params, dragItemCount) => {
      if (dragItemCount > 1) {
        return dragItemCount + " athletes";
      }
      return params.rowNode!.data.athlete;
    },
  },
  { field: "athlete" },
  { field: "sport" },
  {
    suppressHeaderMenuButton: true,
    suppressHeaderFilterButton: true,
    maxWidth: 50,
    cellRenderer: SportRenderer,
  },
];
let leftApi: GridApi;
const leftGridOptions: GridOptions = {
  defaultColDef: {
    flex: 1,
    minWidth: 100,
    filter: true,
  },
  rowSelection: {
    mode: "multiRow",
  },
  rowDragMultiRow: true,
  getRowId: (params: GetRowIdParams) => {
    return params.data.athlete;
  },
  rowDragManaged: true,
  suppressMoveWhenRowDragging: true,
  columnDefs: leftColumnDefs,
  onGridReady: (params) => {
    addGridDropZone(params);
  },
};
let rightApi: GridApi;
const rightGridOptions: GridOptions = {
  defaultColDef: {
    flex: 1,
    minWidth: 100,
    filter: true,
  },
  getRowId: (params: GetRowIdParams) => {
    return params.data.athlete;
  },
  rowDragManaged: true,
  columnDefs: rightColumnDefs,
};

function addGridDropZone(params: GridReadyEvent) {
  const dropZoneParams = rightApi!.getRowDropZoneParams({
    onDragStop: (params) => {
      const deselectCheck = (
        document.querySelector("input#deselect") as HTMLInputElement
      ).checked;
      const moveCheck = (
        document.querySelector("input#move") as HTMLInputElement
      ).checked;
      const nodes = params.nodes;

      if (moveCheck) {
        leftApi!.applyTransaction({
          remove: nodes.map(function (node) {
            return node.data;
          }),
        });
      } else if (deselectCheck) {
        leftApi!.setNodesSelected({ nodes, newValue: false });
      }
    },
  });

  params.api.addRowDropZone(dropZoneParams);
}

function loadGrid(
  options: GridOptions,
  oldApi: GridApi,
  side: string,
  data: any[],
) {
  const grid = document.querySelector<HTMLElement>("#e" + side + "Grid")!;

  oldApi?.destroy();

  options.rowData = data;
  return createGrid(grid, options);
}

function resetInputs() {
  const inputs = document.querySelectorAll(
    ".example-toolbar input",
  ) as NodeListOf<HTMLInputElement>;
  const checkbox = inputs[inputs.length - 1];

  if (!checkbox.checked) {
    checkbox.click();
  }

  inputs[0].checked = true;
}

function loadGrids() {
  fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
    .then((response) => response.json())
    .then(function (data) {
      const athletes: any[] = [];
      let i = 0;

      while (athletes.length < 20 && i < data.length) {
        const pos = i++;
        if (
          athletes.some(function (rec) {
            return rec.athlete === data[pos].athlete;
          })
        ) {
          continue;
        }
        athletes.push(data[pos]);
      }

      leftApi = loadGrid(leftGridOptions, leftApi, "Left", athletes);
      rightApi = loadGrid(rightGridOptions, rightApi, "Right", []);
    });
}

const resetBtn = document.querySelector("button.reset")!;

resetBtn.addEventListener("click", () => {
  resetInputs();
  loadGrids();
});

loadGrids();
```

[Live example: Multiple Records with Drop Position](https://www.ag-grid.com/examples/row-dragging-to-grid/two-grids-with-multiple-records/typescript)
