Overview
The DevExtreme framework includes a data layer, which is a set of complementary components that enable you to read and write data. The core data layer objects are DataSource and Stores.
The DataSource is intended to simplify working with data, whether it be an in-memory array, a local browser storage, remote service, or any custom storage. The DataSource is a stateful object that keeps sorting, grouping, filtering, and keeps data transformation options and applies them each time data is loaded. It also provides events intended to handle changing data and the state.
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. For more information about Stores refer to the What Are Stores section of this article.
The following picture illustrates how the DataSource and Stores interact.
Asynchrony
All Data transferring operations in the data layer are asynchronous, whether it be an access to a remote service or to local data. It is made to support the universal Store interface, regardless of the source of data you access. These operations return a native Promise or a jQuery.Promise when you use jQuery. Use this Promise to specify callbacks for successful operation completion and for operation failure.
dataSource.load() .done(function(result) { //success callback }) .fail(function(error) { //error callback });
Creating DataSource
To create a DataSource instance, call the DataSource constructor and pass the required configuration object to this constructor. This section covers the ways to associate the required data with the DataSource instance being created. You can use one of these options to create a DataSource instance.
From Array
Pass the required array to the DataSource constructor.
var dataSource = new DevExpress.data.DataSource(array);
If you need to specify other DataSource configuration options, use another form.
var dataSource = new DevExpress.data.DataSource({ // array of data store: array, // additional configuration options sort: "name", pageSize: 10 });
In this case, the array is passed to the store option of the configuration object.
From url
If you pass a string to the DataSource constructor, the DataSource will access the data returned by the AJAX request to the URL specified by this string.
var dataSource = new DevExpress.data.DataSource("http://www.example.com/dataservices/jsondata");
You may also use a JSONP callback parameter supported by jQuery.ajax().
var dataSource = new DevExpress.data.DataSource("http://www.example.com/dataservices/jsonpdata?callback=?");
See Also
From Store
Pass a Store instance to the DataSource constructor.
var store = new DevExpress.data.ArrayStore(data); var dataSource = new DevExpress.data.DataSource(store);
If you need to specify other DataSource options, use the following code.
var store = new DevExpress.data.ArrayStore(array); var dataSource = new DevExpress.data.DataSource({ sort: "name", pageSize: 10, store: store });
In this example, the Store instance is passed to the store configuration option.
A Store can be specified implicitly. Two possibilities are described above (From Array and From Url). In addition, you can use the following.
Add the properties implementing the required data access logic to the DataSource configuration object. In this case, the CustomStore will be automatically created within the DataSource. The properties should correspond to the CustomStore configuration options.
JavaScriptvar dataSource = new DevExpress.data.DataSource({ //DataSource configuration sort: "name", pageSize: 10, //data access logic load: function(loadOptions) { return array; }, byKey: function(key) { return array[key]; }, ... });
Pass a Store configuration object. Besides the required Store configuration options, this object should contain the type property that specifies which Store object will be created within the DataSource.
JavaScriptvar dataSource = new DevExpress.data.DataSource( store: { type: "array", data: array } });
The following Store types are available.
- "array" - creates an ArrayStore
- "local" - creates a LocalStore
- "odata" - creates an ODataStore
What Are Stores
A store is a universal data access interface within the DevExtreme data layer. It includes the following methods that are required for reading and editing data:
- load(options) - Loads data. This function accepts an object specifying sorting, grouping, filtering, and data transformation options.
- insert(values) - Adds a new item with the passed values.
- remove(key) - Removes the data item the key specifies.
- update(key, values) - Sets the new values to the data item the key specifies.
- byKey(key, extraOptions) - Loads the data item the key specifies. The extraOptions argument requires additional implementation options to get the required item.
- totalCount(options) - Enables you to get the total count of items that satisfy the specified conditions without loading them. The options object can contain filter and group fields that specify filtering and grouping options.
DevExtreme includes the following Store implementations for the most common data access scenarios:
- ArrayStore - provides access to an in-memory array
- LocalStore - provides access to HTML5 web storage
- ODataStore - provides access to a remote OData service
You can implement custom data access logic as described in the Custom Sources topic if these stores are not suitable.
Reading Data
Consider data reading possibilities of the data layer on an example of an in-memory array.
var dataSource = new DevExpress.data.DataSource([ { name: "item1" }, { name: "item2" }, { name: "item3" } ]);
The information given in this section is applicable to any data source. We use in-memory data for the sake of simplicity.
To load data, call the dataSource.load() method.
dataSource.load() .done(function(result) { // 'result' contains the array associated with the DataSource }) .fail(function(error) { // handle error });
The DataSource allows you to specify initial data shaping options (sort, filter, etc.).
var dataSource = new DevExpress.data.DataSource({ store: [ { name: "Charlie", value: 10 }, { name: "Alice", value: 20 }, { name: "Bob", value: 30 } ], filter: [ "value", ">", 15 ], sort: { field: "name", desc: true } });
In the ArrayStore, LocalStore and ODataStore data shaping operations are applied in the following order: Filtering -> Sorting -> Selection -> Grouping -> Paging -> Item mapping -> Post processing.
As for the CustomStore, data shaping options are passed to the load(options) method, which gives you an opportunity to process them in any order you need.
Paging
By default, the DataSource loads items by pages. Page size is determined by the pageSize configuration option of the DataSource.
var dataSource = new DevExpress.data.DataSource({ pageSize: 30, . . . });
The current page index can be accessed and modified via the pageIndex() and pageIndex(newIndex) methods respectively.
dataSource.pageIndex(1); // Switch to the next page dataSource.load();
To disable paging, assign false to the paginate configuration option of the DataSource.
var dataSource = new DevExpress.data.DataSource({ paginate: false, . . . });
Sorting
DataSources and Stores use sort expressions to specify sorting conditions and direction.
For an example, declare a Person class.
var Person = function(firstName, lastName, city) { this.firstName = firstName; this.lastName = lastName; this.address = { city: city }; }; Person.prototype.fullName = function() { return this.firstName + " " + this.lastName; };
After the Person class is created, declare a data array holding several instances of this class.
var data = [ new Person("John", "Smith", "San Francisco"), new Person("John", "Doe", "New York"), new Person("Xavier", "Gomez", "Denver"), new Person("Xavier", "Lee", "New Mexico") ];
Create a simple DataSource.
var dataSource = new DevExpress.data.DataSource(data);
The sorting condition can be specified using the sort configuration option or the sort(sortExpr) method of the DataSource. The value assigned to the option or passed to the method is a sorting expression, which can have one of the following formats.
A single expression
JavaScriptdataSource.sort("lastName"); dataSource.load().done(function(result) { // 'result' contains the 'data' array items sorted by 'lastName' });
To sort data in descending order, pass an object containing the getter (field or selector - they are equivalents) and desc properties to the sort method.
JavaScriptdataSource.sort({ getter: "lastName", desc: true }); dataSource.load().done(function(result) { // 'result' contains the 'data' array items sorted by 'lastName' in descending order });
Pass a function to the method if you need to sort by custom values.
JavaScriptdataSource.sort(function(e) { if(e.Position == "CEO") return "!"; else return e.Position; }); dataSource.load().done(function(result) { // 'result' contains the 'data' array where CEO's are displayed at the top // other positions are sorted in ascending order });
Several expressions
JavaScriptdataSource.sort( "fullName", { getter: "address.city", desc: true } ); dataSource.load().done(function(result) { // 'result' contains the 'data' array items sorted by 'fullName' and then by 'address.city' in the descending order });
You can pass an array of expressions instead of using several arguments.
JavaScriptdataSource.sort([ "firstName", "lastName", { getter: "address.city", desc: true } ]); dataSource.load().done(function(result) { // 'result' contains the 'data' array items sorted by 'firstName', then by 'lastName', // and then by 'address.city' in the descending order });
Stores support the same sort expression syntax as the DataSource.
var arrayStore = new DevExpress.data.ArrayStore(data); arrayStore .load({ sort: [ { getter: "firstName", desc: true }, "lastName" ] }) .done(function (result) { // 'result' contains the 'data' array items sorted by 'firstName' in the descending order and then by 'lastName' });
Filtering
To consider the filter expression syntax, begin by creating a sample DataSource.
var dataSource = new DevExpress.data.DataSource([ { name: "First item", value: 5 }, { name: "Second item", value: 7 }, { name: "Last item", value: 3 } ]);
Filtering conditions are specified as a filter expression, which is a set of filter expressions separated by group operators. The filtering conditions can be defined via the filter configuration option or the filter(filterExpr) method of the DataSource.
Binary Filter Operations
To specify a binary filter operation, use an array containing the following items.
- The getter by which data items are filtered.
- The comparison operator. The available operators are: "=", "<>", ">", ">=", "<", "<=", "startswith", "endswith", "contains", "notcontains".
- The value to which the getter value is compared.
To specify filtering conditions, call the filter(filterExpr) method of the DataSource. If a filter expression consists of a single binary operation, you can either pass an array of the items described above, or you can pass those items as separate arguments.
dataSource.filter("value", ">", 3); dataSource.load().done(function(result) { //'result' contains the "First item" and "Second item" items });
dataSource.filter("name", "contains", "st"); dataSource.load().done(function(result) { //'result' contains the "First item" and "Last item" items });
The second item of the filter expression array is optional. You can specify only the getter and the value. The default operator is "=".
dataSource.filter("value", "=", 3);
is equal to
dataSource.filter("value", 3);
Unary Filter Operation
The unary not operation allows you to filter out data matching some conditions.
To specify an unary filter operation, use an array containing the following items.
- The "!" operator.
- The array with matching conditions.
To specify filtering conditions, call the filter(filterExpr) method of the DataSource. Note that filter expression should be included into square brackets.
dataSource.filter( ["!", ["value", "=", 3]] ); dataSource.load().done(function(result) { //'result' contains the "First item" and "Second item" items });
dataSource.filter( ["!", ["name", "contains", "st"]] ); dataSource.load().done(function(result) { //'result' contains the "Second item" });
Group Filter Operations
To combine several binary operations, use group operators "and" or "or".
dataSource.filter([ [ "value", ">", 3 ], "and", [ "value", "<", 7 ] ]); dataSource.load().done(function(result) { //'result' contains the "First item" item });
dataSource.filter([ [ "value", "<", 4 ], "or", [ "value", ">", 6 ] ]); dataSource.load().done(function(result) { //'result' contains the "Second item" and "Last item" items });
If neighboring filter expressions are not separated by a group operator, the "and" operator is implied.
dataSource.filter([ [ "value", ">", 3 ], "and", [ "value", "<", 7 ] ]);
is equal to
dataSource.filter([ [ "value", ">", 3 ], [ "value", "<", 7 ] ]);
The operator priority depends on the implementation of the underlying Store, or you can define the operator priority enclosing the required expression into square brackets.
dataSource.filter([ ["name", "notcontains", "Second"], "and", //calculated second [ [ "value", "<", 4 ], "or", //calculated first [ "value", ">", 6 ] ] ]);
Custom Filter Conditions
A filter expression can also be a function that takes on an item object and returns a Boolean value that determines whether an item satisfies the filtering condition or not.
dataSource.filter(function(itemData) { return itemData.value > 3 && itemData.name != "Second item"; }); dataSource.load().done(function(result) { //'result' contains the "First item" item });
Stores support the same filter expression syntax as the DataSource.
var arrayStore = new DevExpress.data.ArrayStore(data); arrayStore .load({ filter: [ [ "value", "<", 5 ], "or", [ "value", ">", 5 ] ] }) .done(function (result) { // 'result' contains the "Second item" and "Last item" items });
Search Api
In addition to filtering capabilities, the DataSource provides the search API. It is a convenient way to augment filtering by text search. The search API consists of three options: searchExpr, searchOperation, and searchValue.
var dataSource = new DevExpress.data.DataSource({ store: [ { firstName: "John", lastName: "Doe", birthYear: 1970 }, . . . ], filter: [ "birthYear" , "<", 2000 ], searchExpr: function(dataItem) { return dataItem.firstName + " " + dataItem.lastName; } }); dataSource.searchValue("doe"); dataSource.load().done(function(result) { //'result' contains items whose birthYear is less then 2000 and firstName or lastName contain "doe" });
The default search operation is "contains". It can be changed by providing the DataSource's searchOperation configuration option. You can also modify the search API options via the appropriate methods: searchExpr(expr), searchOperation(op), and searchValue(value).
Data Transformation
The DataSource includes several ways to customize the resulting array structure, which allows you to modify each item as well as process the entire array.
Select Expressions
The DataSource supports the select option intended to specify the structure of an item of the array being loaded. The DataSource passes the value of this option to the underlying Store when loading data. Therefore, this data transformation can be performed on the server side if the Store supports it.
To consider a select expression syntax, begin by creating an array of sample data.
var data = [ { firstName: "John", lastName: "Smith", city: "San Francisco" }, { firstName: "Xavier", lastName: "Lee", city: "New York" }, { firstName: "Maria", lastName: "Gomez", city: "Denver" } ];
You can define the select option value via the configuration object passed to the DataSource constructor.
var dataSource = new DevExpress.data.DataSource({ store: data, select: "lastName" });
To modify the select option, pass the new option value to the select(expr) method of the DataSource.
dataSource.select("firstName", "city"); dataSource.load();
A selection expression can be a getter, several getters, or a function.
A single getter
JavaScriptdataSource.select("lastName"); dataSource.load().done(function(result) { //'result' contains the resulting array });
The resulting array looks like the following.
[ { lastName: "Smith" }, { lastName: "Lee" }, { lastName: "Gomez" }, ]
Several getters
JavaScriptdataSource.select("firstName", "lastName"); dataSource.load().done(function(result) { //'result' contains the resulting array });
The resulting array looks like the following.
[ { firstName: "John", lastName: "Smith" }, { firstName: "Xavier", lastName: "Lee" }, { firstName: "Maria", lastName: "Gomez" }, ]
You can pass an array of getters instead of using several arguments.
JavaScriptdataSource.select(["firstName", "lastName"]);
A function
A select expression can also be a function that takes on an item object and returns the transformed object.
JavaScriptdataSource.select(function(dataItem) { return { fullName: dataItem.firstName + " " + dataItem.lastName, address: dataItem.city }; }); dataSource.load().done(function(result) { //'result' contains the resulting array });
The resulting array looks like the following.
[ { fullName: "John Smith", address: "San Francisco" } { fullName: "Xavier Lee", address: "New York" } { fullName: "Maria Gomez", address: "Denver" } ]
Stores support the same select expression syntax as the DataSource.
var arrayStore = new DevExpress.data.ArrayStore(data); arrayStore .load({ select: [ "firstName", "lastName" ] }) .done(function (result) { //'result' contains the resulting array });
Additional data transformation can be performed with the map and postProcess configuration options of the DataSource.
Item Mapping
The map configuration option of the DataSource enables you to modify each item of the loaded array. A function passed to this option takes an initial item as a parameter and returns the processed item. This function is performed within the DataSource and is not passed to the underlying Store.
Begin with the creation of a sample array.
var data = [ { firstName: "John", lastName: "Smith", city: "San Francisco" }, { firstName: "Xavier", lastName: "Lee", city: "New York" }, { firstName: "Maria", lastName: "Gomez", city: "Denver" } ];
Assign the required function to the map configuration option of the DataSource.
var dataSource = new DevExpress.data.DataSource({ store: data, map: function(itemData) { return { fullName: itemData.firstName + " " + itemData.lastName, address: { city: itemData.city } } } });
The dataSource in the example above will convert the initial data items into the following ones.
[ { fullName: "John Smith", address: { city: "San Francisco" } }, { fullName: "Xavier Lee", address: { city: "New York" } }, { fullName: "Maria Gomez", address: { city: "Denver" } } ]
Post Processing
You may often need to process loaded data in a way, which does not include simple sorting, grouping, filtering or data item transformation. For instance, you may need to group items by a date they were created (Today, This week, Long time ago). The postProcess configuration option of the DataSource will help you perform this task. Assign a function that implements the required post processing algorithm to this option. The function takes on the loaded array as a parameter and returns the processed array.
The following example illustrates how to perform the task mentioned above using the postProcess option.
var data = [ { subject: "First message", message: "This message has been recieved at 12/09/2013", date: new Date(2013, 08, 11) }, { subject: "Today message", message: "This message has been recieved today", date: new Date() }, ... ];
Create a DataSource instance and assign the required function to the postProcess configuration option.
function groupByDate(data) { var MS_PER_DAY = 24 * 60 * 60 * 1000, result = [], todayItems = [], thisWeekItems = [], otherItems = []; // Fill intervals $.each(data, function() { var daysAgo = ($.now() - this.date.getTime()) / MS_PER_DAY; if(daysAgo < 1) todayItems.push(this); else if(daysAgo < 7) thisWeekItems.push(this); else otherItems.push(this); }); // Construct final result if(todayItems.length) result.push({ key: "Today", items: todayItems }); if(thisWeekItems.length) result.push({ key: "This week", items: thisWeekItems }); if(otherItems.length) result.push({ key: "Long time ago", items: otherItems }); return result; } var dataSource = new DevExpress.data.DataSource({ store: data, postProcess: groupByDate });
The DataSource in the example above groups the items by the time period. The resulting array will look like the following.
[ { key: "Today", items: [ //today items ] }, { key: "This week", items: [ //this week items ] }, { key: "Long time ago", items: [ //items elder then a week ] } ]
Grouping
In some cases, you may need to group data by certain criteria. An item of a grouped array is an object containing two fields:
- key - a group key;
- items - an array of items belonging to the group.
var groupedArray = [ { key: "A", items: [ "Amelia", "Andrew"] } { key: "B", items: [ "Betty", "Benjamin"] } ];
The DevExtreme data layer (DataSource and Stores) supports grouping. The group expression syntax is identical to the sort expression syntax.
Consider grouping on the following examples. Begin with the creation of a sample DataSource.
var data = [ { name: "Amelia", birthYear: 1991, gender: "female" }, { name: "Benjamin", birthYear: 1983, gender: "male" }, { name: "Andrew", birthYear: 1991, gender: "male" }, { name: "Daniela", birthYear: 1983, gender: "female" }, { name: "Lee", birthYear: 1983, gender: "male" }, { name: "Betty", birthYear: 1983, gender: "female" } ]; var dataSource = new DevExpress.data.DataSource(data);
The group expression is stored in the group option of the DataSource. You can get and modify the option value via the group() and group(groupExpr) methods respectively. To group the given array by 'birthYear', call the group(groupExpr) method and pass "birthYear" as an argument.
dataSource.group("birthYear"); dataSource.load().done(function(result) { //'result' contains the loaded array });
The return value of the function that processes the loaded data contains the following array.
[ { key: 1983, items: [ { name: "Benjamin", birthYear: 1983, gender: "male" }, { name: "Daniela", birthYear: 1983, gender: "female" }, { name: "Lee", birthYear: 1983, gender: "male" }, { name: "Betty", birthYear: 1983, gender: "female" } ] }, { key: 1991, items: [ { name: "Amelia", birthYear: 1991, gender: "female" }, { name: "Andrew", birthYear: 1991, gender: "male" } ] } ]
To group by custom criteria, pass a function to the group(groupExpr) method.
dataSource.group(function(dataItem) { return dataItem.birthYear < 1990 ? "Born before 1990" : "Born after 1990"; }); dataSource.load().done(function(result) { //'result' contains the loaded array });
In this case, the 'result' array includes the following items.
[ { key: "Born after 1990", items: [ { name: "Amelia", birthYear: 1991, gender: "female" }, { name: "Andrew", birthYear: 1991, gender: "male" } ] }, { key: "Born before 1990", items: [ { name: "Benjamin", birthYear: 1983, gender: "male" }, { name: "Daniela", birthYear: 1983, gender: "female" }, { name: "Lee", birthYear: 1983, gender: "male" }, { name: "Betty", birthYear: 1983, gender: "female" } ] } ]
Multi-level grouping is also supported. Pass several getters or an array of getters to the group(groupExpr) method to load an array containing subgroups.
dataSource.group("birthYear", "gender"); dataSource.load().done(function(result) { //'result' contains the loaded array });
The loaded array looks like the following.
[ { key: 1983, items: [ { key: "female", items:[ { name: "Daniela", birthYear: 1983, gender: "female" }, { name: "Betty", birthYear: 1983, gender: "female" } ] }, { key: "male", items:[ { name: "Benjamin", birthYear: 1983, gender: "male" }, { name: "Lee", birthYear: 1983, gender: "male" } ] } ] }, { key: 1991, items: [ { key: "female", items:[ { name: "Amelia", birthYear: 1991, gender: "female" } ] }, { key: "male", items:[ { name: "Andrew", birthYear: 1991, gender: "male" } ] } ] } ]
Stores support the same group expression syntax as the DataSource.
var arrayStore = new DevExpress.data.ArrayStore(data); arrayStore .load({ group: "birthYear" }) .done(function (result) { // 'result' contains the 'data' array grouped by the 'birthYear' property value. });
Data Modification
For read-write access to data, use a Store directly in addition to the DataSource. You have two opportunities to obtain a Store instance.
Create a Store instance explicitly.
var store = new DevExpress.data.ArrayStore({ key: "id", data: [ { id: 1, value: "value 1" }, { id: 2, value: "value 2" } ] });
Note that it is necessary to specify the key expression to identify data items.
To load data from this Store either call store.load(loadOptions) or wrap this Store with the DataSource.
If you already have an existing DataSource, you can obtain the underlying Store instance via the store() method.
Insertion
To insert a new item to the Store, call the store.insert(values) method.
store.insert({ id: 3, value: "value 3" }) .done(function(values, key) { //handle success }) .fail(function(error) { //handle error });
Some Stores can generate a key value if you have not specified it in the values object. You may access it via the key argument of the done callback.
Update
To update a data item specified by the key, use the store.update(key, values) method.
store.update(1, { value: "new value" }) .done(function(values) { //handle successfull updating }) .fail(function(error) { //handle error });
Note that the second argument of the update(key, values) method contains only the properties whose values should be changed in the data item, but not the entire item, because it will be merged with the original item object.
In a common scenario, before updating, you need to load the required item using the store.byKey(key) method.
Integration with Push Services
Integration with push services allows the store to notify the DataSource and all the UI components bound to it about a data modification. On receiving the notification, the DataSource can reapply data processing operations, and the components can update parts of the UI in real time.
All stores provide the push(changes) method used to insert, update, and remove data objects. It accepts an array. This allows you to update data in batches.
store.push([{ type: "insert", data: data }]); store.push([{ type: "update", data: data, key: key }]); store.push([ { type: "remove", key: key1 }, { type: "remove", key: key2 } ]);
Call this method in the event handlers for client functions that can be invoked from the server. In the following example, such event handlers are insertData
, updateData
, and removeData
. This example shows how to use the push(changes) method with a SignalR service:
jQuery
$(function () { var hubUrl = "..."; var connection = $.hubConnection(hubUrl, { useDefaultPath: false }); var hubProxy = connection.createHubProxy("myHub"); var store = new CustomStore({ // CustomStore is configured here }); hubProxy.on("insertData", function(data) { store.push([{ type: "insert", data: data }]); }); hubProxy.on("updateData", function(key, data) { store.push([{ type: "update", key: key, data: data }]); }); hubProxy.on("removeData", function(key) { store.push([{ type: "remove", key: key }]); }); // ... });
Angular
import { HubConnectionBuilder } from '@aspnet/signalr'; import CustomStore from 'devextreme/data/custom_store'; // ... export class AppComponent { store: CustomStore; constructor() { var hubUrl = "..."; var hubConnection = new HubConnectionBuilder() .withUrl(hubUrl) .build(); this.store = new CustomStore({ // CustomStore is configured here }); hubConnection .start() .then(() => { hubProxy.on("insertData", (data: any) => { store.push([{ type: "insert", data: data }]); }); hubProxy.on("updateData", (key: any, data: any) => { store.push([{ type: "update", key: key, data: data }]); }); hubProxy.on("removeData", (key: any) => { store.push([{ type: "remove", key: key }]); }); }); } }
DataGrid SignalR Demo Scheduler SignalR Demo Chart SignalR Demo
After being notified, the DataSource can reapply sorting, filtering, grouping, and other data processing settings if you set the reshapeOnPush option to true. If pushes come too frequently, specify pushAggregationTimeout to aggregate them:
jQuery
$(function () { // The previous code goes here var ds = new DataSource({ store: store, reshapeOnPush: true, pushAggregationTimeout: 10000 // push every ten seconds }); });
Angular
import CustomStore from 'devextreme/data/custom_store'; import DataSource from 'devextreme/data/data_source'; // ... export class AppComponent { store: CustomStore; ds: DataSource; constructor() { // The previous code goes here this.ds = new DataSource({ store: this.store, reshapeOnPush: true, pushAggregationTimeout: 10000 // push every ten seconds }); } }
Events and Change Tracking
DataSource and Stores support events. The DataSource events are used to track the data loading state. They are used internally by the DevExtreme widgets. You can also use them for your purposes. Stores also raise events before and after each operation (loading, update, insertion, etc.).
Use one of the following approaches to handle events.
Assign a Handler to a Configuration Option
To attach a handler for a certain event to a DataSource or Data Store, use the corresponding configuration option. The options that take on event handling functions have names starting with on.
The following example demonstrates how to use a configuration option to handle an event.
var dataSource = new DevExpress.data.DataSource({ sort: "name", pageSize: 10, store: store, onChanged: function() { // Executed after the DataSource has successfully loaded data } });
Attach Several Handlers Using one Method
To attach several event handlers to a DataSource or Data Store, use the on method. This method has two overloads.
on(eventName, eventHandler)
This method allows you to provide several handlers for an event.JavaScriptdataSource .on("changed", handler1) .on("changed", handler2)
on(events)
This method allows you to subscribe to several events with one method call.JavaScriptdataSource .on({ "changed": handler1, "loadError": handler2 })
You can find a full list of events in the Events section: DataSource events and Store events (for example, ArrayStore events).
To unsubscribe from the events that you handled using the on() method, use the off() method. This method has two overloads.
off(eventName, eventHandler)
This method allows you to detach a specific event handler.JavaScriptdataSource .off("changed", handler1) .off("changed", handler2)
off(eventName)
This method allows you to detach all event handlers from a specific event.JavaScriptdataSource .off("changed")
Example
Usually, a data-bound application contains a list view displaying list data, and an edit view used to edit a data item. Both these views access the same shared Store. After you update a Store item modified in the edit view, the Store should inform the list view that the data has been modified. For this purpose, among other events the Store supports the modified event, which is raised after each data modification. To automatically update the list view, you may subscribe to this event, which enables you to determine whether or not the data should be reloaded.
The following example illustrates how to implement a ViewModel for a list view in a DevExtreme application. In this example, when the modified event is raised, the shouldReload flag is set to true, which causes the reloading of data when the list view is being shown.
// A shared Store declaration MyApplication.store = new DevExpress.data.CustomStore(...); // The list view model declaration MyApplication.list = function() { // A flag indicating whether or not data needs to be reloaded. var shouldReload = false, // A DataSource associated within the shared Store. dataSource = new DevExpress.data.DataSource(MyApplication.store); // A function handling the 'modified' event of the Store function handleStoreModification() { shouldReload = true; } // Subscribe to the 'modified' event of the Store MyApplication.store.on("modified", handleStoreModification); // A function handling the 'viewShown' event of the view function handleViewShown() { if(shouldReload) { shouldReload = false; dataSource.load(); } } // A function handling the 'viewDisposing' event of the view function handleViewDisposing() { //Unsubscribes from the modified event of the Store. MyApplication.store.off("modified", handleStoreModification); } // Exports the list view model return { dataSource: dataSource, viewShown: handleViewShown, viewDisposing: handleViewDisposing }; }
Handling Errors
DevExtreme provides the errorHandler utility property to help you handle errors that occur within the entire data layer. Assign the error handling function to this property. This function takes on the JavaScript Error object as a parameter.
DevExpress.data.errorHandler = function(error) { console.log(error.message); };
A Store enables you to handle errors that occur only within itself. To handle Store errors, assign an error handling function to the handleError configuration option of the Store.
var store = new DevExpress.data.LocalStore({ name: "MyStore", errorHandler: function(error) { console.log(error.message); } };
DevExtreme also provides an ability to handle errors that occur during operation execution. All DevExtreme data transfer operations are asynchronous and return a Promise (a native Promise or a jQuery.Promise when you use jQuery) allowing you to specify both success and error callbacks.
The following example illustrates how to handle errors that occurred during the execution of the load operation of a DataSource.
dataSource.load() .done(function(result) { // process result }) .fail(function(error) { console.log(error.message); });
Query Concept
Besides DataSource and Stores, DevExtreme data layer has one more useful concept named Query. Query is an abstract chainable interface which provides functionality to evaluate data queries.
There are several implementations of this interface, and they are used internally in some Stores as part of the load() method. While Query is considered internal instrument, one implementation, which is a wrapper over a JavaScript array, is particularly interesting, and can be used in the application code.
Here is an example:
var processedArray = DevExpress.data.query(inputArray) .filter([ [ "value", ">=", 10 ], "and", [ "value", "<=", 90 ]]) .sortBy("lastName") .thenBy("firstName") .select("lastName", "firstName", "value") .toArray();
The full list of supported methods is given in the Query API reference.
Getters And Setters
One of the basic data layer concepts is a Getters and Setters concept. Getters are essential tools used to specify sorting, grouping, filtering, and data transformation rules.
A getter is a function that returns a value of the predefined property of an object passed to the getter as a parameter. A setter is a function that assigns the new value to the predefined property of an object passed to the setter as a parameter. The new value is specified via the second parameter of a setter.
Create the person object to consider how to get and set its property values using getters and setters.
var person = { firstName: "John", lastName: "Smith", birthDate: new Date(1985,10,15), getBirthYear: function(){ return this.birthDate.getFullYear(); }, address: { zipCode: 90007, state: "CA", city: "Los Angeles", getAddress: function(){ return this.zipCode + " " + this.state + " " + this.city; } } };
Creating Getters And Setters
DevExtreme includes methods used to create a getter and setter for the property specified as a string.
var getter = DevExpress.data.utils.compileGetter("firstName"); var setter = DevExpress.data.utils.compileSetter("firstName");
Consider some examples to understand how getters and setters work.
To get a value of the person.firstName property, call the created getter and pass the person object to it.
// the getter returns "John" and assigns it to the 'name' variable var name = getter(person);
To assign a new value to the person.firstName property, call the created setter and pass the person object and the new value to it.
// assigns "Michael" to 'person.firstName' setter(person, "Michael");
The property for which you want to create a getter or a setter is specified via a string passed to the compileGetter or the compileSetter method. The property string can have one of the following formats.
A property name
Specifies a top-level property.JavaScriptvar getter = DevExpress.data.utils.compileGetter("firstName"); var setter = DevExpress.data.utils.compileSetter("firstName"); // the getter returns "John" and assigns it to the 'name' variable var name = getter(person); // assigns "Michael" to 'person.firstName' setter(person, "Michael");
A second and higher level property
Specifies a second and higher level property.JavaScriptvar getter = DevExpress.data.utils.compileGetter("address.city"); var setter = DevExpress.data.utils.compileSetter("address.city"); // the getter returns "Los Angeles" and assigns it to the 'city' variable var city = getter(person); // assigns "San Francisco" to 'person.address.city' setter(person, "San Francisco");
A method name
Specifies the method whose value should be returned by the getter.JavaScriptvar getter = DevExpress.data.utils.compileGetter("getBirthYear"); // the getter returns 1985 and assigns it to the 'year' variable var year = getter(person);
An array of property and method names
If you pass an array of strings to the compileGetter function, the compiled getter will return an object containing the specified properties. If an item of the array passed to compileGetter represents a second or higher level property, the getter will create the appropriate subobjects within the returned object.JavaScriptvar getter = DevExpress.data.utils.compileGetter(["firstName", "lastName", "address.city", "getBirthYear"]); var personData = getter(person);
The specified getter called for the person object will return the following object.
{ firstName: "John", lastName: "Smith", getBirthYear: 1985, address: { city: "Los Angeles" } }
Note that the returned object contains the getBirthYear property that holds the value returned by the method with the same name.
A function
If you pass a function to compileGetter, this function will be executed each time the getter is called. The function should have a parameter that takes on an object passed to the getter.JavaScriptvar getter = DevExpress.data.utils.compileGetter(function(dataItem) { return dataItem.firstName + " " + dataItem.lastName; }); // the getter returns "John Smith" and assigns it to the 'fullName' variable var fullName = getter(person);
"this"
In this case, the getter returns the object itself. This getter is used to sort or group an array of items containing a simple value.JavaScriptvar getter = DevExpress.data.utils.compileGetter("this"); // the getter returns "A text" and assigns it to the 'text' variable var text = getter("A text");
Getter And Setter Calling Options
If you assign the name of a method to a compileGetter, the getter normally returns the value returned by this method. If you need to get a reference to the method instead of its value, pass an object with the functionAsIs property set to true to the getter as a second parameter.
var getter = DevExpress.data.utils.compileGetter("address.getAddress"); // the getter returns a reference to the 'person.address.getAddress' function and assigns it to the 'getAddressFunction' variable var getAddressFunction = getter(person, { functionsAsIs: true });
If the getter returns an object containing several functions, the functionsAsIs option affects all functions contained in this object.
When you use a setter to assign a new value to a property containing an object, the object held in the property is replaced by the new one by default.
var setter = DevExpress.data.utils.compileSetter("address"); setter(person, { city: "San Francisco", street: "Stanford Ave" });
In the example above, the object held in the person.address property will contain only the city and street properties after the setter will be called. Other members will be lost.
If you need to merge the new object passed to the setter with the object held in the property, pass an options object containing the merge property set to true to the setter.
var setter = DevExpress.data.utils.compileSetter("address"); setter( person, { city: "San Francisco", street: "Stanford Ave" }, { merge: true } );
In this case, the address object will contain the following properties.
{ zipCode: 90007, state: "CA", city: "San Francisco", street: "Stanford Ave", getAddress: function() { return this.zipCode + " " + this.state + " " + this.city; } }
If you have technical questions, please create a support ticket in the DevExpress Support Center.