React/AntDesign How to make rows draggable? (Table Drag Sorting)
Solution 1
Here is a simple example I came up with based off of a lot of different things I found online and eventually formulated something that worked for my code:
Basically this is the body of a react component table getting called and I am mapping a list of places via the indexes and each row is draggable to allow the user to modify the table. I have also included the function calls associated with the attributes
return (
<Tbody>
{items.map((item, index) => (
<Tr key={index} className="item.name"
draggable={true}
onDragStart={this.dragstart}
onDragEnd={this.dragend}
onDragLeave={this.dragend}
onDragEnter={this.dragend}
onDrop={this.drop}
onDragOver={this.dragend}
id={index} >
<Td><span>{item.name}</span></Td>
<Td><span>{item.latitude}</span></Td>
<Td><span>{item.longitude}</span></Td>
<Td type="data"><span>{this.checkForZero(this.props.propDistances[index])}</span></Td>
<Td type="data"><span>{this.checkForZero(this.props.propCumulativeDist[index])}</span></Td>
</Tr>
))}
{this.finishItineraryTable()}
</Tbody>
);
dragstart(event) {
let dataTransfer = event.dataTransfer;
let node = event.target;
dataTransfer.setData('text/plain',node.innerHTML);
dataTransfer.setData('id',node.id);
event.stopPropagation();
}
dragend(event) {
event.preventDefault();
}
drop(event) {
event.preventDefault();
let dragObjHtml = event.dataTransfer.getData("text/plain");
let dragObjId = document.getElementById(event.dataTransfer.getData("id"));
let dropTarget = event.target.closest("tr");
let temp = dropTarget.innerHTML;
dropTarget.innerHTML = dragObjHtml;
dragObjId.innerHTML = temp;
}
Solution 2
Before a paste my long blob of code, I want to mention a few things:
- It's heavily based on the original drag sorting example.
- Luckily React DnD has perfect TypeScript support as it's written in TypeScript.
- The code below is a drop in replacement for Ant designs
<table>
. - It's generic.
- It uses functional components and hooks.
- There is a
onDragSort
, which gets called back every time some drag sort happened. The parameter includes the sorted data.
First install the packages react-dnd
and react-dnd-html5-backend
. Add this to your DragSortingTable.tsx
:
import * as React from 'react';
import Table, { TableProps, TableComponents } from 'antd/lib/table';
import { DndProvider, DropTarget, DragSource, DragElementWrapper, DropTargetSpec } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
let dragingIndex = -1;
interface BodyRowProps {
isOver: boolean;
connectDragSource: DragElementWrapper<{}>;
connectDropTarget: DragElementWrapper<{}>;
style?: React.CSSProperties;
index: number;
className?: string;
}
const BodyRow: React.FC<BodyRowProps> = props => {
const style = { ...props.style, cursor: 'move' };
let { className } = props;
if (props.isOver) {
if (props.index > dragingIndex) {
className += ' drop-over-downward';
}
if (props.index < dragingIndex) {
className += ' drop-over-upward';
}
}
return props.connectDragSource(
props.connectDropTarget(
<tr className={className} style={style}>
{props.children}
</tr>
)
);
};
const rowSource = {
beginDrag(props) {
dragingIndex = props.index;
return {
index: props.index
};
}
};
interface DropProps {
index: number;
moveRow: (dragIndex: number, hoverIndex: number) => void;
}
const rowTarget: DropTargetSpec<DropProps> = {
drop(props, monitor) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return;
}
// Time to actually perform the action
props.moveRow(dragIndex, hoverIndex);
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
monitor.getItem().index = hoverIndex;
}
};
const DragableBodyRow = DropTarget<DropProps>('row', rowTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver()
}))(
DragSource('row', rowSource, connect => ({
connectDragSource: connect.dragSource()
}))(BodyRow)
);
interface DragSortingTableProps<T> extends TableProps<T> {
onDragSort?: (sortedData: T[]) => void;
}
type extractTableType<T> = T extends DragSortingTableProps<infer T> ? T : never;
export const DragSortingTable: <T>(
props: DragSortingTableProps<T>
) => React.ReactElement<DragSortingTableProps<T>> = props => {
const [dataSource, setDataSource] = React.useState(props.dataSource);
React.useEffect(() => {
setDataSource(props.dataSource);
}, [props.dataSource]);
const components: TableComponents = {
body: {
row: DragableBodyRow
}
};
const moveRow: DropProps['moveRow'] = (dragIndex, hoverIndex) => {
const dragRow = dataSource[dragIndex];
const remaining = dataSource.filter(i => i !== dragRow);
const sorted = [...remaining.slice(0, hoverIndex), dragRow, ...remaining.slice(hoverIndex)];
setDataSource(sorted);
if (props.onDragSort) {
props.onDragSort(sorted);
}
};
const tableProps: TableProps<extractTableType<typeof props>> = {
...props,
className: props.className ? props.className + ' drag-sorting-table' : 'drag-sorting-table',
components,
dataSource,
onRow: (_record, index) => ({ index, moveRow })
};
return (
<DndProvider backend={HTML5Backend}>
<Table {...tableProps}>{props.children}</Table>
</DndProvider>
);
};
Add the following styles to your antd.less
overwrites:
.drag-sorting-table tr.drop-over-downward td {
border-bottom: 2px dashed @primary-color;
}
.drag-sorting-table tr.drop-over-upward td {
border-top: 2px dashed @primary-color;
}
Use your new drag sorting table as follows:
<DragSortingTable<Users>
dataSource={props.users}
rowKey='id'
>
<Column
title='Title'
dataIndex='id'
key='id'
/>
</DragSortingTable>
Adrián Méndez
Updated on June 11, 2022Comments
-
Adrián Méndez almost 2 years
I have been coding this simple one-column drag and drop table, but the rows aren´t moving when I drag them. Where do I fix or check that?
I'm using React, AntDesign and JavaScript (with TypeScript)
import * as React from 'react'; import ReactDOM from "react-dom"; import { Table } from "antd"; import { DndProvider, DragSource, DropTarget } from "react-dnd"; import HTML5Backend from "react-dnd-html5-backend"; import update from "immutability-helper"; let dragingIndex = -1; interface propsDD { isOver: any, connectDragSource: any, connectDropTarget: any, moveRow: any, restProps: { readonly [x: string]: any; children?: React.ReactNode; } className: any, index: any, } class BodyRow extends React.Component<propsDD>{ render() { const { isOver, connectDragSource, connectDropTarget, moveRow, ...restProps } = this.props; const style = { ...restProps, cursor: 'move' }; let { className } = restProps; if (isOver) { if (restProps.index > dragingIndex) { className += " drop-over-downward"; } if (restProps.index < dragingIndex) { className += " drop-over-upward"; } } return connectDragSource( connectDropTarget( <tr {...restProps} className={className} style={style} /> ) ); } } const rowSource = { beginDrag(props: any) { dragingIndex = props.index; return { index: props.index, }; }, }; const rowTarget = { drop(props: any, monitor: any) { const dragIndex = monitor.getItem().index; const hoverIndex = props.index; if (dragIndex === hoverIndex) { return; } props.moveRow(dragIndex, hoverIndex); monitor.getItem().index = hoverIndex; }, }; const DragableBodyRow = DropTarget("row", rowTarget, (connect, monitor) => ({ connectDropTarget: connect.dropTarget(), isOver: monitor.isOver() }))( DragSource("row", rowSource, connect => ({ connectDragSource: connect.dragSource() }))(BodyRow) ); const columns = [ { title: 'Orden de Ejecución', dataIndex: 'attributes.name', key: 'name', }, ]; type propsFromList = { receivedTasks: Task[], onReceivedTasks: (tasks: Task[]) => void, } export default class DDTasks extends React.Component<propsFromList, State>{ public state: State = { data: [], }; components = { body: { row: DragableBodyRow, }, }; onReceivedTasks(tasks: Task[]): void { this.setState({ data: this.props.receivedTasks, } as State) } moveRow = (dragIndex: any, hoverIndex: any) => { const { data } = this.state; const dragRow = data[dragIndex]; this.setState( update(this.state, { data: { $splice: [[dragIndex, 1], [hoverIndex, 0, dragRow]] } }) ); }; render() { return ( < DndProvider backend={HTML5Backend} > <Table rowKey="id" bordered={true} pagination={false} columns={columns} dataSource={this.props.receivedTasks} components={this.components} onRow={(index) => ({ index, moveRow: this.moveRow, })} /> </DndProvider > ); } }
I expect to drag rows. The content of the rows are actually displayed.
-
Adrián Méndez over 4 yearsYes, I have already added the CSS style, as you said. In previous tests, the dragging effect showed, however, in this version doesn´t.
-
Vipin Yadav over 4 yearsWhich version of antd you are using can you update your antd version and then check
-
Adrián Méndez over 4 yearsI´m not really sure about which AntD version I´m using, however, here is the original source code I´m trying to replicate: ant.design/components/table/#components-table-demo-drag-sorting