forked from dojo/dojo1-dstore
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Request.js
296 lines (259 loc) · 9.33 KB
/
Request.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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
define([
'dojo/request',
'dojo/_base/lang',
'dojo/_base/array',
'dojo/json',
'dojo/_base/declare',
'./Store',
'./QueryResults'
], function (request, lang, arrayUtil, JSON, declare, Store, QueryResults) {
var push = [].push;
return declare(Store, {
// summary:
// This is a basic store for RESTful communicating with a server through JSON
// formatted data. It extends dstore/Store.
constructor: function () {
// summary:
// This is a basic store for RESTful communicating with a server through JSON
// formatted data.
// options: dstore/JsonRest
// This provides any configuration information that will be mixed into the store
this.headers || (this.headers = {});
this._targetContainsQueryString = this.target.lastIndexOf('?') >= 0;
},
// headers: Object
// Additional headers to pass in all requests to the server. These can be overridden
// by passing additional headers to calls to the store.
headers: {},
// parse: Function
// This function performs the parsing of the response text from the server. This
// defaults to JSON, but other formats can be parsed by providing an alternate
// parsing function. If you do want to use an alternate format, you will probably
// want to use an alternate stringify function for the serialization of data as well.
// Also, if you want to support parsing a larger set of JavaScript objects
// outside of strict JSON parsing, you can provide dojo/_base/json.fromJson as the parse function
parse: JSON.parse,
// target: String
// The target base URL to use for all requests to the server. This string will be
// prepended to the id to generate the URL (relative or absolute) for requests
// sent to the server
target: '',
// _targetContainsQueryString: Boolean
// A flag indicating whether the target contains a query string
// sortParam: String
// The query parameter to used for holding sort information. If this is omitted, than
// the sort information is included in a functional query token to avoid colliding
// with the set of name/value pairs.
// ascendingPrefix: String
// The prefix to apply to sort property names that are ascending
ascendingPrefix: '+',
// descendingPrefix: String
// The prefix to apply to sort property names that are ascending
descendingPrefix: '-',
// accepts: String
// Defines the Accept header to use on HTTP requests
accepts: 'application/json',
// useRangeHeaders: Boolean
// The indicates if range limits (start and end) should be specified
// a Range header, using items units. If this is set to true, a header
// be included of the form:
// Range: items=start-end
// rangeStartParam: String
// The indicates if range limits (start and end) should be specified
// in a query parameter, and what the start parameter should be.
// This must be used in conjunction with the rangeCountParam
// If this is not specified, the range will
// included with a RQL style limit() parameter
// rangeCountParam: String
// The indicates if range limits (start and end) should be specified
// in a query parameter, and what the count parameter should be.
// This must be used in conjunction with the rangeStartParam
// If this is not specified, the range will
// included with a RQL style limit() parameter
fetch: function (kwArgs) {
var results = this._request(kwArgs);
return new QueryResults(results.data, {
response: results.response
});
},
fetchRange: function (kwArgs) {
var start = kwArgs.start,
end = kwArgs.end,
requestArgs = {};
if (this.useRangeHeaders) {
requestArgs.headers = lang.mixin(this._renderRangeHeaders(start, end), kwArgs.headers);
} else {
requestArgs.queryParams = this._renderRangeParams(start, end);
if (kwArgs.headers) {
requestArgs.headers = kwArgs.headers;
}
}
var results = this._request(requestArgs);
return new QueryResults(results.data, {
totalLength: results.total,
response: results.response
});
},
_request: function (kwArgs) {
kwArgs = kwArgs || {};
// perform the actual query
var headers = lang.delegate(this.headers, { Accept: this.accepts });
if ('headers' in kwArgs) {
lang.mixin(headers, kwArgs.headers);
}
var requestUrl = this._renderUrl(kwArgs.queryParams);
var response = request(requestUrl, {
method: 'GET',
headers: headers
});
var collection = this;
var parsedResponse = response.then(function (response) {
return collection.parse(response);
});
return {
data: parsedResponse.then(function (data) {
// support items in the results
var results = data.items || data;
for (var i = 0, l = results.length; i < l; i++) {
results[i] = collection._restore(results[i], true);
}
return results;
}),
total: parsedResponse.then(function (data) {
// check for a total property
var total = data.total;
if (total > -1) {
// if we have a valid positive number from the data,
// we can use that
return total;
}
// else use headers
return response.response.then(function (response) {
var range = response.getHeader('Content-Range');
return range && (range = range.match(/\/(.*)/)) && +range[1];
});
}),
response: response.response
};
},
_renderFilterParams: function (filter) {
// summary:
// Constructs filter-related params to be inserted into the query string
// returns: String
// Filter-related params to be inserted in the query string
var type = filter.type;
var args = filter.args;
if (!type) {
return [''];
}
if (type === 'string') {
return [args[0]];
}
if (type === 'and' || type === 'or') {
return [arrayUtil.map(filter.args, function (arg) {
// render each of the arguments to and or or, then combine by the right operator
var renderedArg = this._renderFilterParams(arg);
return ((arg.type === 'and' || arg.type === 'or') && arg.type !== type) ?
// need to observe precedence in the case of changing combination operators
'(' + renderedArg + ')' : renderedArg;
}, this).join(type === 'and' ? '&' : '|')];
}
var target = args[1];
if (target) {
if(target._renderUrl) {
// detected nested query, and render the url inside as an argument
target = '(' + target._renderUrl() + ')';
} else if (target instanceof Array) {
target = '(' + target + ')';
}
}
return [encodeURIComponent(args[0]) + '=' + (type === 'eq' ? '' : type + '=') + encodeURIComponent(target)];
},
_renderSortParams: function (sort) {
// summary:
// Constructs sort-related params to be inserted in the query string
// returns: String
// Sort-related params to be inserted in the query string
var sortString = arrayUtil.map(sort, function (sortOption) {
var prefix = sortOption.descending ? this.descendingPrefix : this.ascendingPrefix;
return prefix + encodeURIComponent(sortOption.property);
}, this);
var params = [];
if (sortString) {
params.push(this.sortParam
? encodeURIComponent(this.sortParam) + '=' + sortString
: 'sort(' + sortString + ')'
);
}
return params;
},
_renderRangeParams: function (start, end) {
// summary:
// Constructs range-related params to be inserted in the query string
// returns: String
// Range-related params to be inserted in the query string
var params = [];
if (this.rangeStartParam) {
params.push(
this.rangeStartParam + '=' + start,
this.rangeCountParam + '=' + (end - start)
);
} else {
params.push('limit(' + (end - start) + (start ? (',' + start) : '') + ')');
}
return params;
},
_renderSelectParams: function (properties) {
// summary:
// Constructs range-related params to be inserted in the query string
// returns: String
// Range-related params to be inserted in the query string
var params = [];
if (this.selectParam) {
params.push(this.selectParam + '=' + properties);
} else {
params.push('select(' + properties + ')');
}
return params;
},
_renderQueryParams: function () {
var queryParams = [];
arrayUtil.forEach(this.queryLog, function (entry) {
var type = entry.type,
renderMethod = '_render' + type[0].toUpperCase() + type.substr(1) + 'Params';
if (this[renderMethod]) {
push.apply(queryParams, this[renderMethod].apply(this, entry.normalizedArguments));
} else {
console.warn('Unable to render query params for "' + type + '" query', entry);
}
}, this);
return queryParams;
},
_renderUrl: function (requestParams) {
// summary:
// Constructs the URL used to fetch the data.
// returns: String
// The URL of the data
var queryParams = this._renderQueryParams(),
requestUrl = this.target;
if (requestParams) {
push.apply(queryParams, requestParams);
}
if (queryParams.length > 0) {
requestUrl += (this._targetContainsQueryString ? '&' : '?') + queryParams.join('&');
}
return requestUrl;
},
_renderRangeHeaders: function (start, end) {
// summary:
// Applies a Range header if this collection incorporates a range query
// headers: Object
// The headers to which a Range property is added
var value = 'items=' + start + '-' + (end - 1);
return {
'Range': value,
'X-Range': value //set X-Range for Opera since it blocks "Range" header
};
}
});
});