SharePoint 2010 datasource

14 posts, 0 answers
  1. Toby
    Toby avatar
    20 posts
    Member since:
    Nov 2010

    Posted 21 Jun 2012 Link to this post

    Hi,

    I've been playing around with the Kendo UI datasource and have created a data plugin(?) for using the listdata.svc for SharePoint 2010, it is attached to this post as a zip file

    it can be implemented using the following code

    var dataSource = new kendo.data.DataSource({
            type: "sharepoint",
            transport: {
            read: "_vti_bin/listdata.svc/Inventory"
        },
        schema: {
            model: {
                fields: {
                    Make: { type: "string" },
                    Model: { type: "string" },
                    Price: { type: "number" },
                    PictureUrl: { type: "string" }
                }
            }
        },
        pageSize: 5,
        serverPaging: true,
        serverFiltering: true,
        serverSorting: true
    });

    I'm going to look at adding full CRUD functionality and will post any updates, this currently only does reading.

    SharePoint 2010 listdata.svc ODATA implementation does not currently support JSONP, so this can only be used in the same domain.

    Any comments or obvious errors please.
  2. Clint
    Clint avatar
    56 posts
    Member since:
    Feb 2011

    Posted 09 Jul 2012 Link to this post

    Full CRUD would be awesome!  Let us know it goes.
  3. Kendo UI is VS 2017 Ready
  4. Remco
    Remco avatar
    31 posts
    Member since:
    Sep 2012

    Posted 14 Sep 2012 Link to this post

    I almost managed to get all crud operations to work with the SharePoint 2010 list odata service. Here is the code:

    var crudServiceBaseUrl = "/_vti_bin/listdata.svc/RiskMatrices",
        dataSource = new kendo.data.DataSource({
            type: "odata",
            transport: {
                read: {
                    url: crudServiceBaseUrl,
                    dataType: "json"
                },
                update: {
                    // type must be POST instead of PUT when using MERGE.
                    //type: "POST",
                    url: function (options) {
                        return options.__metadata.uri;
                    },
                    dataType: "json",
                    beforeSend: function (jqXHR, settings) {
                        var options = JSON.parse(settings.data);
                        jqXHR.setRequestHeader("If-Match", options.__metadata.etag);
                        // Using MERGE so that the entire entity doesn't need to be sent over the wire.
                        //jqXHR.setRequestHeader("X-HTTP-Method", 'MERGE');
                    }
                },
                destroy: {
                    url: function (options) {
                        return options.__metadata.uri;
                    },
                    dataType: "json"//,
                    //beforeSend: function (jqXHR, settings) {
                    //    var options = JSON.parse(settings.data);
                    //    jqXHR.setRequestHeader("If-Match", options.__metadata.etag);                       
                    //}
                },
                create: {
                    url: crudServiceBaseUrl,
                    dataType: "json",
                }
            },
            schema: {
                model: {
                    id: "Id",
                    fields: {
                        Id: { type: "number", editable: false, nullable: true },
                        Title: { type: "string", validation: { required: true } },
                        Created: { type: "date" },
                        Modified: { type: "date" },
                        CreatedById: { type: "number" },
                        ModifiedById: { type: "number" }
                    }
                }
            },
            pageSize: 10,
            serverPaging: true,
            serverFiltering: true,
            serverSorting: true,
            error: function (e) {
                var response = JSON.parse(e.responseText);
                alert(response.error.message.value);
            }
        });

    I can do read, create and delete, but I can only do an update once. The difficulty is with the SharePoint 2010 list odata service requiring that a concurrency token is round-tripped. The SharePoint 2010 list odata service responds with an ETAG http header that contains the concurrency token. The SharePoint 2010 list odata service requires the use of an If-Match http header in a request to roundtrip the concurrency token. This etag value is also returned in the response body in a __metadata property in the json object when doing read and create operations. This way I can retrieve this etag value from this __metadata property and set the value in the If-Match http header before doing an update. When the update is successful the response includes the new etag value in the ETAG http header, but the update response body is otherwise empty. The kendo ui grid or datasource needs to update the etag value in the __metadata property of the updated object from the ETAG http header in the response so that I can do subsequent updates. This is currently not happening. As a result subsequent updates pass the out-of-date concurrency token in the If-Match request header. The SharePoint list odata service then throws a concurrency violation error.
  5. Remco
    Remco avatar
    31 posts
    Member since:
    Sep 2012

    Posted 18 Sep 2012 Link to this post

    I managed to get the transport create, read, update and destroy settings defined as functions working. I was not able to get the ETag response header as the jqXHR.getResponseHeader('ETag') would return null. Since I also wanted to get updated values for the Modified, ModifiedById and ModifiedBy properties, I thought it was best to re-load the updated entity. See below:
    var crudServiceBaseUrl = "/_vti_bin/listdata.svc/RiskMatrices",
        dataSource = new kendo.data.DataSource({
            type: "odata",
            transport: {
                read: function (options) {
                    return $.ajax({
                        type: "GET",
                        url: kendo.format("{0}?$expand=CreatedBy,ModifiedBy", crudServiceBaseUrl),
                        dataType: "json",
                        data: options.data
                    }).done(function (data, textStatus, jqXHR) {
                        options.success(data);
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        options.error(jqXHR);
                    });
                },
                update: function (options) {
                    return $.ajax({
                        type: "POST",
                        url: options.data.__metadata.uri,
                        dataType: "json",
                        contentType: "application/json",
                        data: kendo.stringify({
                            Id: options.data.Id,
                            Title: options.data.Title
                        }),
                        headers: {
                            "If-Match": options.data.__metadata.etag,
                            "X-HTTP-Method": 'MERGE'
                        }
                    }).done(function (data, textStatus, jqXHR) {
                        return $.ajax({
                            type: "GET",
                            url: kendo.format("{0}?$expand=CreatedBy,ModifiedBy", options.data.__metadata.uri),
                            dataType: "json"
                        }).done(function(data, textStatus, jqXHR) {
                            options.success(data);
                        }).fail(function(jqXHR, textStatus, errorThrown) {
                            options.error(jqXHR);
                        });
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        options.error(jqXHR);
                    });
                },
                destroy: function (options) {
                    return $.ajax({
                        type: "DELETE",
                        url: options.data.__metadata.uri,
                        dataType: "json",
                        contentType: "application/json",
                        headers: {
                            "If-Match": options.data.__metadata.etag
                        }
                    }).done(function (data, textStatus, jqXHR) {
                        options.success(data);
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        options.error(jqXHR);
                    });
                },
                create: function (options) {
                    return $.ajax({
                        type: "POST",
                        url: crudServiceBaseUrl,
                        dataType: "json",
                        contentType: "application/json",
                        data: kendo.stringify({
                            Id: options.data.Id,
                            Title: options.data.Title
                        })
                    }).done(function (data, textStatus, jqXHR) {
                        return $.ajax({
                            type: "GET",
                            url: kendo.format("{0}?$expand=CreatedBy,ModifiedBy", data.d.__metadata.uri),
                            dataType: "json"
                        }).done(function (data, textStatus, jqXHR) {
                            options.success(data);
                        }).fail(function (jqXHR, textStatus, errorThrown) {
                            options.error(jqXHR);
                        });
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        options.error(jqXHR);
                    });
                }
            },
            schema: {
                model: {
                    id: "Id",
                    fields: {
                        Id: { type: "number", editable: false, nullable: true },
                        Title: { type: "string", validation: { required: true } }
                        Created: { type: "date" },
                        Modified: { type: "date" },
                        CreatedById: { type: "number" },
                        ModifiedById: { type: "number" }
                    }
                }
            },
            pageSize: 10,
            serverPaging: true,
            serverFiltering: true,
            serverSorting: true,
            error: onError
        });

    Also for a create I wanted to re-load the entity. Even though the create response contained the entity, I needed it to expand certain related objects (CreatedBy and ModifiedBy). This seems to work now. Next: get batch editing working.
  6. Remco
    Remco avatar
    31 posts
    Member since:
    Sep 2012

    Posted 21 Sep 2012 Link to this post

    I managed to get batch editing working for the SharePoint 2010 list odata/rest service with the kendo ui datasource using the Microsoft datajs library (http://datajs.codeplex.com/). 
  7. Toby
    Toby avatar
    20 posts
    Member since:
    Nov 2010

    Posted 21 Sep 2012 Link to this post

    That is stirling work Sir!

    I hadn't had any time to work on the CRUD operations as my client only needed read operations, but this will be very useful
  8. Clint
    Clint avatar
    56 posts
    Member since:
    Feb 2011

    Posted 26 Sep 2012 Link to this post

    Remco, can you give us an example of how you used datajs and Sharepoint?  Obviously the read is pretty easy by itself, but how did you integrate into the kendoui datasource?
  9. Remco
    Remco avatar
    31 posts
    Member since:
    Sep 2012

    Posted 27 Sep 2012 Link to this post

    I wrote some helper function which allow you to integrate datajas with the kendoui datasource as follows:



    categoriesDataSource = new kendo.data.DataSource({
        type: "odata",
        transport: {
            read: function (options) {
                var url ="/_vti_bin/listdata.svc/Categories";
                read(url).done(options.success).fail(options.error);
            },
            update: function (options) {
                requestBatchUpdate(options.data.models, "Categories({0})").done(options.success).fail(options.error);
            },
            destroy: function (options) {
                requestBatchDestroy(options.data.models, "Categories({0})").done(options.success).fail(options.error);
            },
            create: function (options) {
                requestBatchCreate(options.data.models, "Categories").done(options.success).fail(options.error);
            }
        },
        schema: {
            model: Category
        },
        batch: true,
        error: onError
    });


    the helper functions are (there are batch and non-batch versions of create, update and destroy):



    function read(url) {
        var deferred = new $.Deferred();
        OData.read(url, function (data, response) {
            deferred.resolve({ d: data });
        }, function (error) {
            deferred.reject(error);
        });
        return deferred;
    }
     
    function requestUpdate(model, selector) {
        var deferred = new $.Deferred();
        OData.request({
            requestUri: model.__metadata.uri,
            method: "POST",
            data: selector ? selector(model) : model,
            headers: {
                "If-Match": model.__metadata.etag,
                "X-HTTP-Method": 'MERGE'
            }
        }, function (data, response) {
            model.__metadata.etag = response.headers.ETag;
            deferred.resolve({ d: model });
        }, function (error) {
            deferred.reject(error);
        });
        return deferred;
    }
     
    function requestDestroy(model) {
        var deferred = new $.Deferred();
        OData.request({
            requestUri: model.__metadata.uri,
            method: "DELETE",
            headers: {
                "If-Match": model.__metadata.etag
            }
        }, function (data, response) {
            deferred.resolve(null);
        }, function (error) {
            deferred.reject(error);
        });
        return deferred;
    }
     
    function requestCreate(model, requestUri, selector) {
        var deferred = new $.Deferred();
        OData.request({
            requestUri: requestUri,
            method: "POST",
            data: selector ? selector(model) : model
        }, function (data, response) {
            deferred.resolve({ d: data });
        }, function (error) {
            deferred.reject(error);
        });
        return deferred;
    }
     
    function requestBatchUpdate(models, requestUriFormat, selector) {
        var deferred = new $.Deferred();
        OData.request({
            requestUri: "/_vti_bin/listdata.svc/$batch",
            method: "POST",
            data: getUpdateChangeRequests(models, requestUriFormat, selector)
        },
            function (data, response) {
                if (data.__batchResponses[0].__changeResponses[0].response) {
                    deferred.reject({ response: data.__batchResponses[0].__changeResponses[0].response });
                } else {
                    $.each(models, function (index, model) {
                        model.__metadata.etag = data.__batchResponses[0].__changeResponses[index].headers.ETag;
                    });
     
                    deferred.resolve({ d: { results: models } });
                }
            },
            function (error) {
                deferred.reject(error);
            },
            OData.batchHandler);
     
        return deferred;
    }
     
    function getUpdateChangeRequests(models, requestUriFormat, selector) {
        var changeRequests = [];
        $.each(models, function (index, model) {
            changeRequests.push({
                requestUri: kendo.format(requestUriFormat, model.Id),
                method: "MERGE",
                data: selector ? selector(model) : model,
                headers: {
                    "If-Match": model.__metadata.etag
                }
            });
        });
        return {
            __batchRequests: [
                {
                    __changeRequests: changeRequests
                }
            ]
        };
    }
     
    function requestBatchDestroy(models, requestUriFormat) {
        var deferred = new $.Deferred();
        OData.request({
            requestUri: "/_vti_bin/listdata.svc/$batch",
            method: "POST",
            data: getDestroyChangeRequests(models, requestUriFormat)
        },
            function (data, response) {
                if (data.__batchResponses[0].__changeResponses[0].response) {
                    deferred.reject({ response: data.__batchResponses[0].__changeResponses[0].response });
                } else {
                    deferred.resolve(null);
                }
            },
            function (error) {
                deferred.reject(error);
            },
            OData.batchHandler);
     
        return deferred;
    }
     
    function getDestroyChangeRequests(models, requestUriFormat) {
        var changeRequests = [];
        $.each(models, function (index, model) {
            changeRequests.push({
                requestUri: kendo.format(requestUriFormat, model.Id),
                method: "DELETE",
                headers: {
                    "If-Match": model.__metadata.etag
                }
            });
        });
        return {
            __batchRequests: [
                {
                    __changeRequests: changeRequests
                }
            ]
        };
    }
     
    function requestBatchCreate(models, requestUri, selector) {
        var deferred = new $.Deferred();
        OData.request({
            requestUri: "/_vti_bin/listdata.svc/$batch",
            method: "POST",
            data: getCreateChangeRequests(models, requestUri, selector)
        },
            function (data, response) {
                if (data.__batchResponses[0].__changeResponses[0].response) {
                    deferred.reject({ response: data.__batchResponses[0].__changeResponses[0].response });
                } else {
                    var results = $.map(data.__batchResponses[0].__changeResponses, function (changeResponse, index) {
                        return changeResponse.data;
                    });
     
                    deferred.resolve({ d: { results: results } });
                }
            },
            function (error) {
                deferred.reject(error);
            },
            OData.batchHandler);
     
        return deferred;
    }
     
    function getCreateChangeRequests(models, requestUri, selector) {
        var changeRequests = [];
        $.each(models, function (index, model) {
            changeRequests.push({
                requestUri: requestUri,
                method: "POST",
                data: selector ? selector(model) : model
            });
        });
        return {
            __batchRequests: [
                {
                    __changeRequests: changeRequests
                }
            ]
        };
    }
  10. Clint
    Clint avatar
    56 posts
    Member since:
    Feb 2011

    Posted 27 Sep 2012 Link to this post

    Remco, sorry to be a bother, but what does new$ refer to?

  11. Remco
    Remco avatar
    31 posts
    Member since:
    Sep 2012

    Posted 27 Sep 2012 Link to this post

    There is a space between new and $.deferred(). See http://api.jquery.com/category/deferred-object/
  12. Clint
    Clint avatar
    56 posts
    Member since:
    Feb 2011

    Posted 27 Sep 2012 Link to this post

    BAHAHAHAHA!



    Ever have one of those days?  Silly me.
  13. Remco
    Remco avatar
    31 posts
    Member since:
    Sep 2012

    Posted 27 Sep 2012 Link to this post

    I think when you subscribe to this thread by email the spaces don't all appear in the email. It's better to view the code in a web browser.
  14. Dhanavanth Venkatesan
    Dhanavanth Venkatesan avatar
    1 posts
    Member since:
    Nov 2007

    Posted 07 Feb 2014 in reply to Remco Link to this post

    This is an awesome solution! Would you be able to attach the solution project to this post? It would be easy for us to incorporate it?
    Thanks in advance!

    Dan
  15. Jason
    Jason avatar
    1 posts
    Member since:
    Mar 2015

    Posted 01 Jun 2015 in reply to Remco Link to this post

    would love to see the final result of this effort. I tried to get this to work on my dev box, to no avail. 
Back to Top
Kendo UI is VS 2017 Ready