2020-08-20 15:56:41 +02:00
|
|
|
import { ajax, fire, timeoutable } from '@utils';
|
2019-11-19 17:55:30 +01:00
|
|
|
|
2019-11-19 17:55:36 +01:00
|
|
|
// Manages a queue of Autosave operations,
|
|
|
|
// and sends `autosave:*` events to indicate the state of the requests.
|
2020-03-19 17:48:21 +01:00
|
|
|
export default class AutoSaveController {
|
2019-11-19 17:55:30 +01:00
|
|
|
constructor() {
|
2019-11-19 17:55:36 +01:00
|
|
|
this.timeoutDelay = 60000; // 1mn
|
2019-11-19 17:55:30 +01:00
|
|
|
this.latestPromise = Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a new autosave request to the queue.
|
|
|
|
// It will be started after the previous one finishes (to prevent older form data
|
2021-07-20 17:59:47 +02:00
|
|
|
// to overwrite newer data if the server does not respond in order.)
|
2019-11-19 17:55:30 +01:00
|
|
|
enqueueAutosaveRequest(form) {
|
|
|
|
this.latestPromise = this.latestPromise.finally(() => {
|
|
|
|
return this._sendAutosaveRequest(form)
|
|
|
|
.then(this._didSucceed)
|
|
|
|
.catch(this._didFail);
|
|
|
|
});
|
|
|
|
this._didEnqueue();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a fetch request that saves the form.
|
|
|
|
// Returns a promise fulfilled when the request completes.
|
|
|
|
_sendAutosaveRequest(form) {
|
|
|
|
const autosavePromise = new Promise((resolve, reject) => {
|
|
|
|
if (!document.body.contains(form)) {
|
|
|
|
return reject(new Error('The form can no longer be found.'));
|
|
|
|
}
|
|
|
|
|
|
|
|
const [formData, formDataError] = this._formDataForDraft(form);
|
|
|
|
if (formDataError) {
|
|
|
|
formDataError.message = `Error while generating the form data (${formDataError.message})`;
|
|
|
|
return reject(formDataError);
|
|
|
|
}
|
|
|
|
|
2020-08-20 15:56:41 +02:00
|
|
|
const params = {
|
|
|
|
url: form.action,
|
|
|
|
type: form.method,
|
|
|
|
data: formData,
|
|
|
|
dataType: 'script'
|
2019-11-19 17:55:30 +01:00
|
|
|
};
|
|
|
|
|
2020-08-20 15:56:41 +02:00
|
|
|
return ajax(params)
|
|
|
|
.then(({ response }) => {
|
2019-11-19 17:55:30 +01:00
|
|
|
resolve(response);
|
2020-08-20 15:56:41 +02:00
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
reject(error);
|
|
|
|
});
|
2019-11-19 17:55:30 +01:00
|
|
|
});
|
|
|
|
|
2019-11-19 17:55:36 +01:00
|
|
|
// Time out the request after a while, to avoid recent requests not starting
|
|
|
|
// because an older one is stuck.
|
|
|
|
return timeoutable(autosavePromise, this.timeoutDelay);
|
2019-11-19 17:55:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Extract a FormData object of the form fields.
|
|
|
|
_formDataForDraft(form) {
|
|
|
|
// File inputs are handled separatly by ActiveStorage:
|
|
|
|
// exclude them from the draft (by disabling them).
|
|
|
|
// (Also Safari has issue with FormData containing empty file inputs)
|
|
|
|
const fileInputs = form.querySelectorAll(
|
2019-11-25 18:17:49 +01:00
|
|
|
'input[type="file"]:not([disabled]), .editable-champ-piece_justificative input:not([disabled])'
|
2019-11-19 17:55:30 +01:00
|
|
|
);
|
2020-04-30 15:42:29 +02:00
|
|
|
fileInputs.forEach((fileInput) => (fileInput.disabled = true));
|
2019-11-19 17:55:30 +01:00
|
|
|
|
|
|
|
// Generate the form data
|
|
|
|
let formData = null;
|
|
|
|
try {
|
|
|
|
formData = new FormData(form);
|
|
|
|
return [formData, null];
|
|
|
|
} catch (error) {
|
|
|
|
return [null, error];
|
|
|
|
} finally {
|
|
|
|
// Re-enable disabled file inputs
|
2020-04-30 15:42:29 +02:00
|
|
|
fileInputs.forEach((fileInput) => (fileInput.disabled = false));
|
2019-11-19 17:55:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_didEnqueue() {
|
|
|
|
fire(document, 'autosave:enqueue');
|
|
|
|
}
|
|
|
|
|
|
|
|
_didSucceed(response) {
|
|
|
|
fire(document, 'autosave:end', response);
|
|
|
|
}
|
|
|
|
|
|
|
|
_didFail(error) {
|
|
|
|
fire(document, 'autosave:error', error);
|
|
|
|
}
|
|
|
|
}
|