Overview
DevExtreme 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 properties 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 properties 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 properties, use another form.
var dataSource = new DevExpress.data.DataSource({ // array of data store: array, // additional configuration properties sort: "name", pageSize: 10 });
In this case, the array is passed to the store property 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 properties, 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 property.
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 properties.
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 properties, 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 component 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 properties.
- 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 properties 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 properties.
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 properties (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 properties 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 property 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 property 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 property or the sort(sortExpr) method of the DataSource. The value assigned to the property 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 property 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 properties: 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 property. You can also modify the search API properties 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 property intended to specify the structure of an item of the array being loaded. The DataSource passes the value of this property 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 property value via the configuration object passed to the DataSource constructor.
var dataSource = new DevExpress.data.DataSource({ store: data, select: "lastName" });
To modify the select property, pass the new property 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 properties of the DataSource.
Item Mapping
The map configuration property of the DataSource enables you to modify each item of the loaded array. A function passed to this property 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 property 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 property of the DataSource will help you perform this task. Assign a function that implements the required post processing algorithm to this property. 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 property.
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 property.
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 property of the DataSource. You can get and modify the property 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.
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 property to true. If pushes come too frequently, specify pushAggregationTimeout to aggregate them.
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 UI components. 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 Property
To attach a handler for a certain event to a DataSource or Data Store, use the corresponding configuration property. The properties that take on event handling functions have names starting with on.
The following example demonstrates how to use a configuration property 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")
Handling Errors
DevExtreme includes the setErrorHandler utility method to help you handle errors that occur within the entire data layer. Pass an error handler to this method. The handler accepts a JavaScript Error object as a parameter.
DevExpress.data.setErrorHandler(function (error) { console.log(error.message); }); // or when using modules import { setErrorHandler } from "data/errors"; setErrorHandler(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 property 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 property 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 a properties 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; } }