CxJS

Row Drag and Drop

Grid supports drag and drop for reordering rows and transferring rows between grids.

<div controller={PageController} class="flex gap-4">
  <div class="flex-1">
    <h3 class="text-sm font-semibold mb-2">Team A</h3>
    <Grid
      records={m.left}
      style="width: 100%"
      border
      columns={[
        {
          header: "",
          align: "center",
          pad: false,
          items: (
            <DragHandle class="cursor-move text-gray-400 hover:text-gray-600 px-1">
              ⋮⋮
            </DragHandle>
          ),
        },
        { header: "Name", field: "fullName" },
        { header: "City", field: "city" },
      ]}
      dragSource={{ data: { type: "person", source: "left" } }}
      onDropTest={(e) => e.source?.data?.type === "person"}
      onDrop={(e, instance) =>
        instance.getControllerByType(PageController).onDrop(e, "left")
      }
    />
  </div>

  <div class="flex-1">
    <h3 class="text-sm font-semibold mb-2">Team B</h3>
    <Grid
      records={m.right}
      style="width: 100%"
      border
      columns={[
        { header: "Name", field: "fullName" },
        { header: "City", field: "city" },
      ]}
      row={{ style: { cursor: "move" } }}
      dragSource={{ data: { type: "person", source: "right" } }}
      dropZone={{ mode: "insertion" }}
      onDropTest={(e) => e.source?.data?.type === "person"}
      onDrop={(e, instance) =>
        instance.getControllerByType(PageController).onDrop(e, "right")
      }
    />
  </div>
</div>

Team A

NameCity
⋮⋮
Alice JohnsonNew York
⋮⋮
Bob SmithLos Angeles
⋮⋮
Carol WhiteChicago

Team B

NameCity
David BrownHouston
Eva GreenPhoenix

Drag rows to reorder within a grid or transfer between grids. Use the drag handle to initiate dragging.

Drag Handle

Use DragHandle to create a specific grab area instead of making the entire row draggable:

import { DragHandle } from "cx/widgets";

columns={[
  {
    header: "",
    items: (
      <DragHandle class="cursor-move text-gray-400 hover:text-gray-600 px-1">
        ⋮⋮
      </DragHandle>
    ),
  },
  // ... other columns
]}

This provides better control and prevents accidental drags when clicking on other parts of the row.

Insertion Mode

Use dropZone={{ mode: "insertion" }} to show insertion indicators between rows:

<Grid
  dragSource={{ data: { type: "record" } }}
  dropZone={{ mode: "insertion" }}
  onDropTest={(e) => /* validate drop */}
  onDrop={(e) => /* handle drop */}
/>

Enabling Row Drag

Configure dragSource to make rows draggable:

<Grid
  records={m.records}
  dragSource={{ data: { type: "record" } }}
  row={{ style: { cursor: "move" } }}
/>

Handling Drops

Implement onDropTest to validate drops and onDrop to handle the reordering:

<Grid
  onDropTest={(e) => e.source?.data?.type === "record"}
  onDrop={(e, { controller }) => controller.onDrop(e)}
/>

The onDrop handler receives information about the source records and target position:

onDrop(e) {
  const draggedRecords = e.source.records.map((r) => r.data);
  const insertionIndex = e.target.insertionIndex;

  // Remove and reinsert records at new position
  this.store.update(m.records, (records) => {
    // ... reordering logic
  });
}

Drag Source Configuration

PropertyTypeDescription
dataobjectCustom data attached to the drag operation.
modestringDrag mode: "move" (default) or "copy".

Drop Event Properties

PropertyDescription
e.source.recordsArray of dragged record objects with data and index.
e.source.dataCustom data from dragSource.
e.target.insertionIndexIndex where items should be inserted.

Transferring Between Grids

Include a source identifier in the drag data to track which grid the row came from:

dragSource={{ data: { type: "person", source: "left" } }}

In the drop handler, use the source to remove from the correct grid:

onDrop(e, targetPath) {
  const sourcePath = e.source.data.source;

  // Remove from source grid
  this.store.update(sourcePath, (records) =>
    records.filter((r) => !draggedRecords.includes(r))
  );

  // Insert into target grid
  this.store.update(targetPath, (records) =>
    insertElement(records, insertionIndex, ...draggedRecords)
  );
}

Configuration

PropertyTypeDescription
dragSourceobjectConfiguration for row dragging.
dropZoneobjectDrop zone configuration. Use mode: "insertion" for visual indicators.
onDropTestfunctionTest if a drop is valid. Return true to allow.
onDropfunctionHandle the drop operation.

Drop Zone Modes

ModeDescription
previewDefault mode. Shows a preview of the drop position.
insertionShows insertion lines between rows for reordering.

See also: Grid, Tree Drag and Drop, DragHandle