JavaScript/jQuery TreeList - Getting Started
DevExtreme includes multiple data management components. You can use them to display tabular, hierarchical, multidimensional data, or present information as cards. For more information about these components and their features, see How to Choose a Data Management Component.
jQuery
Angular
Vue
React
DevExtreme TreeList displays data in a multi-column tree view. The component supports local and remote data stores and allows users to shape data using sorting, filtering, and other operations.
Need to create printable documents simply? Try our .NET-based DevExpress Reports: they ship with an intuitive Visual Studio Report Designer, Web Report Designer for end-user ad-hoc reporting, and a rich set of report controls, including cross tabs and charts.
You can generate a variety of report types — from simple mail-merge, table, and vertical reports to master-detail (hierarchical) and cross-tab reports, print or export them to PDF, Excel, and other formats.
Develop using VS Code? Leverage the capabilities of a brand new VS Code Report Designer extension to create and edit reports/documents on any platform, be it Windows, macOS, or Linux.
Get Started with DevExpress Reports | Explore Demos
Use our DevExpress BI Dashboard to embed interactive business intelligence into your next web app.
The Web Dashboard is a data analysis UI component that you can embed into your ASP.NET Core or Angular, React, and Vue applications with .NET backend. Dashboards allow you to display multiple inter-connected data analysis elements such as grids, charts, maps, gauges, and others: all within an automatically-arranged layout.
The set of components allows you to deploy an all-in-one solution and switch between Viewer and Designer modes directly on the web client (includes adaptive layouts for tablet & mobile).
The Web Dashboard is available as a part of a Universal subscription.
This tutorial adds DevExtreme TreeList to a page, binds the component to data, and configures core TreeList features.
Each section in this tutorial covers a single configuration step. You can also find the full code in the following GitHub repository:
Create a TreeList
jQuery
Add DevExtreme to your jQuery application and use the following code to create a TreeList:
Angular
Add DevExtreme to your Angular application and use the following code to create a TreeList:
Vue
Add DevExtreme to your Vue application and use the following code to create a TreeList:
React
Add DevExtreme to your React application and use the following code to create a TreeList:
jQuery
$(function() {
$("#tree-list").dxTreeList({
// Configuration goes here
});
});
<html>
<head>
<!-- ... -->
<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/26.1.3/css/dx.light.css">
<link rel="stylesheet" href="index.css">
<script type="text/javascript" src="https://cdn3.devexpress.com/jslib/26.1.3/js/dx.all.js"></script>
<script type="text/javascript" src="index.js"></script>
</head>
<body class="dx-viewport">
<div id="tree-list"></div>
</body>
</html>ASP.NET Core Controls
@(Html.DevExtreme().TreeList()
.ID("tree-list")
)Angular
<dx-tree-list id="tree-list"
<!-- Configuration goes here -->
>
</dx-tree-list>
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { DxTreeListModule } from 'devextreme-angular';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
DxTreeListModule
],
providers: [ ],
bootstrap: [AppComponent]
})
export class AppModule { }Vue
<template>
<div id="app-container">
<DxTreeList
id="tree-list"
/>
</div>
</template>
<script setup lang="ts">
import 'devextreme/dist/css/dx.fluent.blue.light.css';
import { DxTreeList } from 'devextreme-vue/tree-list';
</script>React
import React from 'react';
import 'devextreme/dist/css/dx.fluent.blue.light.css';
import { TreeList } from 'devextreme-react/tree-list';
function App() {
return (
<div className="App">
<TreeList id="tree-list">
{/* Configuration goes here */}
</TreeList>
</div>
);
}
export default App;Bind the TreeList to Data
TreeList supports plain and hierarchical data structures. To use a hierarchical data source, set dataStructure to "tree" and define itemsExpr to specify the data field that contains child nodes. TreeList automatically generates identifiers for all nodes and builds the node hierarchy. Refer to the following demo for more information:
TreeList - Simple Array: Hierarchical Structure Demo
In a plain data structure, nodes must include unique identifiers and references to parent nodes. To specify data fields that store identifiers and references, use the keyExpr and parentIdExpr properties. Top-level nodes descend from the root node. To specify the root identifier, define rootValue.
TreeList can load and update data from different data source types. To use a local array, assign the array to dataSource. The following code snippet initializes a TreeList and creates columns for all data fields. All columns are equal width, and the column order follows the data source structure:
jQuery
$("#tree-list").dxTreeList({
dataSource: employees,
rootValue: -1,
keyExpr: "ID",
parentIdExpr: "HeadID"
});
const employees = [
// ...
];ASP.NET Core Controls
@using ASP_NET_Core.Models
@(Html.DevExtreme().TreeList<Employee>()
.ID("tree-list")
.DataSource(d => d
.Mvc().Controller("EmployeeData")
.LoadAction("Get")
.InsertAction("Insert")
.UpdateAction("Update")
.DeleteAction("Delete")
.Key("ID")
)
.RootValue(-1)
.ParentIdExpr("HeadID")
)
using ASP_NET_Core.Models;
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using Microsoft.AspNetCore.Mvc;
namespace ASP_NET_Core.Controllers;
public class EmployeeDataController : Controller {
[HttpGet]
public object Get(DataSourceLoadOptions loadOptions) {
return DataSourceLoader.Load(EmployeeData.Employees.OrderBy(e => e.OrderIndex), loadOptions);
}
// ...
}
namespace ASP_NET_Core.Models;
public class Employee {
public int ID { get; set; }
public int HeadID { get; set; }
public string FullName { get; set; }
public string Position { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Email { get; set; }
public string Skype { get; set; }
public string MobilePhone { get; set; }
public string BirthDate { get; set; }
public string HireDate { get; set; }
public int OrderIndex { get; set; }
}
namespace ASP_NET_Core.Models;
static class EmployeeData {
public static List<Employee> Employees = [
// ...
];
}Angular
<dx-tree-list
[dataSource]="employees"
[rootValue]="-1"
keyExpr="ID"
parentIdExpr="HeadID">
</dx-tree-list>
import { Component } from '@angular/core';
import { EmployeesService, type Employee } from './employees.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
employees: Employee[] = [];
constructor(service: EmployeesService) {
this.employees = service.getEmployees();
}
}
import { Injectable } from '@angular/core';
export interface Employee {
ID: number;
HeadID: number;
FullName: string;
Position: string;
City: string;
State: string;
Email: string;
Skype: string;
MobilePhone: string;
BirthDate: string;
HireDate: string;
}
@Injectable({
providedIn: 'root'
})
export class EmployeesService {
private employees: Employee[] = [
// ...
];
getEmployees(): Employee[] {
return this.employees;
}
}Vue
<template>
<div id="app-container">
<DxTreeList
:data-source="employees"
:root-value="-1"
key-expr="ID"
parent-id-expr="HeadID">
</DxTreeList>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DxTreeList } from 'devextreme-vue/tree-list';
import { employeesService, type Employee } from '../employeesService';
const employees = ref<Employee[]>(employeesService.getEmployees());
</script>
export interface Employee {
ID: number;
HeadID: number;
FullName: string;
Position: string;
City: string;
State: string;
Email: string;
Skype: string;
MobilePhone: string;
BirthDate: string;
HireDate: string;
}
const employees: Employee[] = [
// ...
];
let employeeData = [...employees];
export const employeesService = {
getEmployees: (): Employee[] => employeeData,
// ...
}React
import React, { useState } from 'react';
import { TreeList } from 'devextreme-react/tree-list';
import { employeesService, type Employee } from './employeesService';
function App() {
const [currentEmployees, setCurrentEmployees] = useState<Employee[]>(employeesService.getEmployees());
return (
<div className="App">
<TreeList
dataSource={currentEmployees}
rootValue={-1}
keyExpr="ID"
parentIdExpr="HeadID">
</TreeList>
</div>
);
}
export interface Employee {
ID: number;
HeadID: number;
FullName: string;
Position: string;
City: string;
State: string;
Email: string;
Skype: string;
MobilePhone: string;
BirthDate: string;
HireDate: string;
}
const employees: Employee[] = [
// ...
];
let employeeData = [...employees];
export const employeesService = {
getEmployees: (): Employee[] => employeeData,
// ...
}To use another data source type, refer to the following help topics:
Expand Rows
Read Tutorial: TreeList - Expand and Collapse Rows
You can define expandedRowKeys to expand specific rows. Enable autoExpandAll to initialize all rows in the expanded state:
jQuery
$("#tree-list").dxTreeList({
autoExpandAll: true,
// ...
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.AutoExpandAll(true)
@* ... *@
)Angular
<dx-tree-list
[autoExpandAll]="true"
>
<!-- ... -->
</dx-tree-list>Vue
<template>
<DxTreeList
:auto-expand-all="true"
>
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList } from 'devextreme-vue/tree-list';
</script>React
import { TreeList } from 'devextreme-react/tree-list';
function App() {
return (
<TreeList
autoExpandAll={true}
>
{/* ... */}
</TreeList>
);
}Customize Columns
Read Tutorial: Columns - Overview
jQuery
Use the columns array to define and customize TreeList columns. Configure columns as objects to specify column options. To add columns with default options, specify dataField values as strings in the columns[] array.
Angular
Specify the columns array to define and customize TreeList columns. Specify each column's dataField value.
Vue
Specify the columns array to define and customize TreeList columns. To add a column, specify the dataField value.
React
Specify the columns array to define and customize TreeList columns. To add a column, specify the dataField value.
Reorder Columns
Read Tutorial: Column Reordering
To set the initial column order, arrange columns in the columns[] array as needed. Enable allowColumnReordering to allow users to reorder columns in the component UI.
The following code snippet also specifies dataType for BirthDate and HireDate columns to display string values as dates:
jQuery
$(function() {
$("#tree-list").dxTreeList({
columns: [ "FullName", "Position", {
dataField: "BirthDate",
dataType: "date",
}, {
dataField: "HireDate",
dataType: "date",
}, "City", "State", "Email", "MobilePhone", "Skype"],
allowColumnReordering: true,
// ...
});
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.Columns(columns => {
columns.AddFor(m => m.FullName);
columns.AddFor(m => m.Position);
columns.AddFor(m => m.BirthDate)
.DataType(GridColumnDataType.Date);
columns.AddFor(m => m.HireDate)
.DataType(GridColumnDataType.Date);
columns.AddFor(m => m.City);
columns.AddFor(m => m.State);
columns.AddFor(m => m.Email);
columns.AddFor(m => m.MobilePhone);
columns.AddFor(m => m.Skype);
})
@* ... *@
)Angular
<dx-tree-list [allowColumnReordering]="true">
<dxi-tree-list-column dataField="FullName"></dxi-tree-list-column>
<dxi-tree-list-column dataField="Position"></dxi-tree-list-column>
<dxi-tree-list-column
dataField="BirthDate"
dataType="date">
</dxi-tree-list-column>
<dxi-tree-list-column
dataField="HireDate"
dataType="date">
</dxi-tree-list-column>
<dxi-tree-list-column dataField="City"></dxi-tree-list-column>
<dxi-tree-list-column dataField="State"></dxi-tree-list-column>
<dxi-tree-list-column dataField="Email"></dxi-tree-list-column>
<dxi-tree-list-column dataField="MobilePhone"></dxi-tree-list-column>
<dxi-tree-list-column dataField="Skype"></dxi-tree-list-column>
</dx-tree-list>Vue
<template>
<DxTreeList :allow-column-reordering="true">
<DxColumn data-field="FullName" />
<DxColumn data-field="Position" />
<DxColumn
data-field="BirthDate"
data-type="date">
</DxColumn>
<DxColumn
data-field="HireDate"
data-type="date">
</DxColumn>
<DxColumn data-field="City" />
<DxColumn data-field="State" />
<DxColumn data-field="Email" />
<DxColumn data-field="MobilePhone" />
<DxColumn data-field="Skype" />
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxColumn } from 'devextreme-vue/tree-list';
</script>React
import { TreeList, Column } from 'devextreme-react/tree-list';
function App() {
return (
<TreeList allowColumnReordering={true}>
<Column dataField="FullName" />
<Column dataField="Position" />
<Column
dataField="BirthDate"
dataType="date">
</Column>
<Column
dataField="HireDate"
dataType="date">
</Column>
<Column dataField="City" />
<Column dataField="State" />
<Column dataField="Email" />
<Column dataField="MobilePhone" />
<Column dataField="Skype" />
</TreeList>
);
}Resize Columns
In the default configuration, TreeList columns have equal widths (width is set to "auto"). To change the column layout, set each column's width property or enable columnAutoWidth to adjust all columns to fit cell values. To allow users to change the column layout, enable allowColumnResizing.
jQuery
$("#tree-list").dxTreeList({
allowColumnResizing: true,
columnAutoWidth: true,
columns: [{
dataField: "BirthDate",
dataType: "date",
width: 100,
}, {
dataField: "HireDate",
dataType: "date",
width: 100,
}, /* ... */ ],
// ...
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.AllowColumnResizing(true)
.ColumnAutoWidth(true)
.Columns(columns => {
columns.AddFor(m => m.BirthDate)
.DataType(GridColumnDataType.Date)
.Width(100);
columns.AddFor(m => m.HireDate)
.DataType(GridColumnDataType.Date)
.Width(100);
})
@* ... *@
)Angular
<dx-tree-list
[allowColumnResizing]="true"
[columnAutoWidth]="true"
>
<dxi-tree-list-column
dataField="BirthDate"
dataType="date"
[width]="100"
></dxi-tree-list-column>
<dxi-tree-list-column
dataField="HireDate"
dataType="date"
[width]="100"
></dxi-tree-list-column>
<!-- ... -->
</dx-tree-list>Vue
<template>
<DxTreeList
:allow-column-resizing="true"
:column-auto-width="true"
>
<DxColumn
data-field="BirthDate"
data-type="date"
:width="100"
/>
<DxColumn
data-field="HireDate"
data-type="date"
:width="100"
/>
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxColumn } from 'devextreme-vue/tree-list';
</script>React
import { TreeList, Column } from 'devextreme-react/tree-list';
function App() {
return (
<TreeList
columnAutoWidth={true}
allowColumnResizing={true}
>
<Column
dataField="BirthDate"
dataType="date"
width={100}
/>
<Column
dataField="HireDate"
dataType="date"
width={100}
/>
{/* ... */}
</TreeList>
);
}Fix Columns
When the total column width exceeds the UI component width, a horizontal scroll bar appears. To keep specific columns visible, enable columnFixing. Set columns[].fixed to true to fix a column. Set fixedPosition to specify the fixed column position or create a sticky column. Users can also change column fixing options in the TreeList context menu.
jQuery
$("#tree-list").dxTreeList({
columnFixing: { enabled: true },
columns: [{
dataField: "FullName",
fixed: true
}, /* ... */ ],
// ...
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.ColumnFixing(c => c.Enabled(true))
.Columns(columns => {
columns.AddFor(m => m.FullName)
.Fixed(true);
})
@* ... *@
)Angular
<dx-tree-list>
<dxo-tree-list-column-fixing [enabled]="true"></dxo-tree-list-column-fixing>
<dxi-tree-list-column
dataField="FullName"
[fixed]="true"
></dxi-tree-list-column>
<!-- ... -->
</dx-tree-list>Vue
<template>
<DxTreeList>
<DxColumnFixing :enabled="true" />
<DxColumn
data-field="FullName"
:fixed="true"
/>
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxColumnFixing, DxColumn } from 'devextreme-vue/tree-list';
</script>React
import { TreeList, ColumnFixing, Column } from 'devextreme-react/tree-list';
function App() {
return (
<div className="App">
<TreeList>
<ColumnFixing enabled={true} />
<Column
dataField="FullName"
fixed={true}
/>
{/* ... */}
</TreeList>
</div>
);
}Hide Columns
Read Tutorial: Hide a Column Using the API Read Tutorial: Column Chooser
To hide a TreeList column, set columns[].visible to false. If the columnChooser is enabled, users can restore hidden columns. To hide a column in both the component and the column chooser, omit the corresponding columns[] item.
jQuery
$("#tree-list").dxTreeList({
columnChooser: { enabled: true },
columns: [{
dataField: "Email",
visible: false
}, /* ... */ ],
// ...
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.ColumnChooser(c => c.Enabled(true))
.Columns(columns => {
columns.AddFor(m => m.Email)
.Visible(false);
})
@* ... *@
)Angular
<dx-tree-list>
<dxo-tree-list-column-chooser [enabled]="true"></dxo-tree-list-column-chooser>
<dxi-tree-list-column
dataField="Email"
[visible]="false"
></dxi-tree-list-column>
<!-- ... -->
</dx-tree-list>Vue
<template>
<DxTreeList ... >
<DxColumnChooser :enabled="true" />
<DxColumn
data-field="Email"
:visible="false"
/>
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxColumnChooser, DxColumn } from 'devextreme-vue/tree-list';
</script>React
import { TreeList, ColumnChooser, Column } from 'devextreme-react/tree-list';
function App() {
return (
<TreeList ... >
<ColumnChooser enabled={true} />
<Column
dataField="Email"
visible={false}
/>
{/* ... */}
</TreeList>
);
}Sort Data
Read Tutorial: TreeList - Sorting
The sorting.mode property specifies whether users can sort TreeList records against single or multiple columns.
You can also set a column's sortOrder and sortIndex properties to specify initial sorting settings. sortIndex applies only in multi-column sort mode.
To sort data and change sort orders in the UI, click column headers. Hold Shift and click to sort data against multiple columns.
jQuery
$("#tree-list").dxTreeList({
sorting: { mode: "multiple" },
// ...
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.Sorting(s => s.Mode(GridSortingMode.Multiple))
@* ... *@
)Angular
<dx-tree-list>
<dxo-tree-list-sorting mode="multiple"></dxo-tree-list-sorting>
<!-- ... -->
</dx-tree-list>Vue
<template>
<DxTreeList ... >
<DxSorting mode="multiple" />
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxSorting, DxColumn } from 'devextreme-vue/tree-list';
</script>React
import { TreeList, Sorting, Column } from 'devextreme-react/tree-list';
function App() {
return (
<div className="App">
<TreeList ... >
<Sorting mode="multiple" />
{/* ... */}
</TreeList>
</div>
);
}Filter and Search Data
Read Tutorial: TreeList - Filtering and Searching
TreeList includes the following UI elements used to filter and search data:
This tutorial uses the filterRow and searchPanel:
jQuery
$("#tree-list").dxTreeList({
filterRow: { visible: true },
searchPanel: { visible: true },
// ...
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.FilterRow(f => f.Visible(true))
.SearchPanel(s => s.Visible(true))
@* ... *@
)Angular
<dx-tree-list>
<dxo-tree-list-filter-row [visible]="true"></dxo-tree-list-filter-row>
<dxo-tree-list-search-panel [visible]="true"></dxo-tree-list-search-panel>
<!-- ... -->
</dx-tree-list>Vue
<template>
<DxTreeList ... >
<DxFilterRow :visible="true" />
<DxSearchPanel :visible="true" />
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxFilterRow, DxSearchPanel } from 'devextreme-vue/tree-list';
</script>React
import { TreeList, FilterRow, SearchPanel } from 'devextreme-react/tree-list';
function App() {
return (
<TreeList ... >
<FilterRow visible={true} />
<SearchPanel visible={true} />
{/* ... */}
</TreeList>
);
}Edit and Validate Data
Read Tutorial: TreeList - Editing
Users can add new records and update or delete existing records. To allow these operations, enable the following editing options:
TreeList supports multiple edit modes. This tutorial uses the pop-up edit mode.
This tutorial also implements data validation to check that edited data is valid before saving. Required rules are configured to ensure certain column values are never empty:
jQuery
$("#tree-list").dxTreeList({
editing: {
mode: "popup",
allowUpdating: true,
allowDeleting: true,
allowAdding: true
},
columns: [{
dataField: "FullName",
validationRules: [{ type: "required" }]
}, {
dataField: "Position",
validationRules: [{ type: "required" }]
}, {
dataField: "BirthDate",
validationRules: [{ type: "required" }]
}, {
dataField: "HireDate",
validationRules: [{ type: "required" }]
}, {
dataField: "State",
validationRules: [{ type: "required" }]
}, /* ... */ ],
// ...
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.Editing(e => e
.Mode(GridEditMode.Popup)
.AllowAdding(true)
.AllowDeleting(true)
.AllowUpdating(true)
)
.Columns(columns => {
columns.AddFor(m => m.FullName)
.ValidationRules(v => v.AddRequired());
columns.AddFor(m => m.Position)
.ValidationRules(v => v.AddRequired());
columns.AddFor(m => m.BirthDate)
.ValidationRules(v => v.AddRequired());
columns.AddFor(m => m.HireDate)
.ValidationRules(v => v.AddRequired());
columns.AddFor(m => m.State)
.ValidationRules(v => v.AddRequired());
})
@* ... *@
)Angular
<dx-tree-list>
<dxo-tree-list-editing
mode="popup"
[allowUpdating]="true"
[allowDeleting]="true"
[allowAdding]="true"
></dxo-tree-list-editing>
<dxi-tree-list-column dataField="FullName">
<dxi-tree-list-validation-rule type="required"></dxi-tree-list-validation-rule>
</dxi-tree-list-column>
<dxi-tree-list-column dataField="Position">
<dxi-tree-list-validation-rule type="required"></dxi-tree-list-validation-rule>
</dxi-tree-list-column>
<dxi-tree-list-column dataField="BirthDate">
<dxi-tree-list-validation-rule type="required"></dxi-tree-list-validation-rule>
</dxi-tree-list-column>
<dxi-tree-list-column dataField="HireDate">
<dxi-tree-list-validation-rule type="required"></dxi-tree-list-validation-rule>
</dxi-tree-list-column>
<dxi-tree-list-column dataField="State">
<dxi-tree-list-validation-rule type="required"></dxi-tree-list-validation-rule>
</dxi-tree-list-column>
<!-- ... -->
</dx-tree-list>Vue
<template>
<DxTreeList>
<DxEditing
mode="popup"
:allow-updating="true"
:allow-adding="true"
:allow-deleting="true"
/>
<DxColumn data-field="FullName">
<DxRequiredRule />
</DxColumn>
<DxColumn data-field="Position">
<DxRequiredRule />
</DxColumn>
<DxColumn data-field="BirthDate">
<DxRequiredRule />
</DxColumn>
<DxColumn data-field="HireDate">
<DxRequiredRule />
</DxColumn>
<DxColumn data-field="State">
<DxRequiredRule />
</DxColumn>
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxEditing, DxColumn, DxRequiredRule } from 'devextreme-vue/tree-list';
</script>React
import { TreeList, Editing, Column, RequiredRule } from 'devextreme-react/tree-list';
function App() {
return (
<TreeList>
<Editing
mode="popup"
allowUpdating={true}
allowDeleting={true}
allowAdding={true}
/>
<Column dataField="FullName">
<RequiredRule />
</Column>
<Column dataField="Position">
<RequiredRule />
</Column>
<Column dataField="BirthDate">
<RequiredRule />
</Column>
<Column dataField="HireDate">
<RequiredRule />
</Column>
<Column dataField="State">
<RequiredRule />
</Column>
{/* ... */}
</TreeList>
);
}The TreeList component does not support editing hierarchical data sources out of the box. To configure editing operations for hierarchical data sources, refer to the following example:
Select Records
Read Tutorial: TreeList - Selection
TreeList supports single- and multiple-row selection. To enable row selection, configure the selection.mode property.
Handle onSelectionChanged to obtain the selected records at runtime. This tutorial uses onSelectionChanged to display the selected employee names in an element outside the component:
jQuery
$("#tree-list").dxTreeList({
selection: { mode: "single" },
onSelectionChanged: function(e) {
e.component.byKey(e.currentSelectedRowKeys[0]).done(employee => {
if(employee) {
$("#selected-employee").text(`Selected employee: ${employee.FullName}`);
}
});
},
// ...
});
<html>
<!-- ... -->
<body class="dx-viewport">
<div id="app-container">
<div id="tree-list"></div>
<p id="selected-employee"></p>
</div>
</body>
</html>ASP.NET Core Controls
<div id="app-container">
@(Html.DevExtreme().TreeList<Employee>()
.Selection(s => s.Mode(SelectionMode.Single))
.OnSelectionChanged("handleSelectionChanged")
@* ... *@
)
<p id="selected-employee"></p>
</div>
<script>
function handleSelectionChanged(e) {
e.component.byKey(e.currentSelectedRowKeys[0]).done((employee) => {
if (employee) {
$('#selected-employee').text(`Selected employee: ${employee.FullName}`);
}
});
}
</script>Angular
<div id="app-container">
<dx-tree-list (onSelectionChanged)="selectEmployee($event)">
<dxo-tree-list-selection mode="single"></dxo-tree-list-selection>
<!-- ... -->
</dx-tree-list>
@if (selectedEmployee) {
<p id="selected-employee">
Selected employee: {{ selectedEmployee.FullName }}
</p>
}
</div>
import { Component } from '@angular/core';
import { DxTreeListTypes } from 'devextreme-angular/ui/tree-list';
import { Employee, EmployeesService } from './employees.service';
// ...
export class AppComponent {
selectedEmployee: Employee;
constructor(service: EmployeesService) {
this.selectEmployee = this.selectEmployee.bind(this);
}
selectEmployee(e: DxTreeListTypes.SelectionChangedEvent) {
e.component.byKey(e.currentSelectedRowKeys[0]).done(employee => {
if(employee) {
this.selectedEmployee = employee;
}
});
}
}Vue
<template>
<div id="app-container">
<DxTreeList @selection-changed="selectEmployee">
<DxSelection mode="single" />
<!-- ... -->
</DxTreeList>
<p id="selected-employee" v-if="selectedEmployee">
Selected employee: {{ selectedEmployee.FullName }}
</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DxTreeList, DxSelection, type TreeListTypes } from 'devextreme-vue/tree-list';
import { type Employee } from '../employees.service';
const selectedEmployee = ref<Employee | undefined>();
function selectEmployee(e: TreeListTypes.SelectionChangedEvent): void {
e.component.byKey(e.currentSelectedRowKeys[0]).done((employee: Employee) => {
if (employee) {
selectedEmployee.value = employee;
}
});
}
</script>React
import React, { useCallback, useState } from 'react';
import { TreeList, Selection, type TreeListTypes } from 'devextreme-react/tree-list';
import { type Employee } from './employees';
function SelectedEmployee(props) {
if(props.employee) {
return (
<p id="selected-employee">
Selected employee: {props.employee.FullName}
</p>
);
}
return null;
}
function App() {
const [selectedEmployee, setSelectedEmployee] = useState();
const selectEmployee = useCallback((e: TreeListTypes.SelectionChangedEvent): void => {
e.component.byKey(e.currentSelectedRowKeys[0]).then((employee: Employee) => {
setSelectedEmployee(employee);
}).catch(() => {});
}, []);
return (
<div className="App">
<TreeList onSelectionChanged={selectEmployee}>
<Selection mode="single" />
{/* ... */}
</TreeList>
<SelectedEmployee employee={selectedEmployee} />
</div>
);
}Customize the Toolbar
The TreeList includes an integrated toolbar that displays predefined and custom controls. To add or remove toolbar items, declare the toolbar.items[] array. Toolbar items within the UI preserve the order in which they are declared.
This tutorial illustrates how to add the following items to the toolbar:
Predefined controls
Declare a toolbar item element and specify the name and properties that you want to customize (see the "addRowButton" configuration in the code below). If a control does not need customization, include its name only. Ensure that items[] contain controls for all features that you enabled in your TreeList.DevExtreme components
Configure a DevExtreme component within a toolbar item element. In this tutorial, we extended the toolbar's item collection with a custom Button that expands or collapses all grid records.
jQuery
const treeList = $("#tree-list").dxTreeList({
autoExpandAll: true,
toolbar: {
items: [{
location: 'after',
widget: 'dxButton',
options: {
text: 'Collapse All',
width: 136,
onClick(e) {
const expanding = e.component.option('text') === 'Expand All';
treeList.option({
autoExpandAll: expanding,
expandedRowKeys: [],
});
e.component.option('text', expanding ? 'Collapse All' : 'Expand All');
},
},
}, {
name: 'addRowButton',
showText: 'always',
}, 'exportButton', 'columnChooserButton', 'searchPanel' ],
},
// ...
}).dxTreeList("instance");ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.ID("tree-list")
.AutoExpandAll(true)
.Toolbar(t => t.Items(items => {
items.Add()
.Location(ToolbarItemLocation.After)
.Widget(w => w.Button()
.Text("Collapse All")
.Width(136)
.OnClick("handleCollapseAllButtonClick")
);
items.Add().Name(TreeListToolbarItem.AddRowButton).ShowText(ToolbarItemShowTextMode.Always);
items.Add().Name(TreeListToolbarItem.ColumnChooserButton);
items.Add().Name(TreeListToolbarItem.SearchPanel);
}))
@* ... *@
)
<script>
function handleCollapseAllButtonClick(e) {
const expanding = e.component.option('text') === 'Expand All';
$('#tree-list').dxTreeList('instance').option({
autoExpandAll: expanding,
expandedRowKeys: [],
});
e.component.option('text', expanding ? 'Collapse All' : 'Expand All');
}
</script>Angular
<dx-tree-list
[autoExpandAll]="expanded"
[(expandedRowKeys)]="expandedRowKeys"
>
<dxo-tree-list-toolbar>
<dxi-tree-list-item location="after">
<dx-button
[text]="expanded ? 'Collapse All' : 'Expand All'"
[width]="136"
(onClick)="toggleExpansion()"
>
</dx-button>
</dxi-tree-list-item>
<dxi-tree-list-item
name="addRowButton"
showText="always"
></dxi-tree-list-item>
<dxi-tree-list-item name="exportButton"></dxi-tree-list-item>
<dxi-tree-list-item name="columnChooserButton"></dxi-tree-list-item>
<dxi-tree-list-item name="searchPanel"></dxi-tree-list-item>
</dxo-tree-list-toolbar>
<!-- ... -->
</dx-tree-list>
// ...
export class AppComponent {
expanded: boolean = true;
expandedRowKeys: number[] = [];
toggleExpansion(): void {
this.expanded = !this.expanded;
this.expandedRowKeys = [];
}
}
import {
// ...
DxButtonModule
} from 'devextreme-angular';
@NgModule({
// ...
imports: [
// ...
DxButtonModule
],
})
export class AppModule { }Vue
<template>
<DxTreeList
:auto-expand-all="expanded"
:expanded-row-keys="expandedRowKeys"
>
<DxToolbar>
<DxItem
location="after"
template="button-template"
/>
<DxItem
name="addRowButton"
show-text="always"
/>
<DxItem name="exportButton"/>
<DxItem name="columnChooserButton"/>
<DxItem name="searchPanel"/>
</DxToolbar>
<template #button-template>
<DxButton
:text="expanded ? 'Collapse All' : 'Expand All'"
:width="136"
@click="toggleExpansion"
/>
</template>
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DxTreeList, DxToolbar, DxItem } from 'devextreme-vue/tree-list';
import { DxButton } from 'devextreme-vue/button';
const expanded = ref<boolean>(true);
const expandedRowKeys = ref<number[]>([]);
const toggleExpansion = (): void => {
expanded.value = !expanded.value;
expandedRowKeys.value = [];
};
</script>React
import React, { useCallback, useState } from 'react';
import { TreeList, Toolbar, Item } from 'devextreme-react/tree-list';
import { Button } from 'devextreme-react/button';
function App() {
const [expanded, setExpanded] = useState(true);
const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]);
const toggleExpansion = useCallback(() => {
setExpanded((prevExpanded: boolean) => !prevExpanded);
setExpandedRowKeys([]);
}, []);
return (
<TreeList
autoExpandAll={expanded}
expandedRowKeys={expandedRowKeys}
>
<Toolbar>
<Item location="after">
<Button
text={expanded ? 'Collapse All' : 'Expand All'}
width={136}
onClick={toggleExpansion}
/>
</Item>
<Item name="addRowButton" showText="always" />
<Item name="exportButton" />
<Item name="columnChooserButton" />
<Item name="searchPanel" />
</Toolbar>
{/* ... */}
</TreeList>
);
}Enable Row Drag & Drop
TreeList allows users to drag and drop rows to reorder records or modify the node hierarchy. To configure this feature, specify the following rowDragging options:
jQuery
const treeList = $("#tree-list").dxTreeList({
rowDragging: {
allowDropInsideItem: true,
allowReordering: true,
onDragChange(e) {
const visibleRows = treeList.getVisibleRows();
const sourceNode = treeList.getNodeByKey(e.itemData.ID);
let targetNode = visibleRows[e.toIndex].node;
while (targetNode && targetNode.data) {
if (targetNode.data.ID === sourceNode.data.ID) {
e.cancel = true;
break;
}
targetNode = targetNode.parent;
}
},
onReorder(e) {
const visibleRows = e.component.getVisibleRows();
const sourceData = e.itemData;
const targetData = visibleRows[e.toIndex].data;
if (e.dropInsideItem) {
e.itemData.HeadID = targetData.ID;
} else {
const sourceIndex = employees.indexOf(sourceData);
let targetIndex = employees.indexOf(targetData);
if (sourceData.HeadID !== targetData.HeadID) {
sourceData.HeadID = targetData.HeadID;
if (e.toIndex > e.fromIndex) {
targetIndex += 1;
}
}
employees.splice(sourceIndex, 1);
employees.splice(targetIndex, 0, sourceData);
}
e.component.refresh();
},
},
// ...
}).dxTreeList("instance");ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.RowDragging(r => r
.AllowDropInsideItem(true)
.AllowReordering(true)
.OnDragChange("handleDragChange")
.OnReorder("handleReorder")
)
@* ... *@
)
<script>
function handleDragChange(e) {
const targetRow = e.component.getVisibleRows()[e.toIndex];
if (!targetRow) {
return;
}
const sourceNode = e.component.getNodeByKey(e.itemData.ID);
let targetNode = targetRow.node;
while (targetNode && targetNode.data) {
if (targetNode.data.ID === sourceNode.data.ID) {
e.cancel = true;
break;
}
targetNode = targetNode.parent;
}
}
function handleReorder(e) {
const treeList = e.component;
const store = treeList.getDataSource().store();
const visibleRows = treeList.getVisibleRows();
const sourceData = e.itemData;
const targetData = visibleRows[e.toIndex].data;
const newOrderIndex = targetData.OrderIndex;
const d = $.Deferred();
if (e.dropInsideItem) {
store.update(sourceData.ID, { HeadID: targetData.ID }).then(function() {
treeList.refresh().then(d.resolve, d.reject);
}, d.reject);
} else {
store.update(sourceData.ID, {
OrderIndex: newOrderIndex,
HeadID: targetData.HeadID,
}).then(function() {
treeList.refresh().then(d.resolve, d.reject);
}, d.reject);
}
e.promise = d.promise();
}
</script>
using System.Linq;
using System.Text.Json;
using ASP_NET_Core.Models;
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using Microsoft.AspNetCore.Mvc;
namespace ASP_NET_Core.Controllers;
public class EmployeeDataController : Controller {
[HttpPut]
public IActionResult Update(int key, string values) {
var employee = EmployeeData.Employees.FirstOrDefault(e => e.ID == key);
if(employee == null)
return NotFound();
var oldOrderIndex = employee.OrderIndex;
PopulateEmployee(employee, values);
var newOrderIndex = employee.OrderIndex;
if (oldOrderIndex != newOrderIndex) {
employee.OrderIndex = oldOrderIndex;
var sortedEmployees = EmployeeData.Employees
.OrderBy(e => e.OrderIndex)
.ToList();
if (oldOrderIndex < newOrderIndex) {
for(var i = oldOrderIndex + 1; i <= newOrderIndex; i++) {
sortedEmployees[i].OrderIndex--;
}
} else {
for(var i = newOrderIndex; i < oldOrderIndex; i++) {
sortedEmployees[i].OrderIndex++;
}
}
employee.OrderIndex = newOrderIndex;
}
return Ok(employee);
}
}Angular
<dx-tree-list>
<dxo-tree-list-row-dragging
[allowDropInsideItem]="true"
[allowReordering]="true"
[onDragChange]="onDragChange"
[onReorder]="onReorder"
></dxo-tree-list-row-dragging>
<!-- ... -->
</dx-tree-list>
import { type DxTreeListTypes } from 'devextreme-angular/ui/tree-list';
import { EmployeesService, type Employee } from './employees.service';
// ...
export class AppComponent {
employees: Employee[] = [];
onDragChange(e: DxTreeListTypes.RowDraggingChangeEvent): void {
const visibleRows = e.component.getVisibleRows();
const sourceNode = e.component.getNodeByKey(e.itemData.ID);
let targetNode = visibleRows[e.toIndex].node;
while (targetNode?.data) {
if (targetNode.data.ID === sourceNode.data.ID) {
e.cancel = true;
break;
}
const parentNode = targetNode.parent;
if (!parentNode) {
break;
}
targetNode = parentNode;
}
}
onReorder(e: DxTreeListTypes.RowDraggingReorderEvent): void {
const visibleRows = e.component.getVisibleRows();
const sourceData = e.itemData;
const targetData = visibleRows[e.toIndex].data;
if (e.dropInsideItem) {
e.itemData.HeadID = targetData.ID;
e.component.refresh().catch(() => {
// Handle error silently
});
} else {
let targetIndex = this.employees.indexOf(targetData);
if (sourceData.HeadID !== targetData.HeadID) {
sourceData.HeadID = targetData.HeadID;
if (e.toIndex > e.fromIndex) {
targetIndex += 1;
}
}
this.employeesService.reorderEmployees(sourceData, targetIndex);
this.employees = this.employeesService.getEmployees();
}
}
}
// ...
export class EmployeesService {
reorderEmployees(sourceData: Employee, targetIndex: number): void {
const sourceIndex = this.employees.indexOf(sourceData);
this.employees.splice(sourceIndex, 1);
this.employees.splice(targetIndex, 0, sourceData);
}
}Vue
<template>
<DxTreeList>
<DxRowDragging
:allow-drop-inside-item="true"
:allow-reordering="true"
:on-drag-change="onDragChange"
:on-reorder="onReorder"
/>
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxRowDragging, type DxTreeListTypes } from 'devextreme-vue/tree-list';
const onDragChange = (e: DxTreeListTypes.RowDraggingChangeEvent): void => {
const visibleRows = e.component.getVisibleRows();
const sourceNode = e.component.getNodeByKey(e.itemData.ID);
let targetNode = visibleRows[e.toIndex].node;
while (targetNode?.data) {
if (targetNode.data.ID === sourceNode.data.ID) {
e.cancel = true;
break;
}
if (!targetNode.parent) {
break;
}
targetNode = targetNode.parent;
}
};
const onReorder = (e: DxTreeListTypes.RowDraggingReorderEvent): void => {
const visibleRows = e.component.getVisibleRows();
const sourceData = e.itemData;
const targetData = visibleRows[e.toIndex].data;
if (e.dropInsideItem) {
e.itemData.HeadID = targetData.ID;
employeesService.updateEmployee(e.itemData);
e.component.refresh().catch(() => {
// Handle error silently
});
} else {
let targetIndex = employees.value.indexOf(targetData);
if (sourceData.HeadID !== targetData.HeadID) {
sourceData.HeadID = targetData.HeadID;
if (e.toIndex > e.fromIndex) {
targetIndex += 1;
}
}
employeesService.reorderEmployees(sourceData, targetIndex);
employees.value = employeesService.getEmployees();
}
e.component.refresh();
};
</script>
let employeeData = [...employees];
export const employeesService = {
reorderEmployees: (sourceData: Employee, targetIndex: number): void => {
const sourceIndex = employeeData.indexOf(sourceData);
employeeData.splice(sourceIndex, 1);
employeeData.splice(targetIndex, 0, sourceData);
},
// ...
};React
import React, { useCallback, useState } from 'react';
import { TreeList, RowDragging, type TreeListTypes } from 'devextreme-react/tree-list';
import { employeesService, type Employee } from './employeesService';
function App() {
const [currentEmployees, setCurrentEmployees] = useState<Employee[]>(employeesService.getEmployees());
const onDragChange = useCallback((e: any) => {
const visibleRows = e.component.getVisibleRows();
const sourceNode = e.component.getNodeByKey(e.itemData.ID);
let targetNode: TreeListTypes.Node<Employee, number> | undefined = visibleRows[e.toIndex].node;
while (targetNode?.data) {
if (targetNode.data.ID === sourceNode.data.ID) {
e.cancel = true;
break;
}
targetNode = targetNode.parent;
}
}, []);
const onReorder = useCallback((e: any) => {
const visibleRows = e.component.getVisibleRows();
const sourceData = e.itemData;
const targetData = visibleRows[e.toIndex].data;
if (e.dropInsideItem) {
const updatedSourceData = { ...sourceData, HeadID: targetData.ID };
employeesService.updateEmployee(updatedSourceData);
} else {
let targetIndex = currentEmployees.indexOf(targetData);
if (sourceData.HeadID !== targetData.HeadID) {
sourceData.HeadID = targetData.HeadID;
if (e.toIndex > e.fromIndex) {
targetIndex += 1;
}
}
employeesService.reorderEmployees(sourceData, targetIndex);
}
setCurrentEmployees(employeesService.getEmployees());
e.component.refresh();
}, [currentEmployees]);
return (
<TreeList>
<RowDragging
onDragChange={onDragChange}
onReorder={onReorder}
allowDropInsideItem={true}
allowReordering={true}
/>
{/* ... */}
</TreeList>
);
}
let employeeData = [...employees];
export const employeesService = {
reorderEmployees: (sourceData: Employee, targetIndex: number): void => {
const sourceIndex = employeeData.indexOf(sourceData);
employeeData.splice(sourceIndex, 1);
employeeData.splice(targetIndex, 0, sourceData);
},
// ...
};Enable Pagination
Read Tutorial: TreeList - Paging
TreeList can load data in pages. To configure pagination, set paging.enabled to true and configure the pageSize property:
jQuery
$("#tree-list").dxTreeList({
paging: {
enabled: true,
pageSize: 12,
},
// ...
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList<Employee>()
.Paging(p => p
.Enabled(true)
.PageSize(12)
)
@* ... *@
)Angular
<dx-tree-list>
<dxo-tree-list-paging
[enabled]="true"
[pageSize]="12"
></dxo-tree-list-paging>
<!-- ... -->
</dx-tree-list>Vue
<template>
<DxTreeList>
<DxPaging
:enabled="true"
:page-size="12"
/>
<!-- ... -->
</DxTreeList>
</template>
<script setup lang="ts">
import { DxTreeList, DxPaging } from 'devextreme-vue/tree-list';
</script>React
import { TreeList, Paging } from 'devextreme-react/tree-list';
function App() {
return (
<TreeList>
<Paging
enabled={true}
defaultPageSize={12}
/>
{/* ... */}
</TreeList>
);
}For further information on the TreeList component, refer to the following resources: