Custom Sources

Access to a custom data source is configured using the CustomStore component. DevExtreme provides ASP.NET and PHP extensions to configure the CustomStore and implement server-side data processing. You can also use the third-party extension for MongoDB. If these extensions are not suitable, use the instructions below to configure the CustomStore manually.

Load Data

The CustomStore requires the load function. It sends data processing settings to the server and gets processed data back. These settings depend on which remoteOperations are enabled. The following data processing settings apply to the DataGrid:

  • Paging settings: take, skip, requireTotalCount

    NOTE
    When scrolling is infinite, requireTotalCount is false. It should not be changed to ensure scrolling works properly.
  • Sorting settings: sort

  • Filtering settings: filter

  • Grouping settings: group
    The groupInterval field of the group setting is present only when the widget requests 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.

  • Summary calculation settings: totalSummary, groupSummary

  • Group paging settings: requireGroupCount

After receiving these settings, the server should apply them to data and send back an object with 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 
        count: 3,                // count of items in this group; required only when items = null
        summary: [30, 20, 40]    // group summary results
    },
    ...
    ], 
    totalCount: 200,              // if required in requireTotalCount
    summary: [170, 20, 20, 1020], // total summary results
    groupCount: 35                // if required in requireGroupCount
}

If the server has not received the group parameter, the resulting object should be as follows:

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

Below is a generalized CustomStore configuration for the DataGrid widget:

jQuery
JavaScript
var gridDataSource = new DevExpress.data.DataSource({
    key: "ID",
    load: function(loadOptions) {
        var d = $.Deferred(),
                params = {};
        [
            "skip",     
            "take", 
            "requireTotalCount", 
            "requireGroupCount", 
            "sort", 
            "filter", 
            "totalSummary", 
            "group", 
            "groupSummary"
        ].forEach(function(i) {
            if(i in loadOptions && isNotEmpty(loadOptions[i])) 
                params[i] = JSON.stringify(loadOptions[i]);
        });
        $.getJSON("https://mydomain.com/MyDataService", params)
            .done(function(result) {
                d.resolve(result.data, { 
                    totalCount: result.totalCount,
                    summary: result.summary,
                    groupCount: result.groupCount
                });
            });
        return d.promise();
    }
});
function isNotEmpty(value) {
    return value !== undefined && value !== null && value !== "";
}
$(function() {
    $("#dataGridContainer").dxDataGrid({
        dataSource: gridDataSource,
        remoteOperations: { groupPaging: true }
    });
});
Angular
TypeScript
HTML
import { ..., Inject } from "@angular/core";
import { HttpClient, HttpClientModule, HttpParams } from "@angular/common/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(HttpClient) httpClient: HttpClient) {
        function isNotEmpty(value: any): boolean {
            return value !== undefined && value !== null && value !== "";
        }
        this.gridDataSource = new DataSource({
            key: "ID",
            load: (loadOptions) => {
                let params: HttpParams = new HttpParams();
                [
                    "skip", 
                    "take", 
                    "requireTotalCount", 
                    "requireGroupCount", 
                    "sort", 
                    "filter", 
                    "totalSummary", 
                    "group", 
                    "groupSummary"
                ].forEach(function(i) {
                    if(i in loadOptions && isNotEmpty(loadOptions[i])) 
                        params = params.set(i, JSON.stringify(loadOptions[i]));
                });
                return httpClient.get("https://mydomain.com/MyDataService", { params: params })
                    .toPromise()
                    .then(result => {
                        return {
                            data: result.data,
                            totalCount: result.totalCount,
                            summary: result.summary,
                            groupCount: result.groupCount
                        };
                    });
            }
        });
    }
}
@NgModule({
    imports: [
        // ...
        DxDataGridModule,
        HttpClientModule
    ],
    // ...
})
<dx-data-grid ...
    [dataSource]="gridDataSource">
    <dxo-remote-operations 
        [groupPaging]="true">
    </dxo-remote-operations>
</dx-data-grid>
Vue
JavaScript
HTML
import DxDataGrid from "devextreme-vue/data-grid";
import CustomStore from "devextreme/data/custom_store";
// ...
function isNotEmpty(value) {
    return value !== undefined && value !== null && value !== "";
}
function handleErrors(response) {
    if (!response.ok)
        throw Error(response.statusText);
    return response;
}
const gridDataSource = {
    store: new CustomStore({
        key: "ID",
        load: (loadOptions) => {
            let params = "?";
            [
                "skip", 
                "take", 
                "requireTotalCount", 
                "requireGroupCount", 
                "sort", 
                "filter", 
                "totalSummary", 
                "group", 
                "groupSummary"
            ].forEach(function(i) {
                if(i in loadOptions && isNotEmpty(loadOptions[i])) 
                    params += `${i}=${JSON.stringify(loadOptions[i])}&`;
            });
            params = params.slice(0, -1);
            return fetch(`https://mydomain.com/MyDataService${params}`)
                .then(handleErrors)
                .then(response => response.json())
                .then((result) => {
                    return { 
                        data: result.data,
                        totalCount: result.totalCount,
                        summary: result.summary,
                        groupCount: result.groupCount
                    }
                });
        }
    })
}
export default {
    // ...
    data() {
        return {
            dataSource: gridDataSource,
            remoteOperations: { groupPaging: true }
        };
    },
    components: {
        // ...
        DxDataGrid
    }
}
<dx-data-grid ... 
    :data-source="dataSource"
    :remote-operations="remoteOperations" />
React
JavaScript
import React from "react";
import DataGrid, { RemoteOperations } from "devextreme-react/data-grid";
import CustomStore from "devextreme/data/custom_store";
// ...
function isNotEmpty(value) {
    return value !== undefined && value !== null && value !== "";
}
function handleErrors(response) {
    if (!response.ok) 
        throw Error(response.statusText);
    return response;
}
const gridDataSource = {
    store: new CustomStore({
        key: "ID",
        load: (loadOptions) => {
            let params = "?";
            [
                "skip", 
                "take", 
                "requireTotalCount", 
                "requireGroupCount", 
                "sort", 
                "filter", 
                "totalSummary", 
                "group", 
                "groupSummary"
            ].forEach(function(i) {
                if(i in loadOptions && isNotEmpty(loadOptions[i])) 
                    params += `${i}=${JSON.stringify(loadOptions[i])}&`;
            });
            params = params.slice(0, -1);
            return fetch(`https://mydomain.com/MyDataService${params}`)
                .then(handleErrors)
                .then(response => response.json())
                .then((result) => {
                    return { 
                        data: result.data,
                        totalCount: result.totalCount,
                        summary: result.summary,
                        groupCount: result.groupCount
                    }
                });
        }
    })
}
class App extends React.Component {
    render() {
        return (
            <DataGrid ...
                dataSource={gridDataSource}>
                <RemoteOperations
                    groupPaging={true} />
            </DataGrid>
        );
    }
}
export default App;

View Demo

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(){
    $("#dataGridContainer").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
    ],
    // ...
})
Vue
HTML
JavaScript
<dx-data-grid ... >
    <dx-editing
        :allow-adding="true"
        :allow-updating="true"
        :allow-deleting="true" />
</dx-data-grid>
import { DxDataGrid, DxEditing } from "devextreme-vue/data-grid";
export default {
    // ...
    data() {
        return {
            // ...
        };
    },
    components: {
        // ...
        DxDataGrid,
        DxEditing
    }
}
React
JavaScript
import React from "react";
import DataGrid, { Editing } from "devextreme-react/data-grid";
// ...
class App extends React.Component {
    render() {
        return (
            <DataGrid ... >
                <Editing
                    allowAdding={true}
                    allowDeleting={true}
                    allowUpdating={true} />
            </DataGrid>
        );
    }
}
export default App;

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: "https://mydomain.com/MyDataService/",
            method: "POST",
            data: JSON.stringify(values)
        })
    },
    remove: function (key) {
        return $.ajax({
            url: "https://mydomain.com/MyDataService/" + encodeURIComponent(key),
            method: "DELETE",
        })
    },
    update: function (key, values) {
        return $.ajax({
            url: "https://mydomain.com/MyDataService/" + encodeURIComponent(key),
            method: "PUT",
            data: JSON.stringify(values)
        })
    }
});

$(function() {
    $("#gridContainer").dxDataGrid({
        dataSource: gridDataSource,
        // ...
    });
});
Angular
TypeScript
HTML
import { ..., Inject } from "@angular/core";
import { HttpClient, HttpClientModule } from "@angular/common/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(HttpClient) httpClient: HttpClient) {
        this.gridDataSource = new DataSource({
            // ...
            insert: function (values) {
                return httpClient.post('https://mydomain.com/MyDataService', JSON.stringify(values))
                    .toPromise();
            },
            remove: function (key) {
                return httpClient.delete('https://mydomain.com/MyDataService/' + encodeURIComponent(key))
                    .toPromise();
            },
            update: function (key, values) {
                return httpClient.put('https://mydomain.com/MyDataService/' + encodeURIComponent(key), JSON.stringify(values))
                    .toPromise();
            }
        });
    }
}
@NgModule({
    imports: [
        // ...
        DxDataGridModule,
        HttpClientModule
    ],
    // ...
})
<dx-data-grid ...
    [dataSource]="gridDataSource">
</dx-data-grid>
Vue
JavaScript
HTML
import DxDataGrid from "devextreme-vue/data-grid";
import CustomStore from "devextreme/data/custom_store";
// ...
function handleErrors(response) {
    if (!response.ok)
        throw Error(response.statusText);
    return response;
}
const gridDataSource = {
    store: new CustomStore({
        // ...
        insert: (values) => {
            return fetch("https://mydomain.com/MyDataService", {
                method: "POST",
                body: JSON.stringify(values),
                headers:{
                    'Content-Type': 'application/json'
                }
            }).then(handleErrors);
        },
        remove: (key) => {
            return fetch(`https://mydomain.com/MyDataService/${encodeURIComponent(key)}`, {
                method: "DELETE"
            }).then(handleErrors);
        },
        update: (key, values) => {
            return fetch(`https://mydomain.com/MyDataService/${encodeURIComponent(key)}`, {
                method: "PUT",
                body: JSON.stringify(values),
                headers:{
                    'Content-Type': 'application/json'
                }
            }).then(handleErrors);
        }
    })
}
export default {
    // ...
    data() {
        return {
            dataSource: gridDataSource
        };
    },
    components: {
        // ...
        DxDataGrid
    }
}
<dx-data-grid ... 
    :data-source="dataSource" />
React
JavaScript
import React from "react";
import DataGrid from "devextreme-react/data-grid";
import CustomStore from "devextreme/data/custom_store";
// ...
function handleErrors(response) {
    if (!response.ok)
        throw Error(response.statusText);
    return response;
}
const gridDataSource = {
    store: new CustomStore({
        // ...
        insert: (values) => {
            return fetch("https://mydomain.com/MyDataService", {
                method: "POST",
                body: JSON.stringify(values),
                headers:{
                    'Content-Type': 'application/json'
                }
            }).then(handleErrors);
        },
        remove: (key) => {
            return fetch(`https://mydomain.com/MyDataService/${encodeURIComponent(key)}`, {
                method: "DELETE"
            }).then(handleErrors);
        },
        update: (key, values) => {
            return fetch(`https://mydomain.com/MyDataService/${encodeURIComponent(key)}`, {
                method: "PUT",
                body: JSON.stringify(values),
                headers:{
                    'Content-Type': 'application/json'
                }
            }).then(handleErrors);
        }
    })
}
class App extends React.Component {
    render() {
        return (
            <DataGrid ...
                dataSource={gridDataSource}>
            </DataGrid>
        );
    }
}
export default App;
See Also