How to fomat a Kendo Datagrid request into REST format?

9 posts, 0 answers
  1. Robert
    Robert avatar
    5 posts
    Member since:
    Jul 2013

    Posted 11 Aug 2013 Link to this post

    Hi,

    I'm building off the KendoGrid "inline editing" demo. I want the destroy url to be simply:

    /api/company/delete/id ( for example, /api/company/delete/17 )

    What I'm getting is the URL I specified with the querystring '?id=17' appended

    /api/company/delete?id=17

    So how do I get the request in REST format instead of URI format?

    Many thanks in advance,

    Robert

    Here's my code:
    $(document).ready(function() {
           var dataSource = new kendo.data.DataSource({
               transport: {
                   read:  {
                       url: "/api/company/get",
                       dataType: "json"
                   },
                   destroy: {
                       url: "/api/company/delete",
                       dataType: "json"
                   },
                   parameterMap: function(options, operation) {
                       if (operation !== "read" && options.models) {
                           return {models: kendo.stringify(options.models[0].id)};
                       }
                   }
               },
               batch: true,
               pageSize: 20,
               schema: {
                   model: {
                       id: "id",
                       fields: {
                           id: { type: "number" },
                           name: { type: "string" },
                           admin_id: { type: "number" },
                           address_id: { type: "number" },
                           status_msg: { type: "string" }
                       }
                   }
               }
           });
    });
     
  2. Robert
    Robert avatar
    5 posts
    Member since:
    Jul 2013

    Posted 12 Aug 2013 Link to this post

    Hello,

    Okay so no answer to this question so far? I've now figured out half of it myself, which that I need to set the type: "DELETE" in my datasource transport destroy function. But I still don't see how to pass the value of the selected row into the URL?

    I find myself a bit aggravated that the use case of wanting to invoke a REST API isn't documented in a single example. Is this considered to be an unusual case? If there were an example I would have found it and now I wouldn't have to ask this follow up question and you wouldn't have to answer it.

    Can someone please help me out here? Now that I'm getting the URL without the '?' (suitable for invoking my REST API), how do I pass the arguments to it?

    Thank you,

    Robert
  3. Kendo UI is VS 2017 Ready
  4. Andrew
    Andrew avatar
    19 posts
    Member since:
    Jan 2013

    Posted 12 Aug 2013 Link to this post

    Hi, I struggled with this a little myself. Our application uses Jersey/JAX-RS to implement REST services on the server, and I needed to setup a generic DataSource extension that would be configured to work with our standard REST endpoints. The configuration below supports a standard design which supports CRUD using the following URL patterns
    Read list of items: GET "http://server/items"
    Create a new item: POST to "http://server/items"
    Read a single item: GET "http://server/items/{itemId}"
    Update a single item: PUT to "http://server/items/{itemId}"
    Destroy a single item: DELETE to "http://server/items/{itemId}"

    Notice that the transport options support a function for "url", so you can execute some code to build a URL for a single item. In our case, the server generates a URL for each item in the "idUrl" property when it generates the JSON for the item.

    Hope this helps,

        var DataStore = kendo.data.DataSource.extend({
     
            init: function (options) {
                function getIdentityUrl(data) {
                         return data.idUrl;
                     }
                options.transport = {
                    read: {
                        url: options.url,
                        type: 'GET',
                        dataType: 'json'
                    },
                    create: {
                        url: options.url,
                        type: 'POST',
                        dataType: 'json',                   // The response content type
                        contentType: MIME_JSON,             // The request content type
                        processData: false
                    },
                    update: {
                        url: getIdentityUrl,
                        type: 'PUT',
                        dataType: 'json',
                        contentType: MIME_JSON,
                        processData: false
                    },
                    destroy: {
                        url: getIdentityUrl,
                        type: 'DELETE',
                        dataType: 'json'
                    },
                    parameterMap: parameterMap
                };
     
                kendo.data.DataSource.prototype.init.call(this, options);
            },
    });
  5. Robert
    Robert avatar
    5 posts
    Member since:
    Jul 2013

    Posted 12 Aug 2013 Link to this post

    Hi Andrew,

    Thank for the detailed response. It does seem that the key is to use a function to build the URL. I still have no idea what the parameterMap is used for. I thought it was used for this very purpose, but I guess not.

    I see that you're setting parameterMap: parameterMap. would be kind enough to tell me what that means? My parameterMap property doesn't seem to do anything, meaning that the code below will work with our without it.

    Thanks again,

    Robert
    var dataSource = new kendo.data.DataSource({
                transport: {
                    read:  {
                        url: "/admin/getcompanies",
                        dataType: "json"
                    },
                    destroy: {
                        url: function (options) {
                            return '/api/company/delete/' + options.models[0].id;
                        },
                        type: "DELETE"
                    },
                    parameterMap: function(options, operation) {
                        if (operation !== "read" && options.models) {
                            var company_id = options.models[0].id;
                            console.log(company_id);
                            return company_id;
                            //return {id: kendo.stringify(options.models[0].id)};
                        }
                    }
                },
                batch: true,
                pageSize: 20,
                schema: {
                    model: {
                        id: "id",
                        fields: {
                            id: { type: "number" },
                            name: { type: "string" },
                            admin_id: { type: "number" },
                            address_id: { type: "number" },
                            status_msg: { type: "string" }
                        }
                    }
                }
            });
  6. Andrew
    Andrew avatar
    19 posts
    Member since:
    Jan 2013

    Posted 13 Aug 2013 Link to this post

    Right, the parameterMap function handles query string arguments or entity bodies depending on the type of request. Our function translates query parameters for paging and sorting based on the names we use in our server implementation. For POST/PUT, it handles JSON-encoding the data object.

    BTW, in REST design, you generally don't want verbs in your URLs. The verbs are the HTTP method. For example, I would change the URL for your companies list to "/admin/companies", not "/admin/getcompanies".

    /**
     * Called by the DataSource to convert parameters/body into the appropriate format.
     * @param data
     * @param requestType One of "read", "create", "update", or "destroy"
     * @returns {*}
     */
    function parameterMap(data, requestType) {
        if (requestType === 'read') {
            if (!_.isUndefined(data.skip)) {
                data['pg_start'] = data.skip;   // "skip" is what kendo calls the start index
                delete data.page;
                delete data.skip;
            }
            if (!_.isUndefined(data.pageSize)) {
                data['pg_size'] = data.pageSize;    // "pageSize" and "take" mean the same thing
                delete data.take;
                delete data.pageSize;
            }
            if (_.isArray(data.sort)) {
                // The server currently supports one level of sorting
                data['sort_by'] = data.sort[0].field;
                data['sort_dir'] = data.sort[0].dir;
                delete data.sort;
            }
        }
        if (requestType === 'create' || requestType === 'update') {
            return JSON.stringify(data);
        }
        return data;
    }
  7. Robert
    Robert avatar
    5 posts
    Member since:
    Jul 2013

    Posted 18 Aug 2013 Link to this post

    Hi Andrew,

    I've got the Kendo Grid working with the remote data source, and the request parameters into the right format. Thank you for the help. By the way, do you work for Telerik?

    Now I've changed the code such that the data source for the Grid is an MVVM view model, and the data source for the view model is the remote data source. The good news is that this all seems to be working.

    The question is, how can I print out the state of the view model? (I know the view model values are shown in the grid, but I want to print them out -  as they are in the MVVM widget binding example. But mine isn't formatted correctly, and it shows way to much information. I only want the contents of the Grid.

    This works. This is in my html file:
    <pre>
        {
        gridSource: [<span data-bind="text: displayGridSource"></span>    ]
        }
    </pre>

    Here's the 'displayGridSource' function, which is defined in view model definition. You'll see it in the complete script code below.
    displayGridSource: function() {
        return kendo.stringify(this.get("gridSource"));
    }

    But the output is hard to read, JSON encoded, and contains too much information (I'm happy this is working, but it's not what I want):
    {
        gridSource: [{"options":{"data":[],"schema":{"model":{"id":"id","fields":{"id":{"type":"number"},"name":{"type":"string"},"admin_id":{"type":"number"},"address_id":{"type":"number"},"status_msg":{"type":"string"}}}},"serverSorting":false,"serverPaging":false,"serverFiltering":false,"serverGrouping":false,"serverAggregates":false,"batch":true,"transport":{"read":{"url":"/api/company","dataType":"json","type":"GET"},"destroy":{"dataType":"json","type":"DELETE"}},"pageSize":20},"_map":{},"_prefetch":{},"_data":[{"id":1,"company_type":"1","name":"Acme","admin_id":1,"address_id":1,"status_msg":"unverified"}],"_ranges":[{"start":0,"end":1,"data":[{"id":1,"company_type":"1","name":"Acme","admin_id":1,"address_id":1,"status_msg":"unverified"}]}],"_view":[{"id":1,"company_type":"1","name":"Acme","admin_id":1,"address_id":1,"status_msg":"unverified"}],"_pristine":[{"id":1,"company_type":"1","name":"Acme","admin_id":1,"address_id":1,"status_msg":"unverified"}],"_destroyed":[],"_pageSize":20,"_page":1,"_group":[],"_total":1,"_events":{"change":[null,null,null],"progress":[null],"error":[null]},"transport":{"options":{"read":{"url":"/api/company","dataType":"json","type":"GET"},"destroy":{"dataType":"json","type":"DELETE"}},"cache":{}},"reader":{},"_skip":0,"_take":20,"_requestInProgress":false,"_aggregateResult":{}}    ]
    }

    This is what I want:
    <pre>
        {
        id: <span data-bind="text: viewModel._view.id"></span>,
        company_id: <span data-bind="text: company_type"></span>,
        name: <span data-bind="text: name"></span>,
        admin_id: <span data-bind="text: admin_id"></span>,
        address_id: <span data-bind="text: address_id"></span>,
        status_msg: <span data-bind="text: status_msg"></span>,
        }
    </pre>

    But none of the values are printed:
    {
        id: ,
        company_id: ,
        name: ,
        admin_id: ,
        address_id: ,
        status_msg: ,
    }

    Here's the complete code. There are two scripts, one is included on the main page, and one on a page specific page. The main javascript is always loaded, while
    the page specific javascript is loaded once I click the "list companies" link. Note that the viewModel (and the data source defined therein) is defined in the main script, and the Grid is defined in the page specific page.

    Main javascript
    viewModel = kendo.observable({
            gridSource: new kendo.data.DataSource({
                transport: {
                    read:  {
                        url: "/api/company",
                        dataType: "json",
                        type: "GET"
                    },
                    destroy: {
                        url: function (options) {
                            return '/api/company/' + options.models[0].id;
                        },
                        dataType: "json",
                        type: "DELETE"
                    },
                    parameterMap: function(options, operation) {
                        if (operation !== "read" && options.models) {
                            var company_id = options.models[0].id;
                            console.log(company_id);
                            return company_id;
                        }
                    }
                },
                batch: true,
                pageSize: 20,
                schema: {
                    model: {
                        id: "id",
                        fields: {
                            id: { type: "number" },
                            name: { type: "string" },
                            admin_id: { type: "number" },
                            address_id: { type: "number" },
                            status_msg: { type: "string" }
                        }
                    }
                }
            }),
            displayGridSource: function() {
                return kendo.stringify(this.get("gridSource"));
            }
        });
        kendo.bind($('#base'), viewModel);

    And here's the dynamically loaded content, which includes the HTML and the page specific java script:
    <div id="right-top1">
        <div id="company_grid" style="height: 380px"></div>
    </div>
     
    <script>
        $(document).ready(function() {
            $("#company_grid").kendoGrid({
                dataSource: viewModel.gridSource,
                pageable: true,
                columns: [
                    {field:"id", title: "Company ID", width: "20px"},
                    {field: "name", title: "Company Name", width: "60px"},
                    {field: "admin_id", title: "Admin ID", width: "20px"},
                    {field: "address_id", title: "Address ID", width: "20px"},
                    {field: "status_msg", title: "Status", width: "20px"},
                    {command: ["destroy"], title: " ", width: "30px"}
                ],
                editable: "inline"
            });
        });
    </script>



  8. Andrew
    Andrew avatar
    19 posts
    Member since:
    Jan 2013

    Posted 20 Aug 2013 Link to this post

    Robert, glad I can help - I don't work for Telerik, but I enjoy using their library. :-)

    I don't have as much experience with Kendo's MVVM, so I'm not sure about how well it supports looping through a template. In another MVVM library like knockout, you can bind an array of objects using syntax like:
    <table>
        <thead>
            <tr><th>First name</th><th>Last name</th></tr>
        </thead>
        <tbody data-bind="foreach: people">
            <tr>
                <td data-bind="text: firstName"></td>
                <td data-bind="text: lastName"></td>
            </tr>
        </tbody>
    </table>
    The above template would be bound using a model that has a property "people" containing an array of objects.
    In your case, you can use "this.get("gridSource").data()" to get the array of objects in the data source. If Kendo MVVM supports foreach, then you might be able to pass that array directly to kendo.bind(). If not, you could use another MVVM library or just write jQuery code by hand that iterates over the array and creates a <pre> tag based on what you have above.

    Good luck!
  9. Zachary
    Zachary avatar
    23 posts
    Member since:
    Feb 2013

    Posted 11 Dec 2013 Link to this post

    This forum thread was extremely helpful in determining how to get the transport URLs into a format suitable for a Web API controller. Particularly when Robert mentioned the key is to use a function to build the URL.

    var dataSource = new kendo.data.DataSource({
        transport: {
            read: {
                url: "api/products",
                type: "GET",
                dataType: "json"
            },
            create: {
                url: "api/products",
                type: "POST",
                dataType: "json"
            },
            update: {
                url: function (e) {
                    return "api/products/" + e.productID;
                },
                type: "PUT",
                dataType: "json"
            },
            destroy: {
                url: function (e) {
                    return "api/products/" + e.productID;
                },
                type: "DELETE",
                dataType: "json"
            }
        }
    });
  10. ian
    ian avatar
    1 posts
    Member since:
    Aug 2014

    Posted 21 Aug 2014 in reply to Andrew Link to this post

    You da man Andrew!!!!
Back to Top
Kendo UI is VS 2017 Ready