DataProcessor and custom REST response from server

I’m trying to figure out how to use Webix DataTable with legacy REST services that return its result in a non-webix way.

To fetch data and populate a DataTable using a custom server response was quite easy. The steps I used was

  1. Create a new DataDriver that extends the existing json DataDriver
  2. Implement a custom toObject(text, response) method in the new data driver
    that adapts the the server result to the model Webix uses ( {pos, total_count, data[] } )
  3. Specify the new DataDriver in DataTable using the “datatype” property

But I’m not able to figure out what I must do to be able to handle a custom JSON response from POST, PUT and DELETE requests!

I was able to adjust the URL format & parameters used by Webix when performing REST requests against the server by

  1. Create a new Proxy named “customProxy” that extends webix.proxy.json
  2. In the new proxy override the load & save functions
  3. In the DataTable config use the “customProxy->” prefix in the url and save.url properties.

But the problem now is where and how shall I add the logic that adapts the custom response from the server when a POST, PUT or DELETE is submitted?

I have looked at the DataProcessor and maybe the processResult() method can be where I add the adaption. But I still want the normal internal default logic of the DataProcessor to continue after I have adapted the result! The DataProcessor seems to be created by the “save” config in the DataTable and I have found no clean / simple way to append logic to the processResult().

I could use webix.wrap() but then I must add logic somewhere that keeps track of all the UI components that has a DataProcessor and apply the “wrap” fix to all those instances.

There must be simpler way that I haven’t found yet!

I might add that the PUT & POST response from the server contains the complete modified/created entity with property values generated by the server.

To accommodate for this type of response I’m using “updateFromResponse” ( https://docs.webix.com/api__dataprocessor_updatefromresponse_config.html ) but since the data structure of the response isn’t matching the expected Webix format ( { id, newid, status, … } ) it doesn’t work.

Since adapting the response from GET requests is possible using a custom DataDriver.toObject() method I assume that there’s a similar way to adapt the POST, PUT & DELETE responses, but so far I have not been able to figure out how it can be done.

Hello @PorkLip,

How and where shall I add the logic that adapts the POST, PUT & DELETE responses in a similar way that GET can be handled by DataDriver.toObject()?

The server response can be handled via a custom proxy. You can wait for the original server response and alter it in any way, shape or form on the client, which you then should return (you can return either a promise or a data object from within the proxy methods). Unfortunately, I am not able to provide a complete demo, since I do not have a custom backend set up, but the general idea on how this should work can be seen from the following example: https://snippet.webix.com/hic8kh84 (try changing the value in any cell). Please note the way the save() method of a proxy is set:

//  issue a POST request (PUT, DELETE), get a promise =>
//  handle the promise via a then() method, which receives the server response as its argument

save: {
  $proxy: true,
  save: function(view, params, dp) { // params - server request parameters (also contains the type of the operation, important to note when sending requests)
    webix.ajax().post("//test", params).then((obj) => { // where "obj" is your response object
       const data = obj.json();
       data.custom = "test"; // add the "custom" property to the response object - this is just an example
       return data; // return the altered object 
    });
  },
  updateFromResponse: true
}

The resulting object will be handled by the rest of your logic accordingly, depending on how the response should be treated. Naturally, this also allows you to reassign properties as needed.

I might add some notes about how I ended up solving the problem. Basically I used the solution provided by Dzmitry but with a few modifications.

  1. In the custom DataDriver I never override/implement toObject(). This was not needed since the same functionality could be obtained in a better way by implementing getRecords(), getDetails() and getInfo().
  2. In the proxy only one of the save() and saveAll() methods shall be implemented!
  3. Currently result(state, view, dataProcessor, text, data, loader) in the proxy
    is only invoked if saveAll() is implemented (not sure if this is a bug).
  4. In the custom Proxy I implemented the save(view, parameters, dataProcessor) using the callback function of the ajax methods.
    webix.proxy.custom = webix.extend({

        save: function (view, parameters, dataProcessor) {

            let context = {
                source: this.source, // Just here to make it easier to debug
                view: view,
                parameters: parameters
            };

            let ajax = webix.ajax().headers({
                "content-type": "application/json",
                "accept": "application/json"
            });

            let url = this.source.endsWith("/") ? this.source : this.source + "/";
            let callback = webix.proxy.custom._handleSaveResponse.bind(context);

            switch (parameters.operation) {
                case "insert":
                    return ajax.post(url, parameters.data, callback);
                case "update":
                    return ajax.put(url.substring(0, url.length - 1), parameters.data, callback);
                case "delete":
                    return ajax.del(url + escape(parameters.id), parameters.data, callback);
                default :
                    webix.message("Unknown save operation: " + parameters.operation, "error");
                    return null;
            }
        }

        /**
         * Callback used to modify result from server. The "context" it shall be executed
         * within shall contain source, view & parameters.
         */
        _handleSaveResponse: function (text, data, xmlHttpRequest) {
            const contentType = xmlHttpRequest.getResponseHeader("content-type");
            if (contentType === "application/json") {
                
                let jsonObject = data.json();

                // HERE IS WHERE YOU ADD YOUR ADAPTION / TRANSFORMATION OF THE jsonObject.
                // In the example below we just add the "newid" that is needed by Webix if that
                // isn't included in the server response
                if(this.parameters.operation=="insert") {
                    jsonObject.newid = jsonObject.id;
                }
                
                let text = JSON.stringify(jsonObject);
                data.json = function () {
                    return jsonObject;
                }
                data.text = function () {
                    return text;
                }
            } else {
                console.warn("CustomProxy._handleSaveResponse(): Expected json but response is %s!", contentType);
            }
        },

    }, webix.proxy.json);
1 Like

Hello @PorkLip thanks for the explained solution.
But, how do you handle your custom proxy with a model? And then how do you load data into a datable using a model with this proxy?

Thank you
A

Hi @algianotti. I don’t fully understand you question, but if it is how I enable the custom proxy and model on the datatable then it is done in two steps. To handle a special model/result from the server you must specify an “datatype” in the datatable. Enabling a custom proxy is performed using “proxyName->” prefix in the url:s of the datatable. Part of an an example could look something like this:

const exampleTable = {
    view: "datatable",
    id: "exampleTable",

    datatype: "customDataDriver", // Custom data driver
    url: "custom->/rest/v1/example", // This url:s uses the "custom" proxy
    save: {
        autoupdate: true, // Use false to enable multiple row modifications that needs a save button
        updateFromResponse: true, // Use rest logic where an updated object is expected in the response
        undoOnError: true, // Undo changes when an error is encountered
        url: "custom->/rest/v1/example" // This url:s uses the "custom" proxy
    },
    datafetch: 256,
    loadahead: 256,
    datathrottle: 512,
    resizeColumn: true,

    select: "row",
    ...
}

You can read more about custom data drivers and proxies here Data Drivers of Guides, Auxiliary Resources Webix Docs
Webix Proxy Objects of Guides, Interacting with the Server Side Webix Docs