Add Data-Based Widgets to MVC Project

The DevExtreme UI Widgets Library includes widgets that are bound to data: dxDataGrid, dxGallery, dxMenu, dxLookup, etc. Data can be loaded from a locally located resource and from a remote service. To simplify working with data, DevExtreme supplies a Data Library. This library includes the DataSource object that keeps sorting, grouping, filtering and data transformation options, and applies them each time data is loaded. The DataSource underlying data access logic is isolated in a Store. Unlike the DataSource, a Store is a stateless object implementing a universal interface for reading and modifying data. Each Store contains the same set of methods. Several Store types are already implemented - Array Data Store (takes an array of data), Local Data Store (works with window.localStorage) and OData (works with OData protocol). All these stores have a single interface and thus can easily be changed to use another data source. Widgets from the Data Visualization library are also designed with an intention to support the DataSource-DataStore concept. In this tutorial, you will learn how to create a DataSource object for the dxDataGrid and dxChart widgets using a common Data Store. This will be a custom Store that accesses data from a custom service in a JSON format.

See Also
  • If you are not going to use DevExtreme widgets in MVC applications, follow the Use Remote Data for DataGrid and Use Remote Data for Chart tutorials to learn how to create a grid and chart based on a remote data source. These tutorials are similar to this one and allow you to learn how to present data with the DevExtreme widgets in an environment-independent project.
  • To learn more about the Data Library, refer to the Data Layer article.
  • To find examples on using the Data Library, refer to the Data Layer tutorials.
Download Code

Prepare a Sample MVC Application

  • Create a new MVC application project and add DevExtreme Web libraries as described in the Add Widget to MVC Project tutorial.

  • Add a Home controller and Index View. In addition, add a JavaScript file to implement JavaScript logic for the widgets that you will add to the Index view.

  • Add the Knockout library to the application's scripts, because the widgets will be added using the Knockout approach.

  • Add all the required references to the Index view, as it is described in the Knockout Approach step of the Add Widget to MVC Project tutorial.

    HTML
    <html>
    <head runat="server">
        <meta name="viewport" content="width=device-width" />
        <title>Index</title>
        <link rel="stylesheet" type="text/css" href="/Content/dx.common.css" />
        <link rel="stylesheet" type="text/css" href="/Content/dx.light.css" />
        <script src="/Scripts/jquery-2.1.1.min.js"></script>
        <script src="/Scripts/knockout-3.1.0.js"></script>
        <script src="/Scripts/globalize/globalize.js"></script>
        <script src="/Scripts/dx.webappjs.debug.js"></script>
        <script src="/Scripts/dx.chartjs.debug.js"></script>
        <script src="/MyJS/script_Knockout.js"></script>
    </head>
    <body>
    </body>
    </html>
  • Add a file with the following JSON object to the project.

    JavaScript
    [
        {
            "month": "january",
            "recordLow": -7,
            "recordHigh": 18,
            "average": 8.9,
            "color": "#00BFFF",
            "temperature": [
                { "day": 1, "t": 13.1 },
                { "day": 2, "t": 13.2 },
                //...
                { "day": 30, "t": 7.2 },
                { "day": 31, "t": 7.6 }
            ]
        },
        {
            "month": "february",
            //...
        }
    ]

    This object will serve as the data that came from "custom service". To simplify the tutorial and create an accent on how to transport received data to a widget, custom service will not be implemented.

Here is the resulting project structure.

Project Structure

Add a Grid

  • Open the Index view and add a div element with dxDataGrid data binding .

    HTML
    <div data-bind="dxDataGrid: {}"></div>
  • Open the script_Knockout.js file, which you added previously. Declare the ViewModel object and pass it to the ko.applyBindings() method as a parameter.

    JavaScript
    var viewModel = {};
    window.onload = function () {
        ko.applyBindings(viewModel);
    };
  • Create a CustomStore object to get JSON data from the file added in the previous step.

    JavaScript
    var customStore = new DevExpress.data.CustomStore({
        load: function (loadOptions) {
            var d = $.Deferred();
            $.getJSON('/Models/weatherData.js').done(function (data) {
                d.resolve(data, {totalCount: data.length});
            });
            return d.promise();
        }
    });

    The load method is required for getting data. You can make a more complex request for filtered or sorted data taking the load options passed as a parameter into account. In the code above, these options are not used to simplify the example for this given task.

    NOTE: As you can see, a total count of items in the data source is specified. The grid will use this information to resolve paging and other features.

  • Define a configuration object for the DataSource object. Add the gridDataSource field to the ViewModel object to which the dxDataGrid widget is bound by Knockout. Assign the DataSource configuration object to this field and bind the grid's dataSource option to it. This configuration object will be used to create the DataSource object. Do not create the Data Source object manually. It will be created by the grid internally. This is a dxDataGrid widget peculiarity. Other DevExtreme widgets can take on the manually created DataSource object. For details on grid data binding, refer to the Data Binding article.

    JavaScript
    var gridDataSourceConfiguration = {
        store: customStore
    };
    var viewModel = {
        gridDataSource: gridDataSourceConfiguration,
    };
    HTML
    <div data-bind="dxDataGrid: {
        dataSource: gridDataSource
    }"></div>

    At this step, you can run the project and confirm that the grid is displayed with all the loaded data.

  • Specify the columns that you need to be displayed in the grid.

    HTML
    <div data-bind="dxDataGrid: {
        dataSource: gridDataSource,
        columns:[
            'month',
            'recordLow',
            'recordHigh'
        ]
    }"></div>
  • Disable sorting.
    The dxDataGrid widget supports sorting by default. However, this feature does not work now, because the sorting options that are passed to the remote server to get a sorted data are not processed. You can read details on this in the Data Binding | Provide Data | Using a CustomStore topic. In this tutorial, disable sorting by setting the mode field of the sorting configuration object to none.

    HTML
    <div data-bind="dxDataGrid: {
        dataSource: gridDataSource,
        columns:[
            'month',
            'recordLow',
            'recordHigh'
        ],
        sorting: {
            mode: 'none'
        }
    }"></div>

To learn how to configure the dxDataGrid widget in more detail, refer to the Configure DataGrid tutorial.

View the result below.

Add a Chart

The data that comes as a JSON in this application allows you to display temperature flow during each month on a chart. For this purpose, add the dxChart widget.

HTML
<div data-bind="dxChart:{}"></div>

The Data Store that was created for the grid in the step below, loads an array of the following objects.

[        
    {
        "month": "january",
        "recordLow": -7,
        "recordHigh": 18,
        "average": 8.9,
        "color": "#00BFFF",
        "temperature": [
            { "day": 1, "t": 13.1 },
            { "day": 2, "t": 13.2 },
            //...
            { "day": 30, "t": 7.2 },
            { "day": 31, "t": 7.6 }
        ]
    },
    {...},
    ...
]

This object structure is not appropriate for using the temperature | day field as a data source for chart arguments and the temperature | t field - for chart values. The following data structure is expected to be used for the chart.

[
    { "day": 1, "january": 13.1, "february": 6.4, ...},
    { "day": 2, "january": 13.1, "february": 6.4, ...},
    ...
]

or

[
    {"month": "january", "day": 1, "t": 13.1},
    {"month": "january", "day": 2, "t": 13.2},
    ...
]

The second variant is possible if you process the data received by the Store. To do this, use the postProcess configuration option for the DataSource object.

var chartDataSourceConfiguration = {
    store: customStore,
    postProcess: function(data) {
        var result = [];
        $.each(data, function () {
            var month = this.month;
            $.each(this.temperature, function () {
                this.month = month;
                result.push(this);
            });
        });
        return result;
    }
};

Create the DataSource object using chartDataSourceConfiguration as a configuration object. Add the chartDataSource field to the ViewModel object to which the dxChart widget is bound by Knockout. Assign the DataSource object to this field and bind the chart's dataSource option to it.

HTML
<div data-bind="dxChart:{
    dataSource: chartDataSource
}"></div>
JavaScript
var viewModel = {
    gridDataSource: gridDataSourceConfiguration,
    chartDataSource: new DevExpress.data.DataSource(chartDataSourceConfiguration)
};

Define a series template and specify common options for chart series.

    <div data-bind="dxChart:{
        dataSource: chartDataSource,
        seriesTemplate: {
            nameField: 'month',
        },
        commonSeriesSettings: {
            argumentField: 'day',
            valueField: 't',
            type: 'line',
            point: { visible: false }
        }
    }"></div>

Now you can see the chart in the browser. You can configure it using other configuration options to tune it up to the required view. For instance, specify the start and the end of the argument axis so that non-existing days are not displayed on it.

    <div data-bind="dxChart:{
        dataSource: chartDataSource,
        seriesTemplate: {
            nameField: 'month',
        },
        commonSeriesSettings: {
            argumentField: 'day',
            valueField: 't',
            type: 'line',
            point: { visible: false }
        },
        argumentAxis: {
            min: 1,
            max: 31,
            minValueMargin: 0,
            maxValueMargin: 0
        },
        legend: {
            verticalAlignment: 'bottom',
            horizontalAlignment: 'center'
        }
    }"></div>

To learn how to configure the dxChart widget in more detail, refer to the Configure Charts tutorial.

Here is the result.

Bind Chart to the Selected Grid Row

Now, add one more chart to display the temperature flow within the month that is currently selected in the grid.

HTML
<div data-bind="dxChart:{ }"></div>

To provide data for this chart, enable selection in the grid and handle the selection change.

HTML
<div data-bind="dxDataGrid: {
    dataSource: gridDataSource,
    columns:[
        'month',
        'recordLow',
        'recordHigh'
    ],
    selection: {
        mode: 'single'
    },
    selectionChanged: selectionChangedHandler,
}"></div>
JavaScript
var viewModel = {
    gridDataSource: gridDataSourceConfiguration,
    chartDataSource: new DevExpress.data.DataSource(chartDataSourceConfiguration),
    chart2DataSource: ko.observableArray(),
    selectionChangedHandler: function (selectedItems) {
        if (selectedItems.selectedRowKeys.length) {
            this.chart2DataSource(selectedItems.selectedRowsData[0].temperature)
        }
    }
};

As you can see in the code above, the chart must be bound to the ViewModel's chart2DataSource field - an observable array that is updated with the data presented by the currently selected grid row.

HTML
<div data-bind="dxChart:{
    dataSource: chart2DataSource,
    series: {
        argumentField: 'day',
        valueField: 't',
        type: 'bar'
    }
}"></div>

The last thing to do is to specify a grid row to be selected initially. For this purpose, use the grid's selectedRowKeys option. To specify this option, there should be a key field in the provided data source. So, specify the key configuration option for the custom store that is used for the widgets.

JavaScript
var customStore = new DevExpress.data.CustomStore({
    key: 'month',
    //...
});
HTML
<div data-bind="dxDataGrid: {
    dataSource: gridDataSource,
    columns:[
        'month',
        'recordLow',
        'recordHigh'
    ],
    selection: {
        mode: 'single'
    },
    selectedRowKeys: ['January'],
    selectionChanged: selectionChangedHandler,
}"></div>

Here is the result.