Intermediate

DataProcessor

Webix DataProcessor is a functional library that lets you "communicate" with a server-side backend. DataProcessor:

  • resides on the client side as a mixin of the Webix library;
  • listens to component events - data adding, updating, deleting, and moving (should be enabled separately) - and passes the changed data as well as the performed operation name (insert, update, delete) to the server script in a POST request.
  • can validate data before passing it to a server script;
  • can be used for any UI data component and DataCollection.

Without DataProcessor, you need to attach corresponding functions to component events (onAfterInsert/Update/Delete) to get data ready for processing.

You can find the full list of DataProcessor methods, properties and events in the API Reference.

DataProcessor Initialization

DataProcessor can be initialized implicitly and explicitly.

Implicit Initialization via the Save Property

When you define the save property of a data component (master) as the path to the necessary script for saving data, DataProcessor is automatically initialized:

webix.ui({
    view:"datatable",
    autoConfig:true,
    url:"data_load.php", // your custom script for loading
    save:"data_save.php" // your custom script for saving
});

Explicit Initialization

DataProcessor can be initialized explicitly in the long and short forms. The compulsory parameters include:

  • url, a path to your own server script;
  • master, a component or DataCollection that will use the DataProcessor.

Go to the complete list of other parameters.

Short Form

The short form returns the DataProcessor of a component. If there isn't any, it creates a new DataProcessor.

Short Form (master, url)

webix.dp({
    id:"listDP",    // optional, the ID can be auto-generated
    master:$$("mylist"),
    url:"some_script.php",
    //other properties
});

Long Form

The long form is used to create a new DataProcessor. In this case, DataProcessor is created with the webix.DataProcessor() constructor:

Full Form

var dp = new webix.DataProcessor({
    id:"listDP",    // optional, the ID can be auto-generated
    url:"data.php",
    master:$$("mylist"),
    //other properties
});

Related sample:  Server-side Integration: List

Getting DataProcessor Object

DataProcessor has a set of methods and properties for changing the default processing pattern. You can use them by accessing the DataProcessor with the Webix dp method.

You can get the DataProcessor either by the ID of the master component:

var dp = webix.dp($$("mylist")); // returns dataprocessor object for "list" view

Or by the ID of the DataProcessor, if you have set it explicitly:

var dp = webix.dp($$("mydp"));

Vice versa, inside DataProcessor event handlers, you can reach the master component through the DataProcessor configuration object:

dp.attachEvent("onSomeEvent", function(id, status, obj){
   var grid = this.config.master; // this == DataProcessor
});

Data Processing Operations

DataProcessor interprets client-side operations performed over a data item and defines the type of the operation for it. The default processing looks as follows:

  • If the remove() method is called within the component, the operation is "delete"
  • If editing is done or the updateItem() method is called, the operation is "update";
  • If you add() data to the component, the operation is "insert".

This operation name alongside with the data of the edited/added/deleted record are sent to the server script by a POST request the moment any change is performed.

Request data

id  7
title   The Shawshank Redemption
webix_operation delete

You can define another operation name with the help of the operationName setting of DataProcessor.

Rest Mode

Webix can also trigger RESTful server-side requests via its rest proxy. It this case, the processing looks as follows:

  • If the remove() method is called within the component, the DELETE request is triggered;
  • If editing is done or the updateItem() method is called, the PUT request is triggered;
  • If you add() data to the component, the POST request is triggered.
view:"datatable",
save: "rest->/samples/server/films",

In REST mode, Webix does not append "webix_operation" to request data, since the operation type is stated by the request type.

Request data

id  7
title   The Shawshank Redemption

Related sample:  Datatable: Data Saving with NodeJS

Custom Save Pattern

You can provide custom logic for save operations triggered by DataProcessor. In this way, you benefit from automatic data saving and the ability to modify the requests sent to server.

You can initialize a DataProcessor with a saving function that receives the record ID, the operation name, and the changed record as parameters:

webix.ui({
    view:"datatable",
    save:function(id, operation, update){ /* ... */ }
});
 
//or, explicitly
new webix DataProcessor({
    master:"datatable1",
    url: function(id, operation, update){ /* ... */ }
});

Or, you can provide a data proxy for saving. The save function receives component instance, update object and DataProcessor instance as parameters:

webix.ui({
    view:"datatable",
    save:{
        $proxy:true,
        save:function(view, params, dp){ /* ... */ }
    }
});
 
//or, explicitly
new webix DataProcessor({
    master:"datatable1",
    url: {
        $proxy:true,
        save:function(view, update, dp){ /* ... */ }
    }
});

For such custom solutions, the operation name is not appended to the sent data. Instead, you should get it as a proxy or a function parameter and decide how to inform the server about the client-side changes:

Save function

view:"datatable",
save:function(id, operation, update){
    if (operation == "insert")
        return webix.ajax().post("/samples/server/films", update);
    // ... other operations
}

With the save proxy the operation name is available as a part of the update object:

Save proxy

view:"datatable",
save:{
    $proxy:true,
    save:function(view, update, dp){
        var id = update.data.id;
        if (params.operation == "insert")
            return webix.ajax().post("/samples/server/films", params.data);
        // ... other operations
    }
}

Related sample:  Datatable: Saving Data with Proxy and Url

Server Response

Success

To indicate successful data saving, the server side should return a non-empty response with the following parameters:

  • status - the status of operation ("success"/"error"/"invalid"). You can omit the status field for successful operations;
  • id - ID of the item sent to server;
  • newid - item ID returned from server after update. Normally it happens for newly inserted records.
  • other data that can be automatically applied to the record if updateFromResponse is switched on.

IDs are necessary for the inserted records only. During adding, the record is added to the UI component where it receives the temporary client-side ID, while on the server it receives another ID provided by the database.

If you return this server-side ID as a response parameter, DataProcessor will automatically change the client-side ID for the correct server-side ID. Note that you can return only the id field as the new ID, or both id and newid for old and new IDs respectively.

Error

To indicate data saving error, the server side should return a response containing:

  • status - the status of operation ("error" or "invalid").
  • id - ID of the item sent to server;

Note that en empty response is also considered an error and will trigger error events with the status:"error" field.

Event handling is described below.

Data Updating from Server Response

As it was stated earlier, the new ID from response of an insert request will automatically replace the temporary client-side ID. This is part of default DataProcessor behavior, and no specific actions are required.

Additionally, you can enable data update for all fields taking part in insert and update operations. It requires the following additions to the code:

  • tune server-side response to return the whole data object (not only the status or IDs);
  • enable DataProcessor updateFromResponse functionality:

Either during explicit DataProcessor definition

new webix.DataProcessor({
    updateFromResponse:true,
    master:"datatable1",
    url:"..."
});

Or when defining DataProcessor implicitly

view:"datatable",
save:{
    url:"...",
    updateFromResponse:true
}

Related sample:  Datatable: Updating

It can be useful for REST-full applications or when you need to fill in client-side fields which values can be calculated only on server side.

DataProcessor Event System

The event system for DataProcessor helps track different changes of data saving right on the client side. Here you can:

1) Modify data before it's gone to server with the onBeforeDataSend event that receives the whole data object as a parameter:

dp.attachEvent('onBeforeDataSend', function(obj){
    obj.data.StartDate = webix.i18n.dateFormatStr(obj.data.StartDate);
});

Or attach operation-specific handlers, e.g. onBeforeDelete:

webix.dp($$("grid")).attachEvent("onBeforeDelete", function(id, action){
    action.operation = "update";
    action.data.deleted = webix.i18n.parseFormatStr(new Date());
});

2) Track successful server-side responses with the help of onAfterSync event:

Successful server response

dp.attachEvent('onAfterSync', function(statusObj, text, data, loader){
    //statusObj {status:"success", id:"12"}
 
    var hash = data.json().data;
    //hash { id:"12"}
});

Check response data for more details.

3) Track unsuccessful server-side responses with onAfterSaveError event:

Unsuccessful server response

dp.attachEvent('onAfterSaveError', function(id, status, response, details){
    // id - record id
    // status - response status {id:"1", status:"error"}
});

To indicate an error that happened during saving, the response data should contain "error" or "invalid" status.

Catching the Moment of Saving

You can also catch the moment when the data changes are saved to the server with the help of the waitSave method of data components. waitSave expects a function with one or several data operations and returns a promise that resolves when data saving succeeds and rejects when it fails.

For example, this is how you can add data into the component and catch the result. When there is one data operation, the promise is resolved with an object that is returned from the server:

$$("grid").waitSave(function(){
    this.add({
        rank:99, title:"", year:"2012", votes:"100"
    });
}).then(function(obj){
    // server returns a data object with the server ID
    $$("grid").select(obj.id);
});

If you want to catch the result of a several-operation save, the promise will resolve with an array of objects:

$$("grid").waitSave(function(){
    for (var i = 0; i < 3; i++){
        this.add({
            rank:99, title:"", year:"2012", votes:"100"
        });
    }
}).then(function(arr){
    for (var i = 0; i < arr.length; i++){
        $$("grid").select(arr[i].id, i);
    }
});

Related sample:  Datatable: Wait Saving

Canceling and Limiting Auto Saving

If you do not want to save all updates to the server, you can temporarily cancel DataProcessor. There are several options for doing this:

  • Apply the ignore method to the DataProcessor object.
webix.dp($$("grid")).ignore(function(){
    $$("grid").add(data);
});
  • Switch off DataProcessor during the update operation:
dp.off();
$$("grid").add(data);
dp.on();
  • Cancel automatic data sending via DataProcessor autoupdate property and send changes manually.
//initially during dp configuration
new webix.DataProcessor({
    master:"datatable1",
    url:"...",
    autoupdate:false
});
 
//dynamically
webix.dp($$("datatable1")).define("autoupdate", false);

To trigger sending of a specific data update, use the save method with the item ID and the operation name as parameters:

webix.dp($$("datatable1")).save(1, "update");

You can also save all changes at once - read more in Webix Proxy Objects.

DataProcessor for Drag-n-Drop and Data Moving

DnD operations and data item moving can be tracked by DataProcessor like any other CRUD operation provided that you switch on DataProcessor trackMove functionality.

new webix.DataProcessor({
    master: tree,
    url: "...",
    trackMove:true
});

Related sample:  Server-side Integration: Tree

Data Validation with DataProcessor

Data validation is enabled by including specific rules for input field. You can read about them here. With rules specified, the validation process starts each time you try to save data to the database.

dp = new webix.DataProcessor({
    rules:{
        $all:webix.rules.isNotEmpty
    },
    url: "save.php",
    master: $$("mylist")
});

Sending Headers with DataProcessor-based Requests

You cannot send headers with DataProcessor requests as they are executed in the background. There are other ways you can do this:

1. If DataProcessor sends requests on its own, you can catch Webix onBeforeAjax request to modify any Ajax request issued from the page:

webix.attachEvent("onBeforeAjax",
    function(mode, url, data, request, headers, files, promise){
        headers["Content-type"] = "application/json";
    }
);

2. If you use the save function or a save proxy, you can add headers there with the webix.ajax().header() method.

Related sample:  Saving: Proxy and Url

Pessimistic Data Saving

By default, all data changes are first applied on the client side and only after that are sent to the server. You can explicitly send data to the server at any moment with the help of the save method. save() expects the following parameters:

  • id - the ID of the changed data item,
  • operation - type of data operation (e.g. "update", "insert", "delete"),
  • data - the data object that is sent to the sever.

save() returns a promise that resolves with the server response object and can be used to update the client side data with the server response data. Note that to avoid one more data saving, you need to wrap the client-side data operation in ignore.

webix.dp($$("list")).save(
    webix.uid(),
    "insert",
    { name:"New User", email:"", roles:"" }
).then(function(obj){
    webix.dp($$("list")).ignore(function(){
        $$("list").add(obj);
    });
});

To catch an unsuccessful result of data saving, add one more handler function to then():

webix.dp($$("list")).save(
    webix.uid(),
    "insert",
    { name:"New User", email:"", roles:"" }
).then(function(obj){
    webix.dp($$("list")).ignore(function(){
        $$("list").add(obj);
    });
}, function(){
    webix.message("Data were not saved");
});

Related sample:  Datatable: Pessimistic Data Saving

Standalone DataProcessor

DataProcessor can also be initialized without a master component:

var serverData = new webix.DataProcessor({ url:"/server/patients" });

The benefits of this approach are these:

  • A dataprocessor without a linked component can be used as a single server API for several components.

Pessimistic data saving with a standalone dataprocessor will be shorter in code:

serverData.save(
    webix.uid(),
    "insert",
    { name:"New User", email:"", roles:"" }
).then(function(obj){
    $$("list").add(obj);
});

Related sample:  Datatable: Pessimistic Data Saving

Related Article

Back to top