Row Reordering Pro

The Pro version of react-bootstrap-data-grid allows a developer to allow the user to rearrange rows in a grid through a drag-and-drop interface.

End User Usage

When reordering is enabled for a grid, there will be an extra column on the left side of the grid that contains buttons that are drag handles. One can drag the handle to a different position on the grid to move the row to that position.

Drop Zones

The drop zones are around the borders between rows and extend half of the height of each adjacent row. See the image below for an illustration of the location of the drag handles and the drop zone that corresponds to a particular row index.

Row Reorder Handles and Drop Zone IllustrationIllustration showing the location of drag handles on a grid with reordering enabled. In addition, the illustration shows the area of the viewport that corresponds to a particular row index.Header RowBody Row 1 (index 0)Body Row 2 (index 1)Body Row 3 (index 2)Index 2dropzoneReorderhandle

Visual Indication

When the user drags a reorder handle, the styling of the top and bottom borders of the row being dragged changes to indicate that fact. By default, the color of the borders change to the Bootstrap success color.

In addition, when the user hovers over a drop zone while dragging, the border between cells indicating where the dropped row will be inserted will undergo a style change. By default, the Bootstrap primary color is used.

The below illustration shows the location of highlighted borders as described above.

Header RowBody Row 1 (index 0)Body Row 2 (index 1)Body Row 3 (index 2)Drop targethighlightDrageehighlights

Drag Marker

When the user is dragging a row, a translucent div appears under the cursor that contains the ID of the row. A common term for this div is drag ghost.

Sorting and Filtering

When a table is being sorted or filtered, the reordering feature is also disabled by disabling the buttons that are the drag handles.

Note that even if sorting or filtering is enabled for a grid, by passing in a sortModel or filterModel prop, respectively, those features may not be active. Sorting is not active if no column is being sorted upon. Likewise, if filtering id disabled for all columns, filtering is not active.

The reorder drag handle buttons are disabled if either sorting or filtering is active.

Keyboard Navigation

To reorder rows with the keyboard:

  1. Use the Tab key to cycle through focus on elements on the page until you get to the resize handle of the row that you want to reorder. Those buttons have a horizontal grip icon: .
  2. Click on the button using a keyboard key. For most browsers, either Space or Enter can be used to click on a button once it is focused. Note that the button behaves differently when clicking with a pointing device or touch screen (i.e. nothing happens).
  3. You should see the same visual indications as with a pointing device for the selected row (Bootstrap success-colored borders by default) and the destination index (Bootstrap primary-colored border by default).
  4. Used the Up Arrow and Down Arrow keys to select the index to which you want to move the selected row.
  5. Press Enter or Space key to perform the reorder to the selected destination index. To cancel the reordering process at any time without reordering anything, press the Escape key.

Enabling

To enable the feature for a grid, pass in a reorderModel prop. This prop is an object with a single member named callback, which is a callback function of the type (id: RowId, destIndex: number) => void;.

This callback function takes the row id and destIndex, which is the destination index from the drag and drop action. It is up to this function to act upon the action, often by setting a new rows prop with the rows in a different order.

Row Insert Utility Function

A function that takes an array of rows, a RowId, and a destination index and swaps the position of the element is often useful for this callback function. It is not trivial to write and thus has been exported as a utility function reorderRows.

To use the utility function, import it from the react-bootstrap-data-grid pro package as follows:

import { reorderRows } from "@absreim/react-bootstrap-data-grid-pro";

One imported, a typical way to use the function to implement a reorder callback is as follows

const initRows = [
    // initial rows...
]
 
const ReorderableGrid: FC = () => {
    const [rows, setRows] = useState(initRows);
    const reorderCallback: ReorderCallback = useCallback(
        (id, destIndex) => {
          const newRows = reorderRows(rows, id, destIndex);
          setRows(newRows);
        },
        [rows],
    );
 
    return (
        <GridPro
          rows={rows}
          cols={cols}
          reorder={{ callback: reorderCallback }}
        />
    );
}

Caveats

This feature currently does not support the situation where the contents of the rows prop changes while the user is dragging it. In such a situation, the behavior of this feature may be difficult to predict.

If you expect that your app would cause the rows prop to update via means other than user input, it is recommended that you not enable this feature.

Example

The following example demonstrates a grid with row reordering enabled.

Sorting and Filtering

In addition, sorting and filtering are enabled. One can sort on a column and/or filter on one or more columns to see how row reordering becomes disabled.

To enable sorting, click on the header cell for a sortable column to toggle through sorting modes. For this example, all columns except Description are sortable.

To enable filtering, click on the Filtering button in the toolbar (icon is ), enable filtering for at least one column, and click the Submit button.

Custom Styles

This example demonstrates custom styling specific to the row reordering feature:

  • The border color of dragged row is changed to the Bootstrap warning color.
  • The border color of drop zone is changed ot the Bootstrap info color.
  • The drag ghost has reduced opacity and a thicker border.

Live Demo

Name(not being sorted)Empty transparent square for styling purposes
HP(not being sorted)Empty transparent square for styling purposes
Speed (m/s)(not being sorted)Empty transparent square for styling purposes
Attack(not being sorted)Empty transparent square for styling purposes
Range (m)(not being sorted)Empty transparent square for styling purposes
Description
Fire Badger422292775Medium anti-swarm tank
Hacker32498585110Hacker bot
Mustang343163695High-speed all-purpose tank
Phantom Ray341216108765Medium-sized aircraft
Phoenix1491163219120Long-range strike aircraft
Rhino192971635606Heavy melee bot
Sabertooth155418785895Heavy tank
Sledgehammer3478760895Heavy tank
Steel Ball4571165545High-speed anti-unit bot
Stormcaller11496772180Heavy rocket launcher
Tarantula14773849680Heavy mech
Wasp3111620250Light aircraft

Code

index.tsx

"use client";
 
import GridPro, {
  ProColDef,
  reorderRows,
  RowDef,
  ReorderCallback,
  ReorderStyleModel,
} from "@absreim/react-bootstrap-data-grid-pro";
import { UnitStats } from "@/assets/shared/types";
import { FC, useCallback, useState } from "react";
import "./custom.scss";
 
const initRows: RowDef<UnitStats>[] = [
  {
    id: "Fire Badger",
    data: {
      name: "Fire Badger",
      hp: 4222,
      speed: 9,
      attack: 27,
      range: 75,
      desc: "Medium anti-swarm tank",
    },
  },
  {
    id: "Hacker",
    data: {
      name: "Hacker",
      hp: 3249,
      speed: 8,
      attack: 585,
      range: 110,
      desc: "Hacker bot",
    },
  },
  {
    id: "Mustang",
    data: {
      name: "Mustang",
      hp: 343,
      speed: 16,
      attack: 36,
      range: 95,
      desc: "High-speed all-purpose tank",
    },
  },
  {
    id: "Phantom Ray",
    data: {
      name: "Phantom Ray",
      hp: 3412,
      speed: 16,
      attack: 1087,
      range: 65,
      desc: "Medium-sized aircraft",
    },
  },
  {
    id: "Phoenix",
    data: {
      name: "Phoenix",
      hp: 1491,
      speed: 16,
      attack: 3219,
      range: 120,
      desc: "Long-range strike aircraft",
    },
  },
  {
    id: "Rhino",
    data: {
      name: "Rhino",
      hp: 19297,
      speed: 16,
      attack: 3560,
      range: 6,
      desc: "Heavy melee bot",
    },
  },
  {
    id: "Sabertooth",
    data: {
      name: "Sabertooth",
      hp: 15541,
      speed: 8,
      attack: 7858,
      range: 95,
      desc: "Heavy tank",
    },
  },
  {
    id: "Sledgehammer",
    data: {
      name: "Sledgehammer",
      hp: 3478,
      speed: 7,
      attack: 608,
      range: 95,
      desc: "Heavy tank",
    },
  },
  {
    id: "Steel Ball",
    data: {
      name: "Steel Ball",
      hp: 4571,
      speed: 16,
      attack: 55,
      range: 45,
      desc: "High-speed anti-unit bot",
    },
  },
  {
    id: "Stormcaller",
    data: {
      name: "Stormcaller",
      hp: 1149,
      speed: 6,
      attack: 772,
      range: 180,
      desc: "Heavy rocket launcher",
    },
  },
  {
    id: "Tarantula",
    data: {
      name: "Tarantula",
      hp: 14773,
      speed: 8,
      attack: 496,
      range: 80,
      desc: "Heavy mech",
    },
  },
  {
    id: "Wasp",
    data: {
      name: "Wasp",
      hp: 311,
      speed: 16,
      attack: 202,
      range: 50,
      desc: "Light aircraft",
    },
  },
];
 
const cols: ProColDef[] = [
  {
    name: "name",
    type: "string",
    label: "Name",
    sortable: true,
  },
  {
    name: "hp",
    type: "number",
    label: "HP",
    sortable: true,
  },
  {
    name: "speed",
    type: "number",
    label: "Speed (m/s)",
    sortable: true,
  },
  {
    name: "attack",
    type: "number",
    label: "Attack",
    sortable: true,
  },
  {
    name: "range",
    type: "number",
    label: "Range (m)",
    sortable: true,
  },
  {
    name: "desc",
    type: "string",
    label: "Description",
  },
];
 
const reorderStyleModel: ReorderStyleModel = {
  draggedRowClasses: ["custom-reorder-dragged-row"],
  draggedRowPredecessorClasses: ["custom-reorder-dragged-row-pred"],
  topBorderRowClasses: ["custom-reorder-above-drag-target-row"],
  bottomBorderRowClasses: ["custom-reorder-below-drag-target-row"],
  ghostDivClasses: ["custom-reorder-ghost", "border", "border-3"],
};
 
const ReorderableGrid: FC = () => {
  const [rows, setRows] = useState(initRows);
  const reorderCallback: ReorderCallback = useCallback(
    (id, destIndex) => {
      const newRows = reorderRows(rows, id, destIndex);
      setRows(newRows);
    },
    [rows],
  );
 
  return (
    <GridPro
      useToolbar
      rows={rows}
      cols={cols}
      sortModel={{
        type: "uncontrolled",
        initialSortColDef: null,
      }}
      filterModel={{ type: "uncontrolled" }}
      reorder={{ callback: reorderCallback }}
      styleModel={{
        reorderModel: reorderStyleModel,
      }}
    />
  );
};
 
export default ReorderableGrid;

custom.scss

.custom-reorder-ghost {
  opacity: 50%;
  font-weight: bold;
  position: fixed;
  padding: 0.5rem;
  z-index: 2;
}
 
.custom-reorder-dragged-row-pred {
  border-bottom-color: var(--bs-warning);
}
 
.custom-reorder-dragged-row {
  border-top-width: 1px;
  opacity: 50%;
  border-color: var(--bs-warning);
}
 
.custom-reorder-above-drag-target-row {
  border-bottom-color: var(--bs-info);
}
 
.custom-reorder-below-drag-target-row {
  border-top-width: 1px;
  border-top-color: var(--bs-info);
}

types.ts

export interface UnitStats {
  name: string;
  hp: number;
  speed: number;
  attack: number;
  range: number;
  desc: string;
}

Style Customization

Styles related to the row reordering feature can customized by supplying a property of the styleModel prop of the GridPro component. This property is named reorderModel and of the type named ReorderStyleModel. Properties of ReorderStyleModel are listed below.

Property nameType definitionRequired/OptionalDescription
draggedRowClassesstring[]Optional

Classes to apply to the tr element being dragged.

Defaults to ["rbdg-reorder-dragged-row"]. This class causes the top and bottom borders of the row be highlighted with the Boostrap success color.

draggedRowPredecessorClassesstring[]Optional

Classes to apply to the tr element above the one being dragged.

Defaults to ["rbdg-reorder-dragged-row-pred"]. This class causes the bottom border of the row be highlighted with the Boostrap success color.

topBorderRowClassesstring[]Optional

Classes to apply to the tr element above the drop zone.

Defaults to ["rbdg-reorder-above-drag-target-row"]. This class causes the bottom border of the row be highlighted with the Boostrap primary color.

bottomBorderRowClassesstring[]Optional

Classes to apply to the tr element below the drop zone.

Defaults to ["rbdg-reorder-below-drag-target-row"]. This class causes the top border of the row be highlighted with the Boostrap primary color.

ghostDivClassesstring[]Optional

Classes to apply to the drag ghost div element.

Defaults to ["rbdg-drag-ghost", "border"]. These classes together:

  • make the div translucent
  • cause it to float above the grid and toolbar UIs with a z-index of 2
  • add a border around the div