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
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.
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.
13 Answers, 1 is accepted
0
Clint
Top achievements
Rank 1
answered on 09 Jul 2012, 09:46 AM
Full CRUD would be awesome! Let us know it goes.
0
Remco
Top achievements
Rank 1
answered on 14 Sep 2012, 09:37 AM
I almost managed to get all crud operations to work with the SharePoint 2010 list odata service. Here is the code:
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.
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.
0
Remco
Top achievements
Rank 1
answered on 18 Sep 2012, 12:26 PM
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:
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.
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.
0
Remco
Top achievements
Rank 1
answered on 21 Sep 2012, 08:26 AM
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/).
0
Toby
Top achievements
Rank 1
answered on 21 Sep 2012, 09:02 AM
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
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
0
Clint
Top achievements
Rank 1
answered on 26 Sep 2012, 07:33 PM
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?
0
Remco
Top achievements
Rank 1
answered on 27 Sep 2012, 09:13 AM
I wrote some helper function which allow you to integrate datajas with the kendoui datasource as follows:
the helper functions are (there are batch and non-batch versions of create, update and destroy):
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
}
]
};
}
0
Clint
Top achievements
Rank 1
answered on 27 Sep 2012, 04:47 PM
Remco, sorry to be a bother, but what does new$ refer to?
0
Remco
Top achievements
Rank 1
answered on 27 Sep 2012, 05:37 PM
There is a space between new and $.deferred(). See http://api.jquery.com/category/deferred-object/
0
Clint
Top achievements
Rank 1
answered on 27 Sep 2012, 05:38 PM
BAHAHAHAHA!
Ever have one of those days? Silly me.
Ever have one of those days? Silly me.
0
Remco
Top achievements
Rank 1
answered on 27 Sep 2012, 07:02 PM
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.
0
Dhanavanth Venkatesan
Top achievements
Rank 1
answered on 07 Feb 2014, 04:31 PM
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
Thanks in advance!
Dan
0
Jason
Top achievements
Rank 1
answered on 01 Jun 2015, 05:18 PM
would love to see the final result of this effort. I tried to get this to work on my dev box, to no avail.