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.
Rows can be selectable by making first column as a selectable column. You can use rowSelection.type to set selection type. Default is checkbox . selection happens when clicking checkbox by default.
You can make use of the responsive property on the column that you want to control for screen sizes. Just add a From To column with a custom render function, and set the responsive property on that column to only show on xs screens. The From and To columns will have the responsive property set to show on md and above.
Ant tables provide sorting, filtering, pagination, row selections, infinite scrolling, and many more features. Photo by the author. A table, also called a data grid, is an arrangement of data in rows and columns, or possibly in a more complex structure. It is an essential building block of a user interface.
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;
}
Before a paste my long blob of code, I want to mention a few things:
<table>
.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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With