Intermediate

DataProcessor

Webix DataProcessor is a mixin 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 during component initialization or standalone.

Initialization via the Save Property of a Component

DataProcessor is created automatically if you define the save property of a data component. In the simplest case, save can be the path to the script for saving data:

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

Related sample:  Server-side Integration: List

If you use the same script for loading and saving, set save as true:

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

Standalone Initialization

DataProcessor can be initialized separately from a component. You can use either the dp helper or DataProcessor constructor.

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

Both dp and constructor require the same parameter - an object with at least these 2 properties:

  • 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.

Sending Requests

DataProcessor tracks client-side data operations and sends a POST request to the server script, specified in save. With the request, DataProcessor sends the name of the operation type:

  • 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".

Together with the operation name, request contains the data of the edited/added/deleted record.

Request Form Data

id  7
title   The Shawshank Redemption
webix_operation delete

You can change the name of the field that stores operation name with the help of the operationName setting of DataProcessor.

Rest Mode

Webix DataProcessor can also trigger RESTful server-side requests if you use the rest proxy. It this case, for each operation type a separate request type will be sent:

  • 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.

To use rest proxy, add "rest->" before the path to your script:

view: "datatable",
save: "rest->/samples/server/films",

"webix_operation" is not appended to request data, since the operation type is stated by the request type.

Related sample:  Datatable: Data Saving with NodeJS

Custom Save Pattern

You can further customize DataProcessor logic for saving data. For example, you can define some preprocessing logic for request data and define the type of request. You can customize saving logic by defining the save property as:

  1. a function for defining logic for all types of operations
  2. an object for defining separate logic for different types of operations and in order to use DataProcessor events and properties
  3. a proxy

1. 'save' as a function. In this case, the operation name is not appended to the sent data, but passed as a parameter of the function. All parameters are:

  • id - the record ID
  • operation - the operation name
  • update - the changed record values
webix.ui({
    view:"datatable",
    save:function(id, operation, update){
        if (operation === "insert")
            return webix.ajax().post(
                "/server/films",
                update
            );
        /* ...other operations */
    }
});
 
// or for standalone dataprocessor
const dp = new webix.DataProcessor({
    master:"datatable1",
    url: function(id, operation, update){
        if (operation === "insert")
            return webix.ajax().post(
                "/server/films",
                update
            );
        /* ...other operations */
    }
});

2. You can provide saving logic for each type of data operation separately by defining 'save' as an object. Each property can be either the path to the script or a function:

webix.ui({
    view:"datatable",
    save:{
        // functions or string URLs
        insert: function(id, operation, update){
            return webix.ajax().post(
                "/server/films",
                update
            );
        },
        update: function(id, operation, update){ /* ... */ },
        delete: function(id, operation, update){ /* ... */ }
    }
});

3. You can also provide a data proxy for saving. Here you also must define the save function. Its parameters are:

  • view (Object) - a component instance,
  • update (Object) - an update object,
  • dp (Object) - a DataProcessor instance.

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 (update.operation == "insert")
            return webix.ajax().post("/samples/server/films", update.data);
        // ... other operations
    }
}

Or for standalone initialization:

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

Related sample:  Datatable: Saving Data with Proxy and Url

Using DataProcessor API

DataProcessor has a set of methods and properties for changing the default processing pattern.

If you want to use API of the DataProcessor during its initialization, you need to define save as an object with saving urls/functions in the url property and other properties. The DataProcessor configuration will look as follows:

webix.ui({
    view:"datatable",
    save:{
        url: "some/path", // or function, object, proxy
        on:{
            onAfterSave(){ /* */ }
        },
        operationName: "operation",
        /* ...other config properties */
    }
});

To use DataProcessor API at runtime, you can access the DataProcessor with the Webix dp method either by the ID of the master component:

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

Or by the ID of the DataProcessor, if you have set it during standalone initialization:

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

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