---
title: "SSRM Tree Data"
enterprise: true
framework: vue
version: "36.1.0"
---

# SSRM Tree Data

This section shows how Tree Data can be used with the Server-Side Row Model.

## Tree Data

Tree Data is defined as data that has parent / child relationships where the parent / child relationships are provided as part of the data. This is in contrast to Row Grouping where the parent / child relationships are the result of grouping. When working with Tree Data, there are no columns getting grouped.

An example of a Tree Data JSON structure is shown below:

```json
[{
    "employeeId": 101,
    "employeeName": "Erica Rogers",
    "jobTitle": "CEO",
    "employmentType": "Permanent",
    "children": [{
        "employeeId": 102,
        "employeeName": "Malcolm Barrett",
        "jobTitle": "Exec. Vice President",
        "employmentType": "Permanent",
        "children": [
            {
                "employeeId": 103,
                "employeeName": "Leah Flowers",
                "jobTitle": "Parts Technician",
                "employmentType": "Contract"
            },
            {
                "employeeId": 104,
                "employeeName": "Tammy Sutton",
                "jobTitle": "Service Technician",
                "employmentType": "Contract"
            }
        ]
    }]
}]
```

It is expected that the data set will be too large to send over the network, hence the SSRM is used to lazy-load child row as the parent rows are expanded.

## Tree Data Mode

In order to set the grid to work with Tree Data, simply enable Tree Data mode via the Grid Options using: `gridOptions.treeData = true`.

## Supplying Tree Data

Tree Data is supplied via the [Server-Side Datasource](https://www.ag-grid.com/vue-data-grid/server-side-model-datasource/) just like flat data, however there are two additional gridOptions callbacks: `isServerSideGroup(dataItem)` and `getServerSideGroupKey(dataItem)`.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `isServerSideGroup` | `IsServerSideGroup` |  |  | SSRM Tree Data: Allows specifying which rows are expandable. Module: [`ServerSideRowModelModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `getServerSideGroupKey` | `GetServerSideGroupKey` |  |  | SSRM Tree Data: Allows specifying group keys. Module: [`ServerSideRowModelModule`](https://www.ag-grid.com/vue-data-grid/modules/). |

The following code snippet shows the relevant `gridOptions` entries for configuring tree data with the server-side row model:

```ts
<ag-grid-vue
    :rowModelType="rowModelType"
    :treeData="treeData"
    :isServerSideGroup="isServerSideGroup"
    :getServerSideGroupKey="getServerSideGroupKey"
    /* other grid options ... */>
</ag-grid-vue>

// choose Server-Side Row Model
this.rowModelType = 'serverSide';
// enable Tree Data
this.treeData = true;
// indicate if row is a group node
this.isServerSideGroup = dataItem => {
    return dataItem.group;
};
// specify which group key to use
this.getServerSideGroupKey = dataItem => {
    return dataItem.employeeId;
};
```

> **Note**
>
> Be careful not to get mixed up with the [Client-Side Tree Data](https://www.ag-grid.com/vue-data-grid/tree-data/) configurations by mistake.

The example below shows this in action where the following can be noted:

- Tree Data is enabled with the Server-Side Row Model using `gridOptions.treeData = true`.
- Group nodes are determined using the callback `gridOptions.isServerSideGroup(dataItem)`.
- Group keys are returned from the callback `gridOptions.getServerSideGroupKey(dataItem)`.

#### Tree Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetServerSideGroupKey,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ICellRendererParams,
  IServerSideDatasource,
  IServerSideGetRowsParams,
  IServerSideGetRowsRequest,
  IsServerSideGroup,
  IsServerSideGroupOpenByDefault,
  IsServerSideGroupOpenByDefaultParams,
  ModuleRegistry,
  RowModelType,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  ServerSideRowModelModule,
  TreeDataModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  TreeDataModule,
  ServerSideRowModelModule,
]);

function createFakeServer(fakeServerData: any[]) {
  const fakeServer = {
    data: fakeServerData,
    getData: function (request: IServerSideGetRowsRequest) {
      function extractRowsFromData(groupKeys: string[], data: any[]): any {
        if (groupKeys.length === 0) {
          return data.map(function (d) {
            return {
              group: !!d.children,
              employeeId: d.employeeId,
              employeeName: d.employeeName,
              employmentType: d.employmentType,
              jobTitle: d.jobTitle,
            };
          });
        }
        const key = groupKeys[0];
        for (let i = 0; i < data.length; i++) {
          if (data[i].employeeId === key) {
            return extractRowsFromData(
              groupKeys.slice(1),
              data[i].children.slice(),
            );
          }
        }
      }
      return extractRowsFromData(request.groupKeys, this.data);
    },
  };
  return fakeServer;
}

function createServerSideDatasource(fakeServer: any) {
  const dataSource: IServerSideDatasource = {
    getRows: (params: IServerSideGetRowsParams) => {
      console.log("ServerSideDatasource.getRows: params = ", params);
      const allRows = fakeServer.getData(params.request);
      const request = params.request;
      const doingInfinite = request.startRow != null && request.endRow != null;
      const result = doingInfinite
        ? {
            rowData: allRows.slice(request.startRow, request.endRow),
            rowCount: allRows.length,
          }
        : { rowData: allRows };
      console.log("getRows: result = ", result);
      setTimeout(() => {
        params.success(result);
      }, 200);
    },
  };
  return dataSource;
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :autoGroupColumnDef="autoGroupColumnDef"
      :rowModelType="rowModelType"
      :treeData="true"
      :isServerSideGroupOpenByDefault="isServerSideGroupOpenByDefault"
      :isServerSideGroup="isServerSideGroup"
      :getServerSideGroupKey="getServerSideGroupKey"
      :rowData="rowData"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "employeeId", hide: true },
      { field: "employeeName", hide: true },
      { field: "jobTitle" },
      { field: "employmentType" },
    ]);
    const defaultColDef = ref<ColDef>({
      width: 240,
      flex: 1,
      sortable: false,
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      field: "employeeName",
      cellRendererParams: {
        innerRenderer: (params: ICellRendererParams) => {
          // display employeeName rather than group key (employeeId)
          return params.data.employeeName;
        },
      },
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const isServerSideGroupOpenByDefault = ref<IsServerSideGroupOpenByDefault>(
      (params: IsServerSideGroupOpenByDefaultParams) => {
        // open first two levels by default
        return params.rowNode.level < 2;
      },
    );
    const isServerSideGroup = ref<IsServerSideGroup>((dataItem: any) => {
      // indicate if node is a group
      return dataItem.group;
    });
    const getServerSideGroupKey = ref<GetServerSideGroupKey>(
      (dataItem: any) => {
        // specify which group key to use
        return dataItem.employeeId;
      },
    );
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        const fakeServer = createFakeServer(data);
        const datasource = createServerSideDatasource(fakeServer);
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

      fetch("https://www.ag-grid.com/example-assets/small-tree-data.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      autoGroupColumnDef,
      rowModelType,
      isServerSideGroupOpenByDefault,
      isServerSideGroup,
      getServerSideGroupKey,
      rowData,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Tree Data](https://www.ag-grid.com/examples/server-side-model-tree-data/tree-data/vue3)

## Refreshing Tree Data

Tree Data can be refreshed in the same way as groups are refreshed when not using Tree Data. This is explained in the [SSRM Refresh](https://www.ag-grid.com/vue-data-grid/server-side-model-updating-refresh/).

The example below shows this in action where the following can be noted:

- Click **Refresh Everything** to clear all caches by calling `api.refreshServerSide({ route: [], purge: true })`.
- Click **Refresh ['Kathryn Powers','Mabel Ward']** to clear a single cache by calling `api.refreshServerSide({ route: ['Kathryn Powers','Mabel Ward'], purge: true })`.

#### Purging Tree Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetServerSideGroupKey,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IServerSideDatasource,
  IServerSideGetRowsParams,
  IServerSideGetRowsRequest,
  IsServerSideGroup,
  IsServerSideGroupOpenByDefault,
  IsServerSideGroupOpenByDefaultParams,
  ModuleRegistry,
  RowModelType,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  ServerSideRowModelApiModule,
  ServerSideRowModelModule,
  TreeDataModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  TreeDataModule,
  ServerSideRowModelModule,
  ServerSideRowModelApiModule,
]);

function createFakeServer(fakeServerData: any[]) {
  const fakeServer = {
    getData: (request: IServerSideGetRowsRequest) => {
      function extractRowsFromData(groupKeys: string[], data: any[]): any {
        if (groupKeys.length === 0) {
          return data.map(function (d) {
            return {
              group: !!d.underlings,
              employeeId: d.employeeId + "",
              employeeName: d.employeeName,
              employmentType: d.employmentType,
              startDate: d.startDate,
            };
          });
        }
        const key = groupKeys[0];
        for (let i = 0; i < data.length; i++) {
          if (data[i].employeeName === key) {
            return extractRowsFromData(
              groupKeys.slice(1),
              data[i].underlings.slice(),
            );
          }
        }
      }
      return extractRowsFromData(request.groupKeys, fakeServerData);
    },
  };
  return fakeServer;
}

function createServerSideDatasource(fakeServer: any) {
  const dataSource: IServerSideDatasource = {
    getRows: (params: IServerSideGetRowsParams) => {
      console.log("ServerSideDatasource.getRows: params = ", params);
      const request = params.request;
      const allRows = fakeServer.getData(request);
      const doingInfinite = request.startRow != null && request.endRow != null;
      const result = doingInfinite
        ? {
            rowData: allRows.slice(request.startRow, request.endRow),
            rowCount: allRows.length,
          }
        : { rowData: allRows };
      console.log("getRows: result = ", result);
      setTimeout(() => {
        params.success(result);
      }, 500);
    },
  };
  return dataSource;
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <div style="margin-bottom: 5px">
        <button v-on:click="refreshCache([])">Refresh Everything</button>
        <button v-on:click="refreshCache(['Kathryn Powers', 'Mabel Ward'])">
          Refresh ['Kathryn Powers','Mabel Ward']
        </button>
      </div>
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :defaultColDef="defaultColDef"
        :autoGroupColumnDef="autoGroupColumnDef"
        :rowModelType="rowModelType"
        :treeData="true"
        :cacheBlockSize="cacheBlockSize"
        :isServerSideGroupOpenByDefault="isServerSideGroupOpenByDefault"
        :isServerSideGroup="isServerSideGroup"
        :getServerSideGroupKey="getServerSideGroupKey"
        :rowData="rowData"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "employeeId", hide: true },
      { field: "employeeName", hide: true },
      { field: "employmentType" },
      { field: "startDate" },
    ]);
    const defaultColDef = ref<ColDef>({
      width: 235,
      flex: 1,
      sortable: false,
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      field: "employeeName",
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const cacheBlockSize = ref(10);
    const isServerSideGroupOpenByDefault = ref<IsServerSideGroupOpenByDefault>(
      (params: IsServerSideGroupOpenByDefaultParams) => {
        const isKathrynPowers =
          params.rowNode.level == 0 &&
          params.data.employeeName == "Kathryn Powers";
        const isMabelWard =
          params.rowNode.level == 1 && params.data.employeeName == "Mabel Ward";
        return isKathrynPowers || isMabelWard;
      },
    );
    const isServerSideGroup = ref<IsServerSideGroup>((dataItem: any) => {
      // indicate if node is a group
      return dataItem.group;
    });
    const getServerSideGroupKey = ref<GetServerSideGroupKey>(
      (dataItem: any) => {
        // specify which group key to use
        return dataItem.employeeName;
      },
    );
    const rowData = ref<any[]>(null);

    function refreshCache(route: string[]) {
      gridApi.value!.refreshServerSide({ route: route, purge: true });
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        const fakeServer = createFakeServer(data);
        const datasource = createServerSideDatasource(fakeServer);
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

      fetch("https://www.ag-grid.com/example-assets/tree-data.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      autoGroupColumnDef,
      rowModelType,
      cacheBlockSize,
      isServerSideGroupOpenByDefault,
      isServerSideGroup,
      getServerSideGroupKey,
      rowData,
      onGridReady,
      refreshCache,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Purging Tree Data](https://www.ag-grid.com/examples/server-side-model-tree-data/purging-tree-data/vue3)

## Transactions with Tree Data

Tree Data can have transactions applied in the same way as row groups. This is explained in the [SSRM Transactions](https://www.ag-grid.com/vue-data-grid/server-side-model-updating-transactions/) section.

The example below demonstrates transactions with Tree Data. Note the following:

- **Add Child to Selected** adds a child under the selected row, even if it wasn't originally a group.
- **Update Selected** changes the selected row's `Employment Type`.
- **Delete Selected** removes the selected row, and all of its child rows.
- **Move Selected to Robert Peterson** moves the selected row and its children directly under `Robert Peterson`.

#### Transactions with Tree Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GetServerSideGroupKey,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IRowNode,
  IServerSideDatasource,
  IServerSideGetRowsParams,
  IServerSideGetRowsRequest,
  IsServerSideGroup,
  IsServerSideGroupOpenByDefault,
  IsServerSideGroupOpenByDefaultParams,
  ModuleRegistry,
  RowModelType,
  RowSelectionOptions,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  ServerSideRowModelApiModule,
  ServerSideRowModelModule,
  TreeDataModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  TextFilterModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  TreeDataModule,
  ServerSideRowModelModule,
  ServerSideRowModelApiModule,
]);

let fakeServer: {
  getData: (request: IServerSideGetRowsRequest) => void;
  addChildRow: (route: string[], newRow: any) => void;
  toggleEmployment: (route: string[]) => void;
  removeEmployee: (route: string[]) => void;
  moveEmployee: (from: string[], to: string[]) => void;
};

function getRouteToNode(rowNode: IRowNode): string[] {
  if (!rowNode.parent) {
    return [];
  }
  return [
    ...getRouteToNode(rowNode.parent),
    rowNode.key ? rowNode.key : rowNode.data.employeeName,
  ];
}

let latestId = 100000;

function createFakeServer(fakeServerData: any[], api: GridApi) {
  const getDataAtRoute = (route: string[]) => {
    let mutableRoute = [...route];
    let target: any = { underlings: fakeServerData };
    while (mutableRoute.length) {
      const nextRoute = mutableRoute[0];
      mutableRoute = mutableRoute.slice(1);
      target = target.underlings.find((e: any) => e.employeeName === nextRoute);
    }
    return target;
  };
  const sanitizeRowForGrid = (d: any) => {
    return {
      group: !!d.underlings && !!d.underlings.length,
      employeeId: d.employeeId,
      employeeName: d.employeeName,
      employmentType: d.employmentType,
      startDate: d.startDate,
    };
  };
  fakeServer = {
    getData: (request: IServerSideGetRowsRequest) => {
      function extractRowsFromData(groupKeys: string[], data: any[]): any {
        if (groupKeys.length === 0) {
          return data.map(sanitizeRowForGrid);
        }
        const key = groupKeys[0];
        for (let i = 0; i < data.length; i++) {
          if (data[i].employeeName === key) {
            return extractRowsFromData(
              groupKeys.slice(1),
              data[i].underlings.slice(),
            );
          }
        }
      }
      return extractRowsFromData(request.groupKeys, fakeServerData);
    },
    addChildRow: (route: string[], newRow: any) => {
      const target = getDataAtRoute(route);
      if (!target.underlings || target.underlings.length === 0) {
        target.underlings = [newRow];
        // update the parent row via transaction
        api.applyServerSideTransaction({
          route: route.slice(0, route.length - 1),
          update: [sanitizeRowForGrid(target)],
        });
      } else {
        target.underlings.push(newRow);
        // add the child row via transaction
        api.applyServerSideTransaction({
          route,
          add: [sanitizeRowForGrid(newRow)],
        });
      }
    },
    toggleEmployment: (route: string[]) => {
      const target = getDataAtRoute(route);
      // update the data at the source
      target.employmentType =
        target.employmentType === "Contract" ? "Permanent" : "Contract";
      // inform the grid of the changes
      api.applyServerSideTransaction({
        route: route.slice(0, route.length - 1),
        update: [sanitizeRowForGrid(target)],
      });
    },
    removeEmployee: (route: string[]) => {
      const target = getDataAtRoute(route);
      const parent = getDataAtRoute(route.slice(0, route.length - 1));
      parent.underlings = parent.underlings.filter(
        (child: any) => child.employeeName !== target.employeeName,
      );
      if (parent.underlings.length === 0) {
        // update the parent row via transaction, as it's no longer a group
        api.applyServerSideTransaction({
          route: route.slice(0, route.length - 2),
          update: [sanitizeRowForGrid(parent)],
        });
      } else {
        // inform the grid of the changes
        api.applyServerSideTransaction({
          route: route.slice(0, route.length - 1),
          remove: [sanitizeRowForGrid(target)],
        });
      }
    },
    moveEmployee: (route: string[], to: string[]) => {
      const target = getDataAtRoute(route);
      // remove employee from old group
      fakeServer.removeEmployee(route);
      // add employee to new group
      fakeServer.addChildRow(to, target);
    },
  };
  return fakeServer;
}

function createServerSideDatasource(fakeServer: any) {
  const dataSource: IServerSideDatasource = {
    getRows: (params: IServerSideGetRowsParams) => {
      console.log("ServerSideDatasource.getRows: params = ", params);
      const request = params.request;
      const allRows = fakeServer.getData(request);
      const doingInfinite = request.startRow != null && request.endRow != null;
      const result = doingInfinite
        ? {
            rowData: allRows.slice(request.startRow, request.endRow),
            rowCount: allRows.length,
          }
        : { rowData: allRows };
      console.log("getRows: result = ", result);
      setTimeout(() => {
        params.success(result);
      }, 500);
    },
  };
  return dataSource;
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <div style="margin-bottom: 5px">
        <button v-on:click="addToSelected()">Add Child to Selected</button>
        <button v-on:click="updateSelected()">Update Selected</button>
        <button v-on:click="deleteSelected()">Delete Selected</button>
        <button v-on:click="moveSelected()">Move Selected to Robert Peterson</button>
      </div>
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :defaultColDef="defaultColDef"
        :autoGroupColumnDef="autoGroupColumnDef"
        :rowModelType="rowModelType"
        :treeData="true"
        :cacheBlockSize="cacheBlockSize"
        :rowSelection="rowSelection"
        :isServerSideGroupOpenByDefault="isServerSideGroupOpenByDefault"
        :getRowId="getRowId"
        :isServerSideGroup="isServerSideGroup"
        :getServerSideGroupKey="getServerSideGroupKey"
        :rowData="rowData"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "employeeId", hide: true },
      { field: "employeeName", hide: true },
      { field: "employmentType" },
      { field: "startDate" },
    ]);
    const defaultColDef = ref<ColDef>({
      width: 235,
      flex: 1,
      sortable: false,
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      field: "employeeName",
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const cacheBlockSize = ref(10);
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "singleRow",
    });
    const isServerSideGroupOpenByDefault = ref<IsServerSideGroupOpenByDefault>(
      (params: IsServerSideGroupOpenByDefaultParams) => {
        const isKathrynPowers =
          params.rowNode.level == 0 &&
          params.data.employeeName == "Kathryn Powers";
        const isMabelWard =
          params.rowNode.level == 1 && params.data.employeeName == "Mabel Ward";
        return isKathrynPowers || isMabelWard;
      },
    );
    const getRowId = ref<GetRowIdFunc>((row: GetRowIdParams) =>
      String(row.data.employeeId),
    );
    const isServerSideGroup = ref<IsServerSideGroup>(
      (dataItem: any) => dataItem.group,
    );
    const getServerSideGroupKey = ref<GetServerSideGroupKey>(
      (dataItem: any) => dataItem.employeeName,
    );
    const rowData = ref<any[]>(null);

    function addToSelected() {
      const selected = gridApi.value!.getSelectedNodes()[0];
      if (!selected) {
        console.log("No row was selected.");
        return;
      }
      const route = getRouteToNode(selected);
      const newRow = {
        employeeId: ++latestId,
        employeeName: "Bertrand Parker " + latestId,
        employmentType: "Permanent",
        startDate: "20/01/1999",
      };
      fakeServer.addChildRow(route, newRow);
    }
    function updateSelected() {
      const selected = gridApi.value!.getSelectedNodes()[0];
      if (!selected) {
        console.log("No row was selected.");
        return;
      }
      const route = getRouteToNode(selected);
      fakeServer.toggleEmployment(route);
    }
    function deleteSelected() {
      const selected = gridApi.value!.getSelectedNodes()[0];
      if (!selected) {
        console.log("No row was selected.");
        return;
      }
      const route = getRouteToNode(selected);
      fakeServer.removeEmployee(route);
    }
    function moveSelected() {
      const selected = gridApi.value!.getSelectedNodes()[0];
      if (!selected) {
        console.log("No row was selected.");
        return;
      }
      const route = getRouteToNode(selected);
      fakeServer.moveEmployee(route, ["Robert Peterson"]);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        const adjustedData = [
          {
            employeeId: -1,
            employeeName: "Robert Peterson",
            employmentType: "Founder",
            startDate: "24/01/1990",
          },
          ...data,
        ];
        const fakeServer = createFakeServer(adjustedData, params.api!);
        const datasource = createServerSideDatasource(fakeServer);
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

      fetch("https://www.ag-grid.com/example-assets/tree-data.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      autoGroupColumnDef,
      rowModelType,
      cacheBlockSize,
      rowSelection,
      isServerSideGroupOpenByDefault,
      getRowId,
      isServerSideGroup,
      getServerSideGroupKey,
      rowData,
      onGridReady,
      addToSelected,
      updateSelected,
      deleteSelected,
      moveSelected,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Transactions with Tree Data](https://www.ag-grid.com/examples/server-side-model-tree-data/transactions-tree-data/vue3)

## Selection with Tree Data

Tree Data can have row selection applied in the same way as row groups. This is explained in the [SSRM Row Selection](https://www.ag-grid.com/vue-data-grid/server-side-model-selection/) section.

The example below demonstrates row selection with Tree Data where the grid has been configured with `rowSelection.groupSelects = 'descendants'`. This means that selecting a row with descendants selects all of its descendants. Selecting some, but not all, of the descendants of a row places that row in the indeterminate state.

#### Selection with Tree Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetServerSideGroupKey,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IServerSideDatasource,
  IServerSideGetRowsRequest,
  IsServerSideGroup,
  IsServerSideGroupOpenByDefault,
  ModuleRegistry,
  RowModelType,
  RowSelectionOptions,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  ServerSideRowModelApiModule,
  ServerSideRowModelModule,
  TreeDataModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ServerSideRowModelApiModule,
  TextFilterModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  TreeDataModule,
  ServerSideRowModelModule,
]);

interface FakeServer {
  getData: (request: IServerSideGetRowsRequest) => any;
  addChildRow: (route: string[], newRow: any) => void;
  toggleEmployment: (route: string[]) => void;
  removeEmployee: (route: string[]) => void;
  moveEmployee: (from: string[], to: string[]) => void;
}

let fakeServer: FakeServer;

function createFakeServer(fakeServerData: any[], api: GridApi) {
  const getDataAtRoute = (route: string[]) => {
    let mutableRoute = [...route];
    let target: any = { underlings: fakeServerData };
    while (mutableRoute.length) {
      const nextRoute = mutableRoute[0];
      mutableRoute = mutableRoute.slice(1);
      target = target.underlings.find((e: any) => e.employeeName === nextRoute);
    }
    return target;
  };
  const sanitizeRowForGrid = (d: any) => {
    return {
      group: !!d.underlings && !!d.underlings.length,
      employeeId: d.employeeId,
      employeeName: d.employeeName,
      employmentType: d.employmentType,
      startDate: d.startDate,
    };
  };
  fakeServer = {
    getData: (request: IServerSideGetRowsRequest): any => {
      function extractRowsFromData(groupKeys: string[], data: any[]): any {
        if (groupKeys.length === 0) {
          return data.map(sanitizeRowForGrid);
        }
        const key = groupKeys[0];
        for (let i = 0; i < data.length; i++) {
          if (data[i].employeeName === key) {
            return extractRowsFromData(
              groupKeys.slice(1),
              data[i].underlings.slice(),
            );
          }
        }
      }
      return extractRowsFromData(request.groupKeys, fakeServerData);
    },
    addChildRow: (route: string[], newRow: any) => {
      const target = getDataAtRoute(route);
      if (!target.underlings || target.underlings.length === 0) {
        target.underlings = [newRow];
        // update the parent row via transaction
        api.applyServerSideTransaction({
          route: route.slice(0, route.length - 1),
          update: [sanitizeRowForGrid(target)],
        });
      } else {
        target.underlings.push(newRow);
        // add the child row via transaction
        api.applyServerSideTransaction({
          route,
          add: [sanitizeRowForGrid(newRow)],
        });
      }
    },
    toggleEmployment: (route: string[]) => {
      const target = getDataAtRoute(route);
      // update the data at the source
      target.employmentType =
        target.employmentType === "Contract" ? "Permanent" : "Contract";
      // inform the grid of the changes
      api.applyServerSideTransaction({
        route: route.slice(0, route.length - 1),
        update: [sanitizeRowForGrid(target)],
      });
    },
    removeEmployee: (route: string[]) => {
      const target = getDataAtRoute(route);
      const parent = getDataAtRoute(route.slice(0, route.length - 1));
      parent.underlings = parent.underlings.filter(
        (child: any) => child.employeeName !== target.employeeName,
      );
      if (parent.underlings.length === 0) {
        // update the parent row via transaction, as it's no longer a group
        api.applyServerSideTransaction({
          route: route.slice(0, route.length - 2),
          update: [sanitizeRowForGrid(parent)],
        });
      } else {
        // inform the grid of the changes
        api.applyServerSideTransaction({
          route: route.slice(0, route.length - 1),
          remove: [sanitizeRowForGrid(target)],
        });
      }
    },
    moveEmployee: (route: string[], to: string[]) => {
      const target = getDataAtRoute(route);
      // remove employee from old group
      fakeServer.removeEmployee(route);
      // add employee to new group
      fakeServer.addChildRow(to, target);
    },
  };
  return fakeServer;
}

function createServerSideDatasource(fakeServer: FakeServer) {
  const dataSource: IServerSideDatasource = {
    getRows: (params) => {
      console.log("ServerSideDatasource.getRows: params = ", params);
      const request = params.request;
      const allRows = fakeServer.getData(request);
      const doingInfinite = request.startRow != null && request.endRow != null;
      const result = doingInfinite
        ? {
            rowData: allRows.slice(request.startRow, request.endRow),
            rowCount: allRows.length,
          }
        : { rowData: allRows };
      console.log("getRows: result = ", result);
      setTimeout(() => {
        params.success(result);
      }, 500);
    },
  };
  return dataSource;
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :defaultColDef="defaultColDef"
        :autoGroupColumnDef="autoGroupColumnDef"
        :rowModelType="rowModelType"
        :treeData="true"
        :columnDefs="columnDefs"
        :cacheBlockSize="cacheBlockSize"
        :rowSelection="rowSelection"
        :isServerSideGroupOpenByDefault="isServerSideGroupOpenByDefault"
        :getRowId="getRowId"
        :isServerSideGroup="isServerSideGroup"
        :getServerSideGroupKey="getServerSideGroupKey"
        :rowData="rowData"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const defaultColDef = ref<ColDef>({
      width: 235,
      flex: 1,
      sortable: false,
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      field: "employeeName",
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const columnDefs = ref<ColDef[]>([
      { field: "employeeId", hide: true },
      { field: "employeeName", hide: true },
      { field: "employmentType" },
      { field: "startDate" },
    ]);
    const cacheBlockSize = ref(10);
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "multiRow",
      groupSelects: "descendants",
    });
    const isServerSideGroupOpenByDefault = ref<IsServerSideGroupOpenByDefault>(
      (params) => {
        const isKathrynPowers =
          params.rowNode.level == 0 &&
          params.data.employeeName == "Kathryn Powers";
        const isMabelWard =
          params.rowNode.level == 1 && params.data.employeeName == "Mabel Ward";
        return isKathrynPowers || isMabelWard;
      },
    );
    const getRowId = ref<GetRowIdFunc>((row) => String(row.data.employeeId));
    const isServerSideGroup = ref<IsServerSideGroup>(
      (dataItem) => dataItem.group,
    );
    const getServerSideGroupKey = ref<GetServerSideGroupKey>(
      (dataItem) => dataItem.employeeName,
    );
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        const adjustedData = [
          {
            employeeId: -1,
            employeeName: "Robert Peterson",
            employmentType: "Founder",
            startDate: "24/01/1990",
          },
          ...data,
        ];
        const fakeServer = createFakeServer(adjustedData, params.api!);
        const datasource = createServerSideDatasource(fakeServer);
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

      fetch("https://www.ag-grid.com/example-assets/tree-data.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      defaultColDef,
      autoGroupColumnDef,
      rowModelType,
      columnDefs,
      cacheBlockSize,
      rowSelection,
      isServerSideGroupOpenByDefault,
      getRowId,
      isServerSideGroup,
      getServerSideGroupKey,
      rowData,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Selection with Tree Data](https://www.ag-grid.com/examples/server-side-model-tree-data/selecting-tree-data/vue3)

## Filtering Tree Data

Server-Side Tree Data Filtering should behave the same as Client-Side [Tree Data Filtering](https://www.ag-grid.com/vue-data-grid/tree-data-filtering/). A group will be included if:

1. it has any children that pass the filter, or
2. it has a parent that passes the filter, or
3. its own data passes the filter

The following example demonstrates Server-Side Tree Data Filtering using the [Set Filter Tree List](https://www.ag-grid.com/vue-data-grid/filter-set-tree-list/), which replicates the Tree Data structure in the filter.

- The **Group** column has the Set Filter Tree List enabled via `filterParams.treeList = true`. A Key Creator is specified to convert the path into a string.
- The **Group** column has the filter values supplied asynchronously as a nested array of strings that matches the data paths. The supplied values are at the child level only.
- The **Date** column has the Set Filter Tree List enabled via `filterParams.treeList = true`, and is grouped by year -> month -> day.
- The **Date** column has the filter values supplied asynchronously as an array of `Date` objects.
- The **Date** column has a `filterParams.keyCreator` provided to convert the `Date` values into the (string) format the server is expecting in the Filter Model.
- The **Group** and **Date** columns both have `filterParams.excelMode = 'windows'`, which allows changes to the tree filter to be applied in batches.

#### Filtering Tree Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetServerSideGroupKey,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IServerSideDatasource,
  ISetFilterParams,
  IsServerSideGroup,
  IsServerSideGroupOpenByDefault,
  IsServerSideGroupOpenByDefaultParams,
  KeyCreatorParams,
  ModuleRegistry,
  RowModelType,
  SetFilterValuesFuncParams,
  TextFilterModule,
  ValueFormatterParams,
  ValueGetterParams,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  ServerSideRowModelModule,
  SetFilterModule,
  TreeDataModule,
} from "ag-grid-enterprise";
import { FakeServer } from "./fakeServer";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  TreeDataModule,
  ServerSideRowModelModule,
  SetFilterModule,
  TextFilterModule,
]);

function valueGetter(params: ValueGetterParams) {
  // server is returning a string, so need to convert to `Date`.
  // could instead do this inside `IServerSideDatasource.getRows`
  return params.data.startDate ? new Date(params.data.startDate) : null;
}

function cellValueFormatter(params: ValueFormatterParams) {
  return params.value ? params.value.toLocaleDateString() : null;
}

function floatingFilterValueFormatter(params: ValueFormatterParams) {
  return params.value ? params.value.toLocaleDateString() : "(Blanks)";
}

function dateKeyCreator(params: KeyCreatorParams) {
  // this is what is being sent in the Filter Model to the server, so want the matching format
  return params.value ? params.value.toISOString() : null;
}

function treeDataKeyCreator(params: KeyCreatorParams) {
  // tree data group filter value is a string[], so convert to a unique string
  return params.value ? params.value.join(",") : null;
}

let fakeServer: any;

function getServerSideDatasource(server: any): IServerSideDatasource {
  return {
    getRows: (params) => {
      console.log("[Datasource] - rows requested by grid: ", params.request);
      // simulating real server call with a 500ms delay
      setTimeout(() => {
        // get data for request from our fake server
        const response = server.getData(params.request);
        if (response.success) {
          // supply rows for requested block to grid
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          params.fail();
        }
      }, 500);
    },
  };
}

function getDatesAsync(params: SetFilterValuesFuncParams<any, Date>) {
  if (!fakeServer) {
    // wait for init
    setTimeout(() => getDatesAsync(params), 500);
    return;
  }
  let dates = fakeServer.getDates();
  if (dates) {
    // values need to match the cell value (what the `valueGetter` returns)
    dates = dates.map((isoDateString: string) =>
      isoDateString ? new Date(isoDateString) : isoDateString,
    );
  }
  // simulating real server call with a 500ms delay
  setTimeout(() => {
    params.success(dates);
  }, 500);
}

function getEmployeesAsync(params: SetFilterValuesFuncParams<any, string[]>) {
  if (!fakeServer) {
    // wait for init
    setTimeout(() => getEmployeesAsync(params), 500);
    return;
  }
  const employees = fakeServer.getEmployees();
  // simulating real server call with a 500ms delay
  setTimeout(() => {
    params.success(employees);
  }, 500);
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :autoGroupColumnDef="autoGroupColumnDef"
      :rowModelType="rowModelType"
      :treeData="true"
      :isServerSideGroupOpenByDefault="isServerSideGroupOpenByDefault"
      :isServerSideGroup="isServerSideGroup"
      :getServerSideGroupKey="getServerSideGroupKey"
      :rowData="rowData"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "employeeId", hide: true },
      { field: "employeeName", hide: true },
      { field: "employmentType" },
      {
        field: "startDate",
        valueGetter: valueGetter,
        valueFormatter: cellValueFormatter,
        filter: "agSetColumnFilter",
        filterParams: {
          treeList: true,
          excelMode: "windows",
          keyCreator: dateKeyCreator,
          valueFormatter: floatingFilterValueFormatter,
          values: getDatesAsync,
        } as ISetFilterParams<any, Date>,
      },
    ]);
    const defaultColDef = ref<ColDef>({
      width: 240,
      filter: "agTextColumnFilter",
      floatingFilter: true,
      flex: 1,
      sortable: false,
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      field: "employeeName",
      filter: "agSetColumnFilter",
      filterParams: {
        treeList: true,
        excelMode: "windows",
        keyCreator: treeDataKeyCreator,
        values: getEmployeesAsync,
      } as ISetFilterParams<any, string[]>,
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const isServerSideGroupOpenByDefault = ref<IsServerSideGroupOpenByDefault>(
      (params: IsServerSideGroupOpenByDefaultParams) => {
        // open first level by default
        return params.rowNode.level === 0;
      },
    );
    const isServerSideGroup = ref<IsServerSideGroup>((dataItem: any) => {
      // indicate if node is a group
      return dataItem.underlings;
    });
    const getServerSideGroupKey = ref<GetServerSideGroupKey>(
      (dataItem: any) => {
        // specify which group key to use
        return dataItem.employeeName;
      },
    );
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        // setup the fake server with entire dataset
        fakeServer = new FakeServer(data);
        // create datasource with a reference to the fake server
        const datasource = getServerSideDatasource(fakeServer);
        // register the datasource with the grid
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

      fetch("https://www.ag-grid.com/example-assets/tree-data.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      autoGroupColumnDef,
      rowModelType,
      isServerSideGroupOpenByDefault,
      isServerSideGroup,
      getServerSideGroupKey,
      rowData,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Filtering Tree Data](https://www.ag-grid.com/examples/server-side-model-tree-data/filtering-tree-data/vue3)

## Providing Hierarchical Data via API

Below is an example of utilising `api.applyServerSideRowData` to populate data. The datasource is only used to provide root level rows, and when these are loaded the grid is provided the child row data via the API.

#### Hierarchical Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetServerSideGroupKey,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ICellRendererParams,
  IServerSideDatasource,
  IServerSideGetRowsParams,
  IsServerSideGroup,
  IsServerSideGroupOpenByDefault,
  IsServerSideGroupOpenByDefaultParams,
  ModuleRegistry,
  RowModelType,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  ServerSideRowModelApiModule,
  ServerSideRowModelModule,
  TreeDataModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ColumnsToolPanelModule,
  TreeDataModule,
  ColumnMenuModule,
  ContextMenuModule,
  ServerSideRowModelModule,
  ServerSideRowModelApiModule,
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :autoGroupColumnDef="autoGroupColumnDef"
      :rowModelType="rowModelType"
      :treeData="true"
      :isServerSideGroupOpenByDefault="isServerSideGroupOpenByDefault"
      :isServerSideGroup="isServerSideGroup"
      :getServerSideGroupKey="getServerSideGroupKey"
      :rowData="rowData"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "employeeId", hide: true },
      { field: "employeeName", hide: true },
      { field: "jobTitle" },
      { field: "employmentType" },
    ]);
    const defaultColDef = ref<ColDef>({
      width: 240,
      flex: 1,
      sortable: false,
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      field: "employeeName",
      cellRendererParams: {
        innerRenderer: (params: ICellRendererParams) => {
          // display employeeName rather than group key (employeeId)
          return params.data.employeeName;
        },
      },
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const isServerSideGroupOpenByDefault = ref<IsServerSideGroupOpenByDefault>(
      (params: IsServerSideGroupOpenByDefaultParams) => {
        // open first two levels by default
        return params.rowNode.level < 2;
      },
    );
    const isServerSideGroup = ref<IsServerSideGroup>((dataItem: any) => {
      // indicate if node is a group
      return !!dataItem.underlings;
    });
    const getServerSideGroupKey = ref<GetServerSideGroupKey>(
      (dataItem: any) => {
        // specify which group key to use
        return dataItem.employeeId;
      },
    );
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        const datasource = createServerSideDatasource(data);
        params.api!.setGridOption("serverSideDatasource", datasource);
        function createServerSideDatasource(data: any) {
          const dataSource: IServerSideDatasource = {
            getRows: (params: IServerSideGetRowsParams) => {
              console.log("ServerSideDatasource.getRows: params = ", params);
              const request = params.request;
              if (request.groupKeys.length) {
                // this example doesn't need to support lower levels.
                params.fail();
                return;
              }
              const result = {
                rowData: data.slice(request.startRow, request.endRow),
              };
              console.log("getRows: result = ", result);
              setTimeout(() => {
                params.success(result);
                const recursivelyPopulateHierarchy = (
                  route: string[],
                  node: any,
                ) => {
                  if (node.underlings) {
                    params.api!.applyServerSideRowData({
                      route,
                      successParams: {
                        rowData: node.underlings,
                        rowCount: node.underlings.length,
                      },
                    });
                    node.underlings.forEach((child: any) => {
                      recursivelyPopulateHierarchy(
                        [...route, child.employeeId],
                        child,
                      );
                    });
                  }
                };
                result.rowData.forEach((topLevelNode: any) => {
                  recursivelyPopulateHierarchy(
                    [topLevelNode.employeeId],
                    topLevelNode,
                  );
                });
              }, 200);
            },
          };
          return dataSource;
        }
      };

      fetch("https://www.ag-grid.com/example-assets/tree-data.json")
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      autoGroupColumnDef,
      rowModelType,
      isServerSideGroupOpenByDefault,
      isServerSideGroup,
      getServerSideGroupKey,
      rowData,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Hierarchical Data](https://www.ag-grid.com/examples/server-side-model-tree-data/supplying-hierarchy/vue3)

Continue to the next section to learn how to combine [Master Detail](https://www.ag-grid.com/vue-data-grid/server-side-model-master-detail/) with the SSRM.
