diff --git a/resources/assets/javascripts/bootstrap/consultations.js b/resources/assets/javascripts/bootstrap/consultations.js index ef79d9ca1e6760be1c2c493c2add338636f3d895..51ffa8530859a373efa7ade3f23c81123a9233c7 100644 --- a/resources/assets/javascripts/bootstrap/consultations.js +++ b/resources/assets/javascripts/bootstrap/consultations.js @@ -10,9 +10,9 @@ $(document).on('click', '.consultation-delete-check:not(.ignore)', event => { } let requests = ids.map(id => { - return STUDIP.jsonapi.GET(`consultation-slots/${id}/bookings`).then(result => result.data.length); + return STUDIP.jsonapi.withPromises().get(`consultation-slots/${id}/bookings`).then(response => response.data.length); }); - $.when(...requests).done((...results) => { + Promise.all(requests).then((...results) => { if (results.some(result => result > 0)) { $(event.target).addClass('ignore').click().removeClass('ignore'); } else { diff --git a/resources/assets/javascripts/init.js b/resources/assets/javascripts/init.js index 2d592be2ca452e85869394e4a1a26aed4730da90..fe20c9ae894c82b70f7dd15373f6c88d2cec6c48 100644 --- a/resources/assets/javascripts/init.js +++ b/resources/assets/javascripts/init.js @@ -38,7 +38,7 @@ import HeaderMagic from './lib/header_magic.js'; import i18n from './lib/i18n.js'; import Instschedule from './lib/instschedule.js'; import InlineEditing from './lib/inline-editing.js'; -import JSONAPI, { jsonapi } from './lib/jsonapi.js'; +import JSONAPI, { jsonapi } from './lib/jsonapi.ts'; import JSUpdater from './lib/jsupdater.js'; import Lightbox from './lib/lightbox.js'; import Markup from './lib/markup.js'; diff --git a/resources/assets/javascripts/lib/abstract-api.js b/resources/assets/javascripts/lib/abstract-api.js index 70a88dbbd60217aa4cc7cba61fcd190283814f7e..95ae015c5f601bb9ebb3e67d5dcffe675397f324 100644 --- a/resources/assets/javascripts/lib/abstract-api.js +++ b/resources/assets/javascripts/lib/abstract-api.js @@ -1,5 +1,20 @@ import Overlay from './overlay.js'; +class APIError extends Error +{ + static createWithJqXhr(message, jqXhr) { + const error = new APIError(message); + error.setJqXhr(jqXhr); + return error; + } + + jqXhr = null; + + setJqXhr(jqXhr) { + this.jqXhr = jqXhr; + } +} + class AbstractAPI { static get supportedMethods() { @@ -118,6 +133,31 @@ class AbstractAPI } }).join('&'); } + + withPromises() { + return new Proxy(this, { + get(target, prop, receiver) { + // This will allow http methods to be written as lowercase when called as methods + // (e.g. api.patch() instead of api.PATCH()) + if (target[prop] === undefined && AbstractAPI.supportedMethods.includes(prop.toUpperCase())) { + prop = prop.toUpperCase(); + } + + // Only handle calls to request methods + if (prop !== 'request') { + return Reflect.get(target, prop, receiver); + } + + // Return a wrapped promise that handles the deferred + return (url, options = {}) => new Promise((resolve, reject) => { + target[prop].apply(target, [url, options]).then( + (response) => resolve(response), + (jqXhr, textStatus, errorThrown) => reject(APIError.createWithJqXhr(errorThrown || textStatus, jqXhr)) + ); + }); + } + }) + } } // Create shortcut methods for easier access by method diff --git a/resources/assets/javascripts/lib/jsonapi.js b/resources/assets/javascripts/lib/jsonapi.ts similarity index 75% rename from resources/assets/javascripts/lib/jsonapi.js rename to resources/assets/javascripts/lib/jsonapi.ts index f3217bc6e6e50cd3ed17763600e364dc4333ad63..80176ccb6ca43091ff0477ed1f445b4d48b600e3 100644 --- a/resources/assets/javascripts/lib/jsonapi.js +++ b/resources/assets/javascripts/lib/jsonapi.ts @@ -3,11 +3,11 @@ import AbstractAPI from './abstract-api.js'; // Actual JSONAPI object class JSONAPI extends AbstractAPI { - constructor(version = 1) { + constructor(version: number = 1) { super(`jsonapi.php/v${version}`); } - encodeData (data, method) { + encodeData (data: any, method: string): any { data = super.encodeData(data); if (['DELETE', 'GET', 'HEAD'].includes(method)) { @@ -21,11 +21,11 @@ class JSONAPI extends AbstractAPI return JSON.stringify(data); } - request (url, options = {}) { + request (url: string, options: any = {}) { options.contentType = 'application/vnd.api+json'; return super.request(url, options); } } export default JSONAPI; -export const jsonapi = new JSONAPI(); +export const jsonapi: JSONAPI = new JSONAPI(); diff --git a/resources/assets/javascripts/studip-ui.js b/resources/assets/javascripts/studip-ui.js index f5812952a850cb886879d775bff288fc7fb8dca0..e611150f7b22113e6ec4e37aa5daeac2d9ff8d71 100644 --- a/resources/assets/javascripts/studip-ui.js +++ b/resources/assets/javascripts/studip-ui.js @@ -33,9 +33,9 @@ import eventBus from "./lib/event-bus.ts"; if (STUDIP.UI.restrictedDates[year] === undefined) { STUDIP.UI.restrictedDates[year] = {}; - STUDIP.jsonapi.GET('holidays', {data: { + STUDIP.jsonapi.withPromises().get('holidays', {data: { 'filter[year]': year - }}).done(response => { + }}).then(response => { // Since PHP will return an empty object as an array, // we need to check if (Array.isArray(response)) { diff --git a/resources/vue/components/MyCoursesTiles.vue b/resources/vue/components/MyCoursesTiles.vue index 73f8aad308bc67f28922bab8a1d17cc97253603b..12b2627258ab00e501846ba0d2add16a44bd3fcd 100644 --- a/resources/vue/components/MyCoursesTiles.vue +++ b/resources/vue/components/MyCoursesTiles.vue @@ -136,7 +136,7 @@ export default { return this.shownColorPicker === course.id; }, changeColor(course, index) { - STUDIP.jsonapi.PATCH(`course-memberships/${course.id}_${this.userid}`, { + STUDIP.jsonapi.withPromises().patch(`course-memberships/${course.id}_${this.userid}`, { data: { data: { type: 'course-memberships', @@ -145,9 +145,9 @@ export default { } } } - }).done(() => { + }).then(() => { course.group = index; - }).always(() => { + }).finally(() => { this.shownColorPicker = null; }); }, diff --git a/resources/vue/store/ContentModulesStore.js b/resources/vue/store/ContentModulesStore.js index 9dc4609630f668a433eccaca598829529a84bc4a..141a1780dc6ec106f4da103293e69da30d71799b 100644 --- a/resources/vue/store/ContentModulesStore.js +++ b/resources/vue/store/ContentModulesStore.js @@ -52,7 +52,7 @@ export default { attributes: { value: view === 'tiles' } }; - return STUDIP.jsonapi.PATCH(`config-values/${documentId}`, { data: { data } }) ; + return STUDIP.jsonapi.withPromises().patch(`config-values/${documentId}`, { data: { data } }) ; }, exchangeModules({ commit, state }, modules) { const order = modules.filter(module => module.active) diff --git a/resources/vue/store/MyCoursesStore.js b/resources/vue/store/MyCoursesStore.js index 08c03891bef1afb3e1139092aa5b5675e79ef36d..af8e0fc464471f9d8540c6b63c34cc04db46ea8f 100644 --- a/resources/vue/store/MyCoursesStore.js +++ b/resources/vue/store/MyCoursesStore.js @@ -71,7 +71,7 @@ export default { attributes: { value: configValue[configKey] } }; - return STUDIP.jsonapi.PATCH(`config-values/${documentId}`, { data: { data } }) + return STUDIP.jsonapi.withPromises().patch(`config-values/${documentId}`, { data: { data } }) }, toggleOpenGroup ({ state, dispatch }, group) { let open_groups = [ ...state.config.open_groups ];