Newer
Older
import Overlay from './overlay.js';
class AbstractAPI
{
static get supportedMethods() {
return ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE'];
}
// Helper function that normalizes options
static adjustOptions (options = {}) {
return Object.assign({}, {
method: 'get',
parameters: {},
headers: {},
data: {},
overlay: false,
async: false,
before: false
}, options || {});
}
constructor (base_url) {
if (this.constructor === AbstractAPI) {
throw new TypeError('You should not instantiate the abstract api');
}
this.total_requests = 0;
this.request_count = 0;
this.queue = [];
this.base_url = base_url;
}

Jan-Hendrik Willms
committed
encodeData (data, method) {
if (data instanceof Function) {
data = data();
}
return data;
}
request (url, options = {}) {
// Normalize parameters
if (Array.isArray(url)) {
// Remove empty trailing chunks
while (url[url.length - 1] === '') {
delete url[url.length - 1];
}
// Convert array to string
url = url.join('/');
}
options = this.constructor.adjustOptions(options);
var deferred;

Jan-Hendrik Willms
committed
const request = this.#createRequest(url, options);
if (options.async && this.request_count > 0) {
// Request should be sent asynchronous after every other request
// is finished. The configuration for this particular request is
// stored in a deferred which is then queued for execution.
deferred = $.Deferred();
deferred.then(() => this.request(url, options));
this.queue.push(deferred);
} else if (options.before instanceof Function && !options.before()) {
// A before function was defined and returned false, so the request
// is canceled
deferred = $.Deferred((dfd) => dfd.reject());
} else {
// Increase request counters, show overlay if neccessary
if (this.request_count === 0 && options.overlay) {
Overlay.show(true, null, true);
}
this.request_count += 1;
this.total_requests += 1;
// Actual request

Jan-Hendrik Willms
committed
deferred = $.ajax(request.url, {
contentType: options.contentType || 'application/x-www-form-urlencoded; charset=UTF-8',
method: options.method.toUpperCase(),

Jan-Hendrik Willms
committed
data: this.encodeData(request.data, options.method.toUpperCase()),
headers: options.headers
}).always(() => {
// Decrease request counter, remove overlay if neccessary
this.request_count -= 1;
if (this.request_count === 0 && options.overlay) {
Overlay.hide();
}
});
}
return deferred.always(() => {
// Check if any request was queued
if (this.request_count === 0 && this.queue.length > 0) {
this.queue.shift().resolve();
}
}).promise();

Jan-Hendrik Willms
committed
#createRequest(url, options) {
const hasBody = ['post', 'put', 'patch'].includes(options.method.toLowerCase());
const query = hasBody ? '' : `?${this.convertDataToRequestParameters(options.data)}`;
return {
url: STUDIP.URLHelper.getURL(`${this.base_url}/${url}${query}`, {}, true),
data: hasBody ? options.data : {},
};
}
convertDataToRequestParameters(data, prefix = '') {
return Object.entries(data).filter(([key, value]) => {
return value !== null;
}).map(([key, value]) => {

Jan-Hendrik Willms
committed
const name = prefix ? `${prefix}[${key}]` : `${key}`;
if (value.constructor?.name === 'Object') {

Jan-Hendrik Willms
committed
return this.convertDataToRequestParameters(value, name);
} else {
return `${name}=${value}`;
}
}).join('&');
}
}
// Create shortcut methods for easier access by method
AbstractAPI.supportedMethods.forEach((method) => {
AbstractAPI.prototype[method] = function (url, options = {}) {
options = this.constructor.adjustOptions(options);
options.method = method;
return this.request.call(this, url, options);
};
});
export default AbstractAPI;