Custom Sources

To consume data from a custom source, the TreeList uses the CustomStore. This article provides details on how to configure it and on the protocol that the CustomStore adheres when communicating with the server. If the server already processes data (that is, performs filtering, sorting or grouping), notify the TreeList by assigning true to the corresponding field of the remoteOperations object.

jQuery
JavaScript
$(function() {
    $("#treeListContainer").dxTreeList({
        //...
        remoteOperations: {
            filtering: true,
            sorting: true,
            // Grouping is required only when
            // a user can filter data using a header filter
            grouping: true
        }
    });
});
Angular
HTML
TypeScript
<dx-tree-list ... >
    <dxo-remote-operations
        [filtering]="true"
        [sorting]="true"
        [grouping]="true"> <!-- Grouping is required only when a user can filter data using a header filter -->
    </dxo-remote-operations>
</dx-tree-list>
import { DxTreeListModule } from 'devextreme-angular';
// ...
export class AppComponent {
    // ...
}
@NgModule({
    imports: [
        // ...
        DxTreeListModule
    ],
    // ...
})
NOTE
Make data operations remote only if data has a plain structure.

If the server does not process data yet, employ one of the following extensions by DevExtreme. They implement server-side data processing and also configure the CustomStore for you. Remember to notify the TreeList of the data processing operations that were delegated to the server.

If these extensions do not suit your needs, configure the CustomStore and implement server-side data processing following the instructions given in this article. Note that the server may leave some of the data processing operations unimplemented. In this case, make sure that the corresponding fields of the remoteOperations object are set to false.

Web API Service Demo

Load Data

The CustomStore needs the load function to load data from the server. This function accepts a collection of loadOptions and passes them to the server. The server then processes data according to the loadOptions and sends it back. The following loadOptions are relevant for the TreeList:

  • sort: Array
    Defines sorting parameters. Multiple parameters apply to the data in sequence to implement multi-level sorting. Contains objects of the following structure:

    { selector: "field", desc: true/false }    
  • filter: Array
    Defines filtering parameters. Possible variants:

    • Binary filter

      [ "field", "=", 3 ]
    • Unary filter

       [ "!", [ "field", "=", 3 ] ]
    • Complex filter

      [
          [ "field", "=", 10 ],
          "and",
          [
              [ "otherField", "<", 3 ],
              "or",
              [ "otherField", ">", 11 ]
          ]
      ]

    See the Filtering topic for more details.

  • group: Array
    Defines grouping levels to be applied to the data. Each object can have the following parameters:

    • selector: String
      The field name to group by.
    • desc: Boolean
      Defines the selector field's descending sort order.
    • isExpanded: Boolean
      Defines whether the group's data objects should be returned instead of grouping data. Relevant only for the last group.
    • groupInterval: Number or String
      A numeric value groups data in ranges of the given length. A string value applies only to dates and can be one of "year", "quarter", "month", "day", "dayOfWeek", "hour", "minute" and "second". This parameter is present only when the widget sends a request for the header filter's data, and only if this data contains numbers or dates. Note that for numbers, the groupInterval option should be specified explicitly.
  • parentIds: Array
    Parent IDs of rows to be loaded.

After receiving these settings, the server should apply them to data and send back an object of the following structure:

{
    data: [{
        key: "Group 1",
        items: [ ... ],  // subgroups or data objects (for the last group when isExpanded = true)
                         // can be null when isExpanded = false and there are no further groups
        count: 3         // count of items in this group; required only when items is null
    },
    ...
    ]
}

If the server has not received the group parameter, the result object should be the following:

{
    data: [ ... ] // result data objects
}

Here is a generalized configuration of the CustomStore for the TreeList widget.

jQuery
JavaScript
var treeListDataSource = new DevExpress.data.DataSource({
    load: function (loadOptions) {
        var d = $.Deferred();
        $.getJSON('http://mydomain.com/MyDataService', {
            sort: loadOptions.sort ? JSON.stringify(loadOptions.sort) : "",
            filter: loadOptions.filter ? JSON.stringify(loadOptions.filter) : "",
            group: loadOptions.group ? JSON.stringify(loadOptions.group) : "",
            parentIds: loadOptions.parentIds ? JSON.stringify(loadOptions.parentIds) : ""
        }).done(function (result) {
            // You can process the received data here
            d.resolve(result.data);
        });
        return d.promise();
    }
});

$(function() {
    $("#treeListContainer").dxTreeList({
        dataSource: treeListDataSource,
        remoteOperations: {
            filtering: true,
            sorting: true,
            grouping: true
        }
    });
});
Angular
TypeScript
HTML
import { ..., Inject } from '@angular/core';
import { Http, HttpModule, URLSearchParams } from '@angular/http';
import { DxTreeListModule } from 'devextreme-angular';
import DataSource from 'devextreme/data/data_source';
import CustomStore from 'devextreme/data/custom_store';
import 'rxjs/add/operator/toPromise';
// ...
export class AppComponent {
    treeListDataSource: any = {};
    constructor(@Inject(Http) http: Http) {
        this.treeListDataSource = new DataSource({
            load: function (loadOptions) {
                let params: URLSearchParams = new URLSearchParams();
                params.set("sort", loadOptions.sort ? JSON.stringify(loadOptions.sort) : "");
                params.set("filter", loadOptions.filter ? JSON.stringify(loadOptions.filter) : "");
                params.set("group", loadOptions.group ? JSON.stringify(loadOptions.group) : "");
                params.set("parentIds", loadOptions.parentIds ? JSON.stringify(loadOptions.parentIds) : "");
                return http.get('http://mydomain.com/MyDataService', {
                                search: params
                            })
                            .toPromise()
                            .then(response => {
                                var json = response.json();
                                // You can process the received data here
                                return json.data
                            });
            }
        });
    }
}
@NgModule({
    imports: [
        // ...
        DxTreeListModule,
        HttpModule
    ],
    // ...
})
<dx-tree-list ...
    [dataSource]="treeListDataSource">
    <dxo-remote-operations
        [filtering]="true"
        [sorting]="true"
        [grouping]="true">
    </dxo-remote-operations>
</dx-tree-list>

Add, Delete, Update Data

To allow a user to add, delete and update data in the TreeList, assign true to the corresponding field of the editing object.

jQuery
JavaScript
$(function(){
    $("#treeListContainer").dxTreeList({
        // ...
        editing: {
            allowUpdating: true, 
            allowDeleting: true, 
            allowAdding: true
        }
    });
});
Angular
HTML
TypeScript
<dx-tree-list ... >
    <dxo-editing
        [allowUpdating]="true"
        [allowDeleting]="true"
        [allowAdding]="true">
    </dxo-editing>
</dx-tree-list>
import { DxTreeListModule } from 'devextreme-angular';
// ...
export class AppComponent {
    // ...
}
@NgModule({
    imports: [
        // ...
        DxTreeListModule
    ],
    // ...
})

With these settings, the TreeList expects that the server can also add, update and delete data. In addition, you need to configure the CustomStore as shown below. Note that in this example, the CustomStore is not declared explicitly. Instead, CustomStore operations are implemented directly in the DataSource configuration object to shorten the example.

jQuery
JavaScript
var treeListDataSource = new DevExpress.data.DataSource({ 
    // ...
    insert: function (values) {
        return $.ajax({
            url: "http://mydomain.com/MyDataService/",
            method: "POST",
            data: values
        })
    },
    remove: function (key) {
        return $.ajax({
            url: "http://mydomain.com/MyDataService/" + encodeURIComponent(key),
            method: "DELETE",
        })
    },
    update: function (key, values) {
        return $.ajax({
            url: "http://mydomain.com/MyDataService/" + encodeURIComponent(key),
            method: "PUT",
            data: values
        })
    }
});

$(function() {
    $("#treeListContainer").dxTreeList({
        dataSource: treeListDataSource,
        // ...
    });
});
Angular
TypeScript
HTML
import { ..., Inject } from '@angular/core';
import { Http, HttpModule, URLSearchParams } from '@angular/http';
import { DxTreeListModule } from 'devextreme-angular';
import DataSource from 'devextreme/data/data_source';
import CustomStore from 'devextreme/data/custom_store';
import 'rxjs/add/operator/toPromise';
// ...
export class AppComponent {
    treeListDataSource: any = {};
    constructor(@Inject(Http) http: Http) {
        this.treeListDataSource = new DataSource({
            // ...
            insert: function (values) {
                return http.post('http://mydomain.com/MyDataService', values)
                           .toPromise();
            },
            remove: function (key) {
                return http.delete('http://mydomain.com/MyDataService' + encodeURIComponent(key))
                           .toPromise();
            },
            update: function (key, values) {
                return http.put('http://mydomain.com/MyDataService' + encodeURIComponent(key), values)
                           .toPromise();
            }
        });
    }
}
@NgModule({
    imports: [
        // ...
        DxTreeListModule,
        HttpModule
    ],
    // ...
})
<dx-tree-list ...
    [dataSource]="treeListDataSource">
</dx-tree-list>
See Also