Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React/AntDesign How to make rows draggable? (Table Drag Sorting)

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.

like image 766
Adrián Méndez Avatar asked Aug 13 '19 16:08

Adrián Méndez


People also ask

How do I select rows in ANTD table?

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.

How do I make an ANTD table responsive?

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.

What is Ant table?

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.


2 Answers

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;
}
like image 73
Matt Aertker Avatar answered Oct 13 '22 08:10

Matt Aertker


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>
like image 36
Robin Avatar answered Oct 13 '22 08:10

Robin