Custom Sources

The DataGrid uses the CustomStore to receive data from a custom source. This article provides details on how to configure it and the protocol that the CustomStore adheres to when communicating with the server. If the server already processes data, that is, performs filtering, sorting, paging, grouping or summary calculation, notify the DataGrid by assigning true to the corresponding remoteOperations object field.

jQuery
JavaScript
$(function() {
    $("#gridContainer").dxDataGrid({ 
        // ...
        remoteOperations: {
            filtering: true,
            sorting: true,
            paging: true,
            grouping: true,
            summary: true
        }
        // or simply
        // remoteOperations: true
    });
}); 
Angular
HTML
TypeScript
<dx-data-grid ... >
    <dxo-remote-operations
        [filtering]="true"
        [sorting]="true"
        [grouping]="true"
        [paging]="true"
        [summary]="true"> 
    </dxo-remote-operations>
    <!-- or simply -->
    <!-- [remoteOperations]="true" -->
</dx-data-grid>
import { DxDataGridModule } from 'devextreme-angular';
// ...
export class AppComponent {
    // ...
}
@NgModule({
    imports: [
        // ...
        DxDataGridModule
    ],
    // ...
})
NOTE
Use server-side filtering if your server returns hierarchical (grouped) data because client-side filtering requires inputting plain data.

If the server does not process data yet, employ one of the following DevExtreme extensions. They implement server-side data processing and also configure the CustomStore. Remember to notify the DataGrid 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 by following the instructions in this article. Note that the server may leave some of the data processing operations unimplemented. In this case, make sure that the corresponding remoteOperations object fields are set to false.

See Also

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 DataGrid:

  • take: Number
    Restricts the number of top-level data objects to return.

  • skip: Number
    Skips some data objects from the start of the result set. In conjunction with take, this parameter is used to implement paging.

  • 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.

  • requireTotalCount: Boolean
    Indicates that a total count of data objects in the result set must be returned in the totalCount field of the result. This count must reflect the number of data items after filtering, but disregard any take parameter used for the query.

  • totalSummary: Array
    Contains summary definitions of the following structure, where summaryType can be "sum", "avg", "min", "max" or "count":

    { selector: "field", summaryType: "sum" }

    The summary calculations' results should be returned in an array called summary that contains the result values in the same order as the summary definitions.

  • 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.
  • groupSummary: Array
    The structure is the same as for totalSummary, but these summary values are returned for each group. Used in conjunction with group.

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
        summary: [30, 20, 40]    // group summary results
    },
    ...
    ], 
    totalCount: 200,             // if required in requireTotalCount
    summary: [170, 20, 20, 1020] // total summary results
}

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

{
    data: [ ... ],               // result data objects
    totalCount: 200,             // if required in requireTotalCount
    summary: [170, 20, 20, 1020] // total summary results
}

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

jQuery
JavaScript
var gridDataSource = new DevExpress.data.DataSource({
    load: function (loadOptions) {
        var d = $.Deferred();
        $.getJSON('http://mydomain.com/MyDataService', {
            skip: loadOptions.skip,
            take: loadOptions.take,
            sort: loadOptions.sort ? JSON.stringify(loadOptions.sort) : "",
            filter: loadOptions.filter ? JSON.stringify(loadOptions.filter) : "",
            requireTotalCount: loadOptions.requireTotalCount,
            totalSummary: loadOptions.totalSummary ? JSON.stringify(loadOptions.totalSummary) : "",
            group: loadOptions.group ? JSON.stringify(loadOptions.group) : "",
            groupSummary: loadOptions.groupSummary ? JSON.stringify(loadOptions.groupSummary) : ""
        }).done(function (result) {
                d.resolve(result.data, { 
                    totalCount: result.totalCount,
                    summary: result.summary
                });
            });
        return d.promise();
    }
});

$(function() {
    $("#dataGridContainer").dxDataGrid({
        dataSource: gridDataSource,
        remoteOperations: true
    });
});
Angular
TypeScript
HTML
import { ..., Inject } from '@angular/core';
import { Http, HttpModule, URLSearchParams } from '@angular/http';
import { DxDataGridModule } 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 {
    gridDataSource: any = {};
    constructor(@Inject(Http) http: Http) {
        this.gridDataSource = new DataSource({
            load: function (loadOptions) {
                let params: URLSearchParams = new URLSearchParams();
                params.set("skip", loadOptions.skip);
                params.set("take", loadOptions.take);
                params.set("sort", loadOptions.sort ? JSON.stringify(loadOptions.sort) : "");
                params.set("filter", loadOptions.filter ? JSON.stringify(loadOptions.filter) : "");
                params.set("requireTotalCount", loadOptions.requireTotalCount);
                params.set("totalSummary", loadOptions.totalSummary ? JSON.stringify(loadOptions.totalSummary) : "");
                params.set("group", loadOptions.group ? JSON.stringify(loadOptions.group) : "");
                params.set("groupSummary", loadOptions.groupSummary ? JSON.stringify(loadOptions.groupSummary) : "");
                return http.get('http://mydomain.com/MyDataService', {
                                search: params
                            })
                            .toPromise()
                            .then(response => {
                                var json = response.json();
                                // You can process the received data here
                                return {
                                    data: json.data,
                                    totalCount: json.totalCount,
                                    summary: json.summary
                                }
                            });
            }
        });
    }
}
@NgModule({
    imports: [
        // ...
        DxDataGridModule,
        HttpModule
    ],
    // ...
})
<dx-data-grid ...
    [dataSource]="gridDataSource"
    [remoteOperations]="true">
</dx-data-grid>

Consider using the remote group paging feature when grouping large data lowers DataGrid performance. Note that for this feature, both the server and the client sides should be configured differently. Refer to the Remote Group Paging topic for more information.

Add, Delete, Update Data

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

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

With these settings, the DataGrid 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 gridDataSource = 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() {
    $("#gridContainer").dxDataGrid({
        dataSource: gridDataSource,
        // ...
    });
});
Angular
TypeScript
HTML
import { ..., Inject } from '@angular/core';
import { Http, HttpModule, URLSearchParams } from '@angular/http';
import { DxDataGridModule } 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 {
    gridDataSource: any = {};
    constructor(@Inject(Http) http: Http) {
        this.gridDataSource = 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: [
        // ...
        DxDataGridModule,
        HttpModule
    ],
    // ...
})
<dx-data-grid ...
    [dataSource]="gridDataSource">
</dx-data-grid>
See Also