Search results

Remote Data in JavaScript (ES5) TreeGrid control

25 Jan 2023 / 22 minutes to read

To bind remote data to TreeGrid component, assign service data as an instance of DataManager to the dataSource property. To interact with remote data source, provide the endpoint url and define the hasChildMapping property of treegrid.

The hasChildMapping property maps the field name in data source, that denotes whether current record holds any child records. This is useful internally to show expand icon while binding child data on demand.

The TreeGrid provides Load on Demand support for rendering remote data. The Load on demand is considered in TreeGrid for the following actions.

  • Expanding root nodes.
  • Navigating pages, with paging enabled in TreeGrid.

When load on demand is enabled, all the root nodes are rendered in collapsed state at initial load.

When load on demand support is enabled in TreeGrid with paging, the current or active page’s root node alone will be rendered in collapsed state. On expanding the root node, the child nodes will be loaded from the remote server.

When a root node is expanded, its child nodes are rendered and are cached locally, such that on consecutive expand/collapse actions on root node, the child nodes are loaded from the cache instead from the remote server.

Similarly, if the user navigates to a new page, the root nodes of that specific page, will be rendered with request to the remote server.

Remote Data Binding supports only Self-Referential Data and by default the pageSizeMode for Remote Data is Root mode. i.e only root node’s count will be shown in pager while using Remote Data

Source
Preview
index.js
index.html
Copied to clipboard
ej.treegrid.TreeGrid.Inject(ej.treegrid.Page);

var data = new ej.data.DataManager({
        url: 'https://ej2services.syncfusion.com/production/web-services/api/SelfReferenceData',
        adaptor: new ej.data.WebApiAdaptor(),
        crossDomain: true
});
    
var treeGridObj = new ej.treegrid.TreeGrid({
    dataSource: data,
    hasChildMapping: 'isParent',
    idMapping: 'TaskID',
    parentIdMapping: 'ParentItem',
    height: 260,
    treeColumnIndex: 1,
    allowPaging: true,
    columns: [
        { field: 'TaskID', headerText: 'Task ID', width: 80, textAlign: 'Right' },
        { field: 'TaskName', headerText: 'Task Name', width: 200, textAlign: 'Left' },
        { field: 'StartDate', headerText: 'Start Date', width: 90, textAlign: 'Right', type: 'date', format: 'yMd' },
        { field: 'Duration', headerText: 'Duration', width: 90, textAlign: 'Right' },
    ]
});

treeGridObj.appendTo('#TreeGrid');
Copied to clipboard
<!DOCTYPE html><html lang="en"><head>
            
    <title>EJ2 Grid</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Typescript Grid Control">
    <meta name="author" content="Syncfusion">
    <link href="index.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-base/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-grids/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-treegrid/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-buttons/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-popups/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-navigations/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-dropdowns/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-lists/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-inputs/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-calendars/styles/material.css" rel="stylesheet">
    
    
    
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-splitbuttons/styles/material.css" rel="stylesheet">
    
    
    
    
<script src="https://cdn.syncfusion.com/ej2/20.4.38/dist/ej2.min.js" type="text/javascript"></script>
<script src="es5-datasource.js" type="text/javascript"></script>
</head>
<body>
    
    <div id="container">
        <div id="TreeGrid"></div>        
    </div>

<script>
var ele = document.getElementById('container');
if(ele) {
    ele.style.visibility = "visible";
 }   
        </script>
<script src="index.js" type="text/javascript"></script>
</body></html>

By default, DataManager uses ODataAdaptor for remote data-binding. Based on the RESTful web services, set the corresponding adaptor to DataManager. Refer here for more details. Filtering and searching server-side data operations are not supported in load on demand

LoadChildOnDemand

While binding remote data to Tree Grid component, by default Tree Grid renders parent rows in collapsed state. Tree Grid provides option to load the child records also during the initial rendering itself for remote data binding by setting loadChildOnDemand as true.

When loadChildOnDemand is enabled parent records are rendered in expanded state.

The following code example describes the behavior of the loadChildOnDemand feature of Tree Grid.

Copied to clipboard
import { TreeGrid, Page }from '@syncfusion/ej2-treegrid';
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';

TreeGrid.Inject(Page);

let data: DataManager = new DataManager({
url: "Home/DataSource",
updateUrl: "Home/Update",
insertUrl: "Home/Insert",
removeUrl: "Home/Delete",
batchUrl: "Home/Remove",
adaptor: new UrlAdaptor
});


let treegrid: TreeGrid = new TreeGrid({
dataSource: data,
idMapping: 'TaskID',
parentIdMapping: 'ParentItem',
hasChildMapping: 'isParent',
loadChildOnDemand: true,
height: 260,
allowPaging: true,
treeColumnIndex: 1,
    columns: [
    { field: 'TaskID', headerText: 'Task ID', textAlign: 'Right', width: 90 },
    { field: 'TaskName', headerText: 'Task Name', width: 180 },
    { field: 'StartDate', headerText: 'Start Date', textAlign: 'Right', width: 90, format: { skeleton: 'yMd', type: 'date' } },
    { field: 'Duration', headerText: 'Duration', width: 80, textAlign: 'Right' }
]
});
treegrid.appendTo('#TreeGrid');

Also while using loadChildOnDemand we need to handle the child records on server end and it is applicable to CRUD operations also.

The following code example describes handling of child records at server end.

Copied to clipboard
public ActionResult UrlDatasource(DataManagerRequest dm)
{
if (TreeData.tree.Count == 0)
      TreeData.GetTree();
IEnumerable DataSource = TreeData.tree;

DataOperations operation = new DataOperations();
if (dm.Where != null && dm.Where.Count > 0)
{
    DataSource = operation.PerformFiltering(DataSource, dm.Where, "and");   //perform filtering  
}
if (dm.Sorted != null && dm.Sorted.Count > 0)
{
    DataSource = operation.PerformSorting(DataSource, dm.Sorted);  //perform sorting
}
var count = DataSource.ToList<TreeData>().Count();
if (dm.Skip != 0)
{
    DataSource = operation.PerformSkip(DataSource, dm.Skip);   //Paging
}
if (dm.Take != 0)
{
    DataSource = operation.PerformTake(DataSource, dm.Take);
}
if (dm.Where != null)
{
    DataSource = CollectChildRecords(DataSource, dm);  // method to collect child records
}

return dm.RequiresCounts ? Json(new { result = DataSource, count = count }) : Json(DataSource);
}

 public IEnumerable CollectChildRecords(IEnumerable datasource, DataManagerRequest dm)
 {
 DataOperations operation = new DataOperations();
 IEnumerable DataSource = TreeData.tree;  // use the total DataSource here
 string IdMapping = "TaskID";  // define your IdMapping field name here
 int[] TaskIds = new int[0];
 foreach (var rec in datasource)
 {
    int taskid = (int)rec.GetType().GetProperty(IdMapping).GetValue(rec);
    TaskIds = TaskIds.Concat(new int[] { taskid }).ToArray();     //get the Parentrecord Ids based on IdMapping Field  
 }
IEnumerable ChildRecords = null;
 foreach (int id in TaskIds)
 {
    dm.Where[0].value = id;
    IEnumerable records = operation.PerformFiltering(DataSource, dm.Where, dm.Where[0].Operator);     //perform filtering to collect the childrecords based on Ids
    ChildRecords = ChildRecords == null || (ChildRecords.AsQueryable().Count() == 0) ? records : ((IEnumerable<object>)ChildRecords).Concat((IEnumerable<object>)records);  //concate the childrecords with dataSource
 }
 if (ChildRecords != null)
 {
    ChildRecords = CollectChildRecords(ChildRecords, dm);  // repeat the operation for inner level child
    if (dm.Sorted != null && dm.Sorted.Count > 0) // perform Sorting
    {
        ChildRecords = operation.PerformSorting(ChildRecords, dm.Sorted);
    }
    datasource = ((IEnumerable<object>)datasource).Concat((IEnumerable<object>)ChildRecords);    //concate the childrecords with dataSource  
 }
return datasource;
 }

Offline mode

On remote data binding, all treegrid actions such as paging, loading child on-demand, will be processed on server-side. To avoid postback, set the treegrid to load all data on initialization and make the actions process in client-side. To enable this behavior, use the offline property of DataManager.

Source
Preview
index.js
index.html
Copied to clipboard
ej.treegrid.TreeGrid.Inject(ej.treegrid.Page);

var data = new ej.data.DataManager({
        url: 'https://ej2services.syncfusion.com/production/web-services/api/SelfReferenceData',
        adaptor: new ej.data.WebApiAdaptor(),
        crossDomain: true,
        offline: true
});

var treeGridObj = new ej.treegrid.TreeGrid({
    dataSource: data,
    idMapping: 'TaskID',
    parentIdMapping: 'ParentItem',
    height: 260,
    treeColumnIndex: 1,
    allowPaging: true,
    columns: [
        { field: 'TaskID', headerText: 'Task ID', width: 80, textAlign: 'Right' },
        { field: 'TaskName', headerText: 'Task Name', width: 200, textAlign: 'Left' },
        { field: 'StartDate', headerText: 'Start Date', width: 90, textAlign: 'Right', type: 'date', format: 'yMd' },
        { field: 'Duration', headerText: 'Duration', width: 90, textAlign: 'Right' },
    ]
});
    
treeGridObj.appendTo('#TreeGrid');
Copied to clipboard
<!DOCTYPE html><html lang="en"><head>
            
    <title>EJ2 Grid</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Typescript Grid Control">
    <meta name="author" content="Syncfusion">
    <link href="index.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-base/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-grids/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-treegrid/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-buttons/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-popups/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-navigations/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-dropdowns/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-lists/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-inputs/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-calendars/styles/material.css" rel="stylesheet">
    
    
    
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-splitbuttons/styles/material.css" rel="stylesheet">
    
    
    
    
<script src="https://cdn.syncfusion.com/ej2/20.4.38/dist/ej2.min.js" type="text/javascript"></script>
<script src="es5-datasource.js" type="text/javascript"></script>
</head>
<body>
    
    <div id="container">
        <div id="TreeGrid"></div>        
    </div>

<script>
var ele = document.getElementById('container');
if(ele) {
    ele.style.visibility = "visible";
 }   
        </script>
<script src="index.js" type="text/javascript"></script>
</body></html>

Custom adaptor

You can create your own adaptor by extending the built-in adaptors. The following demonstrates custom adaptor approach and how to add a serial number for the records by overriding the built-in response processing using the processResponse method of the WebApiAdaptor.

Source
Preview
index.js
index.html
Copied to clipboard
ej.treegrid.TreeGrid.Inject(ej.treegrid.Page);

class SerialNoAdaptor extends ej.data.WebApiAdaptor {
    processResponse(){
        var i = 0;
        // calling base class processResponse function
        var original = super.processResponse.apply(this, arguments);
        // adding serial number
        original.forEach((item) => item['Sno'] = ++i);
        return original;
    }
}

var data = new ej.data.DataManager({
        url: 'https://ej2services.syncfusion.com/production/web-services/api/SelfReferenceData',
        adaptor: new SerialNoAdaptor,
        crossDomain: true,
        offline: true
});

var treeGridObj = new ej.treegrid.TreeGrid({
    dataSource: data,
    idMapping: 'TaskID',
    parentIdMapping: 'ParentItem',
    height: 260,
    treeColumnIndex: 1,
    allowPaging: true,
    columns: [
        { field: 'Sno', width: 120, headerText: 'SNO', textAlign: 'Right' },
        { field: 'TaskName', headerText: 'Task Name', width: 200, textAlign: 'Left' },
        { field: 'StartDate', headerText: 'Start Date', width: 90, textAlign: 'Right', type: 'date', format: 'yMd' },
        { field: 'Duration', headerText: 'Duration', width: 90, textAlign: 'Right' },
    ]
});
    
treeGridObj.appendTo('#TreeGrid');
Copied to clipboard
<!DOCTYPE html><html lang="en"><head>
            
    <title>EJ2 Grid</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Typescript Grid Control">
    <meta name="author" content="Syncfusion">
    <link href="index.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-base/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-grids/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-treegrid/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-buttons/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-popups/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-navigations/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-dropdowns/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-lists/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-inputs/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-calendars/styles/material.css" rel="stylesheet">
    
    
    
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-splitbuttons/styles/material.css" rel="stylesheet">
    
    
    
    
<script src="https://cdn.syncfusion.com/ej2/20.4.38/dist/ej2.min.js" type="text/javascript"></script>
<script src="es5-datasource.js" type="text/javascript"></script>
</head>
<body>
    
    <div id="container">
        <div id="TreeGrid"></div>        
    </div>

<script>
var ele = document.getElementById('container');
if(ele) {
    ele.style.visibility = "visible";
 }   
        </script>
<script src="index.js" type="text/javascript"></script>
</body></html>

Sending additional parameters to the server

To add a custom parameter to the data request, use the addParams method of Query class. Assign the Query object with additional parameters to the treegrid query property.

Source
Preview
index.js
index.html
Copied to clipboard
ej.treegrid.TreeGrid.Inject(ej.treegrid.Page);

var data = new ej.data.DataManager({
        url: 'https://ej2services.syncfusion.com/production/web-services/api/SelfReferenceData',
        adaptor: new ej.data.WebApiAdaptor(),
        crossDomain: true
});
   
var treeGridObj = new ej.treegrid.TreeGrid({
    dataSource: data,
    hasChildMapping: 'isParent',
    idMapping: 'TaskID',
    parentIdMapping: 'ParentItem',
    height: 260,
    query: new ej.data.Query().addParams('ej2treegrid', 'true'),
    treeColumnIndex: 1,
    allowPaging: true,
    columns: [
        { field: 'TaskID', headerText: 'Task ID', width: 80, textAlign: 'Right' },
        { field: 'TaskName', headerText: 'Task Name', width: 200, textAlign: 'Left' },
        { field: 'StartDate', headerText: 'Start Date', width: 90, textAlign: 'Right', type: 'date', format: 'yMd' },
        { field: 'Duration', headerText: 'Duration', width: 90, textAlign: 'Right' },
    ]
});

    treeGridObj.appendTo('#TreeGrid');
Copied to clipboard
<!DOCTYPE html><html lang="en"><head>
            
    <title>EJ2 Grid</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Typescript Grid Control">
    <meta name="author" content="Syncfusion">
    <link href="index.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-base/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-grids/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-treegrid/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-buttons/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-popups/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-navigations/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-dropdowns/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-lists/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-inputs/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-calendars/styles/material.css" rel="stylesheet">
    
    
    
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-splitbuttons/styles/material.css" rel="stylesheet">
    
    
    
    
<script src="https://cdn.syncfusion.com/ej2/20.4.38/dist/ej2.min.js" type="text/javascript"></script>
<script src="es5-datasource.js" type="text/javascript"></script>
</head>
<body>
    
    <div id="container">
        <div id="TreeGrid"></div>        
    </div>

<script>
var ele = document.getElementById('container');
if(ele) {
    ele.style.visibility = "visible";
 }   
        </script>
<script src="index.js" type="text/javascript"></script>
</body></html>

Handling HTTP error

During server interaction from the treegrid, some server-side exceptions may occur, and you can acquire those error messages or exception details in client-side using the actionFailure event.

The argument passed to the actionFailure event contains the error details returned from the server.

Source
Preview
index.js
index.html
Copied to clipboard
var data = new ej.data.DataManager({
       url: 'http://some.com/invalidUrl'
    });

var treeGridObj = new ej.treegrid.TreeGrid({
    dataSource: data,
    idMapping: 'TaskID',
    parentIdMapping: 'ParentItem',
    treeColumnIndex: 1,
    actionFailure: (e) => {
      var span = document.createElement('span');
      treeGridObj.element.parentNode.insertBefore(span, treeGridObj.element);
      span.style.color = "#FF0000"
      span.innerHTML = "Server exception: 404 Not found";
    },
    columns: [
        { field: 'TaskID', headerText: 'Task ID', width: 80, textAlign: 'Right' },
        { field: 'TaskName', headerText: 'Task Name', width: 200, textAlign: 'Left' },
        { field: 'StartDate', headerText: 'Start Date', width: 90, textAlign: 'Right', type: 'date', format: 'yMd' },
        { field: 'Duration', headerText: 'Duration', width: 90, textAlign: 'Right' },
    ]
});
    
treeGridObj.appendTo('#TreeGrid');
Copied to clipboard
<!DOCTYPE html><html lang="en"><head>
            
    <title>EJ2 Grid</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="Typescript Grid Control">
    <meta name="author" content="Syncfusion">
    <link href="index.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-base/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-grids/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-treegrid/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-buttons/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-popups/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-navigations/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-dropdowns/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-lists/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-inputs/styles/material.css" rel="stylesheet">
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-calendars/styles/material.css" rel="stylesheet">
    
    
    
    <link href="//cdn.syncfusion.com/ej2/20.4.38/ej2-splitbuttons/styles/material.css" rel="stylesheet">
    
    
    
    
<script src="https://cdn.syncfusion.com/ej2/20.4.38/dist/ej2.min.js" type="text/javascript"></script>
<script src="es5-datasource.js" type="text/javascript"></script>
</head>
<body>
    
    <div id="container">
        <div id="TreeGrid"></div>        
    </div>

<script>
var ele = document.getElementById('container');
if(ele) {
    ele.style.visibility = "visible";
 }   
        </script>
<script src="index.js" type="text/javascript"></script>
</body></html>

The actionFailure event will be triggered not only for the server errors, but also when there is an exception while processing the treegrid actions.

Load on demand with virtualization

While binding remote data to Tree Grid component, by default Tree Grid renders parent rows in collapsed state. When expanding the root node, the child nodes will be loaded from the remote server.

When using virtualization with remote data binding, it helps you to improve the tree grid performance while loading a large set of data by setting enableVirtualization as true. The Tree Grid UI virtualization allows it to render only rows and columns visible within the view-port without buffering the entire datasource.

hasChildMapping property maps the field name in data source, that denotes whether current record holds any child records. This is useful internally to show expand icon while binding child data on demand.

Copied to clipboard
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { TreeGridComponent, VirtualScroll, Sort, Filter, Edit, Toolbar } from '@syncfusion/ej2-treegrid';
import './App.css';

TreeGrid.Inject(Edit, VirtualScroll, Sort, Filter, Toolbar);

let dataManager: DataManager = new DataManager({
adaptor: new UrlAdaptor,
insertUrl: "Home/Insert",
removeUrl: "Home/Delete",
updateUrl: "Home/Update",
url: "Home/DataSource",
});

let treegrid: TreeGrid = new TreeGrid({
dataSource: dataManager,
idMapping: 'TaskID',
parentIdMapping: 'ParentValue',
hasChildMapping: 'isParent',
loadChildOnDemand: true,
expandStateMapping: 'IsExpanded',
enableVirtualization: true,
height: 400,
editSettings: { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Row', newRowPosition: 'Below' },
toolbar: ['Add', 'Edit', 'Delete', 'Update', 'Cancel'],
pageSettings: {pageSize: 30},
allowPaging: true,
allowFiltering: true,
allowSorting: true,
treeColumnIndex: 1,
    columns: [
    { field: 'TaskID', headerText: 'Task ID', textAlign: 'Right', width: 90 },
    { field: 'TaskName', headerText: 'Task Name', width: 180 },
    { field: 'Duration', headerText: 'Duration', width: 80, textAlign: 'Right' }
]
});
treegrid.appendTo('#TreeGrid');

The following code example describes handling of Load on demand at server end.

Copied to clipboard
public ActionResult lazyLoading()
{
TreeData.tree = new List<TreeData>();
return View();
}
public ActionResult UrlDatasource(DataManagerRequest dm)
{
List<TreeData> data = new List<TreeData>();
data = TreeData.GetTree();
DataOperations operation = new DataOperations();
IEnumerable<TreeData> DataSource = data;
List<TreeData> ExpandedParentRecords = new List<TreeData>();
if (dm.Expand != null && dm.Expand[0] == "ExpandingAction") // setting the ExpandStateMapping property whether is true or false
{
    var val = TreeData.GetTree().Where(ds => ds.TaskID == int.Parse(dm.Expand[1])).FirstOrDefault();
    val.IsExpanded = true;
}
else if (dm.Expand != null && dm.Expand[0] == "CollapsingAction")
{
    var val = TreeData.GetTree().Where(ds => ds.TaskID == int.Parse(dm.Expand[1])).FirstOrDefault();
    val.IsExpanded = false;
}
if (!(dm.Where != null && dm.Where.Count > 1))
{
    data = data.Where(p => p.ParentValue == null).ToList();
}
DataSource = data;
if (dm.Search != null && dm.Search.Count > 0) // Searching
{
    DataSource = operation.PerformSearching(DataSource, dm.Search);
}
if (dm.Sorted != null && dm.Sorted.Count > 0 && dm.Sorted[0].Name != null) // Sorting
{
    DataSource = operation.PerformSorting(DataSource, dm.Sorted);
}
if (dm.Where != null && dm.Where.Count > 1) //filtering
{
    DataSource = operation.PerformFiltering(DataSource, dm.Where, "and");
}
data = new List<TreeData>();
foreach (var rec in DataSource)
{
    if (rec.IsExpanded)
    {
        ExpandedParentRecords.Add(rec as TreeData); // saving the expanded parent records
    }
    data.Add(rec as TreeData);
}
var GroupData = TreeData.GetTree().ToList().GroupBy(rec => rec.ParentValue)
                    .Where(g => g.Key != null).ToDictionary(g => g.Key?.ToString(), g => g.ToList());
if (ExpandedParentRecords.Count > 0)
{
    foreach (var Record in ExpandedParentRecords.ToList())
    {
        var ChildGroup = GroupData[Record.TaskID.ToString()];
        if (dm.Sorted != null && dm.Sorted.Count > 0 && dm.Sorted[0].Name != null) // sorting the child records
        {
            IEnumerable ChildSort = ChildGroup;
            ChildSort = operation.PerformSorting(ChildSort, dm.Sorted);
            ChildGroup = new List<TreeData>();
            foreach (var rec in ChildSort)
            {
                ChildGroup.Add(rec as TreeData);
            }
        }
        if (dm.Search != null && dm.Search.Count > 0) // searching the child records
        {
            IEnumerable ChildSearch = ChildGroup;
            ChildSearch = operation.PerformSearching(ChildSearch, dm.Search);
            ChildGroup = new List<TreeData>();
            foreach (var rec in ChildSearch)
            {
                ChildGroup.Add(rec as TreeData);
            }
        }
        AppendChildren(dm, ChildGroup, Record, GroupData, data);
    }
}
DataSource = data;
if (dm.Expand != null && dm.Expand[0] == "CollapsingAction") // setting the skip index based on collapsed parent
{
    string IdMapping = "TaskID";
    List<WhereFilter> CollapseFilter = new List<WhereFilter>();
    CollapseFilter.Add(new WhereFilter() { Field = IdMapping, value = dm.Where[0].value, Operator = dm.Where[0].Operator });
    var CollapsedParentRecord = operation.PerformFiltering(DataSource, CollapseFilter, "and");
    var index = data.Cast<object>().ToList().IndexOf(CollapsedParentRecord.Cast<object>().ToList()[0]);
    dm.Skip = index;
}
else if (dm.Expand != null && dm.Expand[0] == "ExpandingAction") // setting the skip index based on expanded parent
{
    string IdMapping = "TaskID";
    List<WhereFilter> ExpandFilter = new List<WhereFilter>();
    ExpandFilter.Add(new WhereFilter() { Field = IdMapping, value = dm.Where[0].value, Operator = dm.Where[0].Operator });
    var ExpandedParentRecord = operation.PerformFiltering(DataSource, ExpandFilter, "and");
    var index = data.Cast<object>().ToList().IndexOf(ExpandedParentRecord.Cast<object>().ToList()[0]);
    dm.Skip = index;
}
int count = data.Count;
DataSource = data;
if (dm.Skip != 0)
{
    DataSource = operation.PerformSkip(DataSource, dm.Skip);   //Paging
}
if (dm.Take != 0)
{
    DataSource = operation.PerformTake(DataSource, dm.Take);
}
return dm.RequiresCounts ? Json(new { result = DataSource, count = count }) : Json(DataSource);

}

private void AppendChildren(DataManagerRequest dm, List<TreeData> ChildRecords, TreeData ParentValue, Dictionary<string, List<TreeData>> GroupData, List<TreeData> data) // Getting child records for the respective parent
{
string TaskId = ParentValue.TaskID.ToString();
var index = data.IndexOf(ParentValue);
DataOperations operation = new DataOperations();
foreach (var Child in ChildRecords)
{
    if (ParentValue.IsExpanded)
    {
        string ParentId = Child.ParentValue.ToString();
        if (TaskId == ParentId)
        {
            ((IList)data).Insert(++index, Child);
            if (GroupData.ContainsKey(Child.TaskID.ToString()))
            {
                var DeepChildRecords = GroupData[Child.TaskID.ToString()];
                if (DeepChildRecords?.Count > 0)
                {
                    if (dm.Sorted != null && dm.Sorted.Count > 0 && dm.Sorted[0].Name != null) // sorting the child records
                    {
                        IEnumerable ChildSort = DeepChildRecords;
                        ChildSort = operation.PerformSorting(ChildSort, dm.Sorted);
                        DeepChildRecords = new List<TreeData>();
                        foreach (var rec in ChildSort)
                        {
                            DeepChildRecords.Add(rec as TreeData);
                        }
                    }
                    if (dm.Search != null && dm.Search.Count > 0) // searching the child records
                    {
                        IEnumerable ChildSearch = DeepChildRecords;
                        ChildSearch = operation.PerformSearching(ChildSearch, dm.Search);
                        DeepChildRecords = new List<TreeData>();
                        foreach (var rec in ChildSearch)
                        {
                            DeepChildRecords.Add(rec as TreeData);
                        }
                    }
                    AppendChildren(dm, DeepChildRecords, Child, GroupData, data);
                    if (Child.IsExpanded)
                    {
                        index += DeepChildRecords.Count;
                    }
                }
            }
            else
            {
                Child.isParent = false;
            }
        }
    }
}

}

public ActionResult Update(CRUDModel<TreeData> value)
{
List<TreeData> data = new List<TreeData>();
data = TreeData.GetTree();
var val = data.Where(ds => ds.TaskID == value.Value.TaskID).FirstOrDefault();
val.TaskName = value.Value.TaskName;
val.Duration = value.Value.Duration;
return Json(val);
}

public ActionResult Insert(CRUDModel<TreeData> value)
{
var c = 0;
for (; c < TreeData.GetTree().Count; c++)
{
    if (TreeData.GetTree()[c].TaskID == value.RelationalKey)
    {
        if (TreeData.GetTree()[c].isParent == null)
        {
            TreeData.GetTree()[c].isParent = true;
        }
        break;
    }
}
c += FindChildRecords(value.RelationalKey);
TreeData.GetTree().Insert(c + 1, value.Value);

return Json(value.Value);
}

public int FindChildRecords(int? id)
{
var count = 0;
for (var i = 0; i < TreeData.GetTree().Count; i++)
{
    if (TreeData.GetTree()[i].ParentValue == id)
    {
        count++;
        count += FindChildRecords(TreeData.GetTree()[i].TaskID);
    }
}
return count;
}

public object Delete(CRUDModel<TreeData> value)
{
if (value.deleted != null)
{
    for (var i = 0; i < value.deleted.Count; i++)
    {
        TreeData.GetTree().Remove(TreeData.GetTree().Where(ds => ds.TaskID == value.deleted[i].TaskID).FirstOrDefault());
    }
}
else
{
    TreeData.GetTree().Remove(TreeData.GetTree().Where(or => or.TaskID == int.Parse(value.Key.ToString())).FirstOrDefault());
}
return Json(value);
}

public class CRUDModel<T> where T : class
{

public TreeData Value;
public int Key { get; set; }
public int RelationalKey { get; set; }
public List<T> added { get; set; }
public List<T> changed { get; set; }
public List<T> deleted { get; set; }
}

public class TreeData
{
public static List<TreeData> tree = new List<TreeData>();
[System.ComponentModel.DataAnnotations.Key]
public int TaskID { get; set; }
public string TaskName { get; set; }
public int Duration { get; set; }
public int? ParentValue { get; set; }
public bool? isParent { get; set; }
public bool IsExpanded { get; set; }
public TreeData() { }
public static List<TreeData> GetTree()
{
    if (tree.Count == 0)
    {
        int root = 0;
        for (var t = 1; t <= 500; t++)
        {
            Random ran = new Random();
            string math = (ran.Next() % 3) == 0 ? "High" : (ran.Next() % 2) == 0 ? "Release Breaker" : "Critical";
            string progr = (ran.Next() % 3) == 0 ? "Started" : (ran.Next() % 2) == 0 ? "Open" : "In Progress";
            root++;
            int rootItem = root;
            tree.Add(new TreeData() { TaskID = rootItem, TaskName = "Parent task " + rootItem.ToString(), isParent = true, IsExpanded = false, ParentValue = null, Duration = ran.Next(1, 50) });
            int parent = root;
            for (var d = 0; d < 1; d++)
            {
                root++;
                string value = ((parent + 1) % 3 == 0) ? "Low" : "Critical";
                int par = parent + 1;
                progr = (ran.Next() % 3) == 0 ? "In Progress" : (ran.Next() % 2) == 0 ? "Open" : "Validated";
                int iD = root;
                tree.Add(new TreeData() { TaskID = iD, TaskName = "Child task " + iD.ToString(), isParent = true, IsExpanded = false, ParentValue = rootItem, Duration = ran.Next(1, 50) });
                int subparent = root;
                for (var c = 0; c < 500; c++)
                {
                    root++;
                    string val = ((subparent + c + 1) % 3 == 0) ? "Low" : "Critical";
                    int subchild = subparent + c + 1;
                    string progress = (ran.Next() % 3) == 0 ? "In Progress" : (ran.Next() % 2) == 0 ? "Open" : "Validated";
                    int childID = root ;
                    tree.Add(new TreeData() { TaskID = childID, TaskName = "sub Child task " + childID.ToString(), isParent = false, IsExpanded = false, ParentValue = subparent, Duration = ran.Next(1, 50) });
                }
            }
        }
    }
    return tree;
}
}

Load parent rows in expanded state with virtualization

Tree Grid provides an option to load the child records in the initial rendering itself for remote data binding by setting the loadChildOnDemand as true. When the loadChildOnDemand is enabled, parent records are rendered in expanded state.

When using virtualization with loadChildOnDemand , it helps you to improve the tree grid performance while loading the child records during the initial rendering for remote data binding by setting enableVirtualization as true and loadChildOnDemand as true.

Copied to clipboard
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { TreeGridComponent, VirtualScroll, Sort, Filter, Edit, Toolbar } from '@syncfusion/ej2-treegrid';
import './App.css';

TreeGrid.Inject(Edit, VirtualScroll, Sort, Filter, Toolbar);

let dataManager: DataManager = new DataManager({
adaptor: new UrlAdaptor,
insertUrl: "Home/Insert",
removeUrl: "Home/Delete",
updateUrl: "Home/Update",
url: "Home/DataSource",
});

let treegrid: TreeGrid = new TreeGrid({
dataSource: dataManager,
idMapping: 'TaskID',
parentIdMapping: 'ParentValue',
hasChildMapping: 'isParent',
loadChildOnDemand: true,
expandStateMapping: 'IsExpanded',
enableVirtualization: true,
height: 400,
editSettings: { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Row', newRowPosition: 'Below' },
toolbar: ['Add', 'Edit', 'Delete', 'Update', 'Cancel'],
pageSettings: {pageSize: 30},
allowPaging: true,
allowFiltering: true,
allowSorting: true,
treeColumnIndex: 1,
    columns: [
    { field: 'TaskID', headerText: 'Task ID', textAlign: 'Right', width: 90 },
    { field: 'TaskName', headerText: 'Task Name', width: 180 },
    { field: 'Duration', headerText: 'Duration', width: 80, textAlign: 'Right' }
]
});
treegrid.appendTo('#TreeGrid');

The following code example describes handling of child records at server end.

Copied to clipboard
public ActionResult loadChildOndemand()
{
TreeData.tree = new List<TreeData>();
return View();
}
public ActionResult UrlDatasource(DataManagerRequest dm)
{
List<TreeData> data = new List<TreeData>();
data = TreeData.GetTree();
DataOperations operation = new DataOperations();
IEnumerable<TreeData> DataSource = data;
if (dm.Expand != null && dm.Expand[0] == "CollapsingAction") // setting the ExpandStateMapping property whether is true or false
{
    var val = TreeData.GetTree().Where(ds => ds.TaskID == int.Parse(dm.Expand[1])).FirstOrDefault();
    val.IsExpanded = false;
}
else if (dm.Expand != null && dm.Expand[0] == "ExpandingAction")
{
    var val = TreeData.GetTree().Where(ds => ds.TaskID == int.Parse(dm.Expand[1])).FirstOrDefault();
    val.IsExpanded = true;
}
if (!(dm.Where != null && dm.Where.Count > 1))
{
    data = data.Where(p => p.ParentValue == null).ToList();
}
DataSource = data;
if (dm.Search != null && dm.Search.Count > 0) // Searching
{
    DataSource = operation.PerformSearching(DataSource, dm.Search);
}
if (dm.Sorted != null && dm.Sorted.Count > 0 && dm.Sorted[0].Name != null) // Sorting
{
    DataSource = operation.PerformSorting(DataSource, dm.Sorted);
}
if (dm.Where != null && dm.Where.Count > 1) //filtering
{
    DataSource = operation.PerformFiltering(DataSource, dm.Where, "and");
}
data = new List<TreeData>();
foreach (var rec in DataSource)
{
    data.Add(rec as TreeData);
}
var GroupData = TreeData.GetTree().ToList().GroupBy(rec => rec.ParentValue)
                    .Where(g => g.Key != null).ToDictionary(g => g.Key?.ToString(), g => g.ToList());
foreach (var Record in data.ToList())
{
    if (GroupData.ContainsKey(Record.TaskID.ToString()))
    {
        var ChildGroup = GroupData[Record.TaskID.ToString()];
        if (dm.Sorted != null && dm.Sorted.Count > 0 && dm.Sorted[0].Name != null) // Sorting the child records
        {
            IEnumerable ChildSort = ChildGroup;
            ChildSort = operation.PerformSorting(ChildSort, dm.Sorted);
            ChildGroup = new List<TreeData>();
            foreach (var rec in ChildSort)
            {
                ChildGroup.Add(rec as TreeData);
            }
        }
        if (dm.Search != null && dm.Search.Count > 0) // Searching the child records
        {
            IEnumerable ChildSearch = ChildGroup;
            ChildSearch = operation.PerformSearching(ChildSearch, dm.Search);
            ChildGroup = new List<TreeData>();
            foreach (var rec in ChildSearch)
            {
                ChildGroup.Add(rec as TreeData);
            }
        }
        if (ChildGroup?.Count > 0)
            AppendChildren(dm, ChildGroup, Record, GroupData, data);
    }
}
DataSource = data;
if (dm.Expand != null && dm.Expand[0] == "CollapsingAction") // setting the skip index based on collapsed parent
{
    string IdMapping = "TaskID";
    List<WhereFilter> CollapseFilter = new List<WhereFilter>();
    CollapseFilter.Add(new WhereFilter() { Field = IdMapping, value = dm.Where[0].value, Operator = dm.Where[0].Operator });
    var CollapsedParentRecord = operation.PerformFiltering(DataSource, CollapseFilter, "and");
    var index = data.Cast<object>().ToList().IndexOf(CollapsedParentRecord.Cast<object>().ToList()[0]);
    dm.Skip = index;
}
else if (dm.Expand != null && dm.Expand[0] == "ExpandingAction") // setting the skip index based on expanded parent
{
    string IdMapping = "TaskID";
    List<WhereFilter> ExpandFilter = new List<WhereFilter>();
    ExpandFilter.Add(new WhereFilter() { Field = IdMapping, value = dm.Where[0].value, Operator = dm.Where[0].Operator });
    var ExpandedParentRecord = operation.PerformFiltering(DataSource, ExpandFilter, "and");
    var index = data.Cast<object>().ToList().IndexOf(ExpandedParentRecord.Cast<object>().ToList()[0]);
    dm.Skip = index;
}
int count = data.Count;
DataSource = data;
if (dm.Skip != 0)
{
    DataSource = operation.PerformSkip(DataSource, dm.Skip);   //Paging
}
if (dm.Take != 0)
{
    DataSource = operation.PerformTake(DataSource, dm.Take);
}
return dm.RequiresCounts ? Json(new { result = DataSource, count = count }) : Json(DataSource);

}

private void AppendChildren(DataManagerRequest dm, List<TreeData> ChildRecords, TreeData ParentValue, Dictionary<string, List<TreeData>> GroupData, List<TreeData> data) // Getting child records for the respective parent
{
string TaskId = ParentValue.TaskID.ToString();
var index = data.IndexOf(ParentValue);
DataOperations operation = new DataOperations();
foreach (var Child in ChildRecords)
{
    if (ParentValue.IsExpanded)
    {
        string ParentId = Child.ParentValue.ToString();
        if (TaskId == ParentId)
        {
            ((IList)data).Insert(++index, Child);
            if (GroupData.ContainsKey(Child.TaskID.ToString()))
            {
                var DeepChildRecords = GroupData[Child.TaskID.ToString()];
                if (DeepChildRecords?.Count > 0)
                {
                    if (dm.Sorted != null && dm.Sorted.Count > 0 && dm.Sorted[0].Name != null) // sorting the child records
                    {
                        IEnumerable ChildSort = DeepChildRecords;
                        ChildSort = operation.PerformSorting(ChildSort, dm.Sorted);
                        DeepChildRecords = new List<TreeData>();
                        foreach (var rec in ChildSort)
                        {
                            DeepChildRecords.Add(rec as TreeData);
                        }
                    }
                    if (dm.Search != null && dm.Search.Count > 0) // searching the child records
                    {
                        IEnumerable ChildSearch = DeepChildRecords;
                        ChildSearch = operation.PerformSearching(ChildSearch, dm.Search);
                        DeepChildRecords = new List<TreeData>();
                        foreach (var rec in ChildSearch)
                        {
                            DeepChildRecords.Add(rec as TreeData);
                        }
                    }
                    AppendChildren(dm, DeepChildRecords, Child, GroupData, data);
                    if (Child.IsExpanded)
                    {
                        index += DeepChildRecords.Count;
                    }
                }
            }
        }
    }
}

}

public ActionResult Update(CRUDModel<TreeData> value)
{
List<TreeData> data = new List<TreeData>();
data = TreeData.GetTree();
var val = data.Where(ds => ds.TaskID == value.Value.TaskID).FirstOrDefault();
val.TaskName = value.Value.TaskName;
val.Duration = value.Value.Duration;
return Json(val);
}

public ActionResult Insert(CRUDModel<TreeData> value)
{
var c = 0;
for (; c < TreeData.GetTree().Count; c++)
{
    if (TreeData.GetTree()[c].TaskID == value.RelationalKey)
    {
        if (TreeData.GetTree()[c].isParent == null)
        {
            TreeData.GetTree()[c].isParent = true;
        }
        break;
    }
}
c += FindChildRecords(value.RelationalKey);
TreeData.GetTree().Insert(c + 1, value.Value);

return Json(value.Value);
}

public int FindChildRecords(int? id)
{
var count = 0;
for (var i = 0; i < TreeData.GetTree().Count; i++)
{
    if (TreeData.GetTree()[i].ParentValue == id)
    {
        count++;
        count += FindChildRecords(TreeData.GetTree()[i].TaskID);
    }
}
return count;
}

public object Delete(CRUDModel<TreeData> value)
{
if (value.deleted != null)
{
    for (var i = 0; i < value.deleted.Count; i++)
    {
        TreeData.GetTree().Remove(TreeData.GetTree().Where(ds => ds.TaskID == value.deleted[i].TaskID).FirstOrDefault());
    }
}
else
{
    TreeData.GetTree().Remove(TreeData.GetTree().Where(or => or.TaskID == int.Parse(value.Key.ToString())).FirstOrDefault());
}
return Json(value);
}

public class CRUDModel<T> where T : class
{

public TreeData Value;
public int Key { get; set; }
public int RelationalKey { get; set; }
public List<T> added { get; set; }
public List<T> changed { get; set; }
public List<T> deleted { get; set; }
}

public class TreeData
{
public static List<TreeData> tree = new List<TreeData>();
[System.ComponentModel.DataAnnotations.Key]
public int TaskID { get; set; }
public string TaskName { get; set; }
public int Duration { get; set; }
public int? ParentValue { get; set; }
public bool? isParent { get; set; }
public bool IsExpanded { get; set; }
public TreeData() { }
public static List<TreeData> GetTree()
{
    if (tree.Count == 0)
    {
        int root = 0;
        for (var t = 1; t <= 1500; t++)
        {
            Random ran = new Random();
            string math = (ran.Next() % 3) == 0 ? "High" : (ran.Next() % 2) == 0 ? "Release Breaker" : "Critical";
            string progr = (ran.Next() % 3) == 0 ? "Started" : (ran.Next() % 2) == 0 ? "Open" : "In Progress";
            root++;
            int rootItem = root;
            tree.Add(new TreeData() { TaskID = rootItem, TaskName = "Parent task " + rootItem.ToString(), isParent = true, IsExpanded = true, ParentValue = null, Duration = ran.Next(1, 50) });
            int parent = root;
            for (var d = 0; d < 1; d++)
            {
                root++;
                string value = ((parent + 1) % 3 == 0) ? "Low" : "Critical";
                int par = parent + 1;
                progr = (ran.Next() % 3) == 0 ? "In Progress" : (ran.Next() % 2) == 0 ? "Open" : "Validated";
                int iD = root;
                tree.Add(new TreeData() { TaskID = iD, TaskName = "Child task " + iD.ToString(), isParent = true, IsExpanded = true, ParentValue = rootItem, Duration = ran.Next(1, 50) });
                int subparent = root;
                for (var c = 0; c < 6; c++)
                {
                    root++;
                    string val = ((subparent + c + 1) % 3 == 0) ? "Low" : "Critical";
                    int subchild = subparent + c + 1;
                    string progress = (ran.Next() % 3) == 0 ? "In Progress" : (ran.Next() % 2) == 0 ? "Open" : "Validated";
                    int childID = root ;
                    tree.Add(new TreeData() { TaskID = childID, TaskName = "sub Child task " + childID.ToString(), ParentValue = subparent, Duration = ran.Next(1, 50) });
                }
            }
        }
    }
    return tree;
}
}