forked from lstak/Backbone.SharePoint
-
Notifications
You must be signed in to change notification settings - Fork 2
/
backbone-sharepoint.odata.js
233 lines (178 loc) · 7.21 KB
/
backbone-sharepoint.odata.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/******************************************************************
* Backbone.SharePoint OData proxy
*
* Author: Luc Stakenborg
* Date: Mar 2, 2012
*
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
* Copyright (c) 2012, Luc Stakenborg, Oxida B.V.
******************************************************************
*/
(function (Backbone, _, $) {
// SharePoint ListData service
var LISTDATA_SERVICE = '_vti_bin/ListData.svc',
url;
// calculate url based on site, list and (optional) id
url = function (options) {
var site = options.site,
list = options.list,
id = options.id,
// remove leading and trailing forward slashes from the site path
path = site.replace(/^\/+|\/+$/g, ''),
url = (path ? '/' + path : '') + '/' + LISTDATA_SERVICE + '/' + list +
(id ? '(' + encodeURIComponent(id) + ')' : '');
return url;
};
Backbone.SP = {};
Backbone.SP.Item = Backbone.Model.extend({
// the id attribute of a SharePoint item. Please note capital I
idAttribute: 'Id',
// the SharePoint site on the current server. By default: root
site: '',
initialize: function () {
this._changeSet = {};
this.bind('change', this._updateChangeSet);
},
_updateChangeSet: function () {
var changedAttributes = this.changedAttributes();
// if attributes are set due to server response, the response will contain __metadata
if (changedAttributes.__metadata) {
this._changeSet = {};
} else {
_.extend(this._changeSet, this.changedAttributes());
}
},
url: function () {
var options = {
site: this.site,
list: this.list
};
if (!this.isNew()) {
options.id = this.id;
}
return url(options);
},
unset: function (attr, options) {
var result = Backbone.Model.prototype.unset.call(this, attr, options);
if (result) {
delete this._changeSet[attr];
}
return result;
},
clear: function (attr, options) {
var result = Backbone.Model.prototype.clear.call(this, attr, options);
if (result) {
this._changeSet = {};
}
return result;
},
sync: function (method, model, options) {
var metadata = model.get("__metadata"),
methodMap = {
'create': 'POST',
// OData requires MERGE for partial updates
// We will use Method tunneling throug POST because
// MERGE isn't supported by IE7 + IE8
'update': 'POST',
'delete': 'DELETE',
'read': 'GET'
},
type = methodMap[method],
// Default JSON-request options.
params = _.extend({
type: type,
dataType: 'json',
processData: (type === 'GET')
}, options);
// Ensure that we have a URL.
if (!params.url) {
params.url = model.url();
}
// Ensure that we have the appropriate request data.
if (!params.data && model && (method === 'create' || method === 'update')) {
params.contentType = 'application/json';
if (method === 'create') {
params.data = JSON.stringify(model.toJSON());
}
if (method === 'update') {
params.data = JSON.stringify(model._changeSet || {});
params.headers = {
// header required for Method tunneling
'X-HTTP-Method': 'MERGE',
// header required for concurrency control
'If-Match': metadata ? metadata.etag : '*'
};
}
}
// transfer special url parameters like select and
// orderby to the params.data hash
if (method === 'read') {
params.data = params.data || {};
_(['filter', 'select', 'orderby',
'top', 'skip', 'expand',
'inlinecount'])
.each(function (keyword) {
if (options[keyword]) {
params.data['$' + keyword] = options[keyword];
}
});
}
// Create a success handler to:
// (1) set etag
// (2) normalize the response, so a model.fetch() does not require a parse()
var success = options.success;
params.success = function (resp, status, xhr) {
// OData responds with an updated Etag
var etag = xhr.getResponseHeader('Etag');
// always clear changeSet after a server response
model._changeSet = {};
// Instead of passing resp, we'll pass resp.d
// make sure we cover 204 response (resp is empty) on Delete and Update
// This way we don't need to override the model.parse() method
if (success) {
if (Backbone.VERSION === '0.9.9' || Backbone.VERSION === '0.9.10') {
success(model, resp && resp.d, options);
} else {
success(resp && resp.d, status, xhr);
}
}
if (etag) {
// Backbone doesn't support setting/getting nested attributes
// Updating etag attribute directly instead
model.attributes.__metadata.etag = etag;
}
};
var error = options.error;
params.error = function (xhr, status, errorText) {
if (error) {
// Include the error text in xhr so it's available to the callback
if (errorText) {
xhr.errorText = errorText;
}
error(model, xhr, options);
}
};
// Make the request.
return $.ajax(params);
}
});
Backbone.SP.List = Backbone.Collection.extend({
url: function () {
// use the Model's url method, if available
if (this.model) { return this.model.prototype.url(); }
// otherwise use site and list settings of this collection
return url({ site: this.site, list: this.list });
},
sync: function (method, model, options) {
return this.model.prototype.sync(method, model, options);
},
parse: function (response) {
if (response.__count) {
this._count = parseInt(response.__count, 10);
} else {
delete this._count;
}
return response.results;
}
});
} (Backbone, _, jQuery));