JavaScript Data GridRow parent-child

Reflect the parent-child relationship of your data, using the NestedRows plugin's interactive UI elements such as expand and collapse buttons or an extended context menu.

Quick setup

To enable the NestedRows plugin, set the nestedRows option to true.

const hot = new Handsontable(container, {
  nestedRows: true,
});

Note that using all the functionalities provided by the plugin requires enabling the row headers and the Handsontable context menu. To do this set rowHeaders and contextMenu to true. The collapse / expand buttons are located in the row headers, and the row modification options add row, insert child, etc., are in the Context Menu.

Prepare the data source

The data source must have a specific structure to be used with the Nested Rows plugin.

The plugin requires the data source to be an array of objects. Each object in the array represents a single 0-level entry. 0-level refers to an entry, which is not a child of any other entry. If an entry has any child entries, they need to be declared again as an array of objects. To assign them to a row, create a __children property in the parent element.

Here's an example:

import Handsontable from 'handsontable';
import 'handsontable/dist/handsontable.full.min.css';

const sourceDataObject = [
  {
    category: 'Best Rock Performance',
    artist: null,
    title: null,
    label: null,
    __children: [
      {
        title: "Don't Wanna Fight",
        artist: 'Alabama Shakes',
        label: 'ATO Records',
      },
      {
        title: 'What Kind Of Man',
        artist: 'Florence & The Machine',
        label: 'Republic',
      },
      {
        title: 'Something From Nothing',
        artist: 'Foo Fighters',
        label: 'RCA Records',
      },
      {
        title: "Ex's & Oh's",
        artist: 'Elle King',
        label: 'RCA Records',
      },
      {
        title: 'Moaning Lisa Smile',
        artist: 'Wolf Alice',
        label: 'RCA Records/Dirty Hit',
      },
    ],
  },
  {
    category: 'Best Metal Performance',
    __children: [
      {
        title: 'Cirice',
        artist: 'Ghost',
        label: 'Loma Vista Recordings',
      },
      {
        title: 'Identity',
        artist: 'August Burns Red',
        label: 'Fearless Records',
      },
      {
        title: '512',
        artist: 'Lamb Of God',
        label: 'Epic Records',
      },
      {
        title: 'Thank You',
        artist: 'Sevendust',
        label: '7Bros Records',
      },
      {
        title: 'Custer',
        artist: 'Slipknot',
        label: 'Roadrunner Records',
      },
    ],
  },
  {
    category: 'Best Rock Song',
    __children: [
      {
        title: "Don't Wanna Fight",
        artist: 'Alabama Shakes',
        label: 'ATO Records',
      },
      {
        title: "Ex's & Oh's",
        artist: 'Elle King',
        label: 'RCA Records',
      },
      {
        title: 'Hold Back The River',
        artist: 'James Bay',
        label: 'Republic',
      },
      {
        title: 'Lydia',
        artist: 'Highly Suspect',
        label: '300 Entertainment',
      },
      {
        title: 'What Kind Of Man',
        artist: 'Florence & The Machine',
        label: 'Republic',
      },
    ],
  },
  {
    category: 'Best Rock Album',
    __children: [
      {
        title: 'Drones',
        artist: 'Muse',
        label: 'Warner Bros. Records',
      },
      {
        title: 'Chaos And The Calm',
        artist: 'James Bay',
        label: 'Republic',
      },
      {
        title: 'Kintsugi',
        artist: 'Death Cab For Cutie',
        label: 'Atlantic',
      },
      {
        title: 'Mister Asylum',
        artist: 'Highly Suspect',
        label: '300 Entertainment',
      },
      {
        title: '.5: The Gray Chapter',
        artist: 'Slipknot',
        label: 'Roadrunner Records',
      },
    ],
  },
];

const container = document.querySelector('#example1');

new Handsontable(container, {
  data: sourceDataObject,
  preventOverflow: 'horizontal',
  rowHeaders: true,
  colHeaders: ['Category', 'Artist', 'Title', 'Album', 'Label'],
  nestedRows: true,
  contextMenu: true,
  bindRowsWithHeaders: true,
  autoWrapRow: true,
  autoWrapCol: true,
  height: 'auto',
  licenseKey: 'non-commercial-and-evaluation',
});

In the example above, we’ve created a data object consisting of 2016’s Grammy nominees of the “Rock” genre. Each 0-level entry declares a category, while their children declare nominees - assigned under the __children properties.

Note that the first 0-level object in the array needs to have all columns defined to display the table properly. They can be declared as null or an empty string '', but they need to be defined. This is optional for the other objects.

User interface

The Nested Rows plugin's user interface is placed in the row headers and the Handsontable’s context menu.

Row headers

Each parent row header contains a +/- button. It is used to collapse or expand its child rows.

The child row headers have a bigger indentation, to enable the user to clearly recognize the child and parent elements.

Context Menu

The context menu has been extended with a few Nested Rows related options, such as:

  • Insert child row
  • Detach from parent

The “Insert row above” and “Insert row below” options were modified to work properly with the nested data structure.

Known limitations

When you use the parent-child row structure, the following Handsontable features are not supported:

Windows macOS Action Excel Sheets
Enter Enter Collapse or expand the row group

There is a newer version of Handsontable available. Switch to the latest version ⟶