demarches-normaliennes/app/javascript/new_design/dossiers/auto-save-controller.js
2021-09-07 16:45:52 -05:00

97 lines
3 KiB
JavaScript

import { ajax, fire, timeoutable } from '@utils';
// Manages a queue of Autosave operations,
// and sends `autosave:*` events to indicate the state of the requests:
//
// - autosave:enqueue () when an autosave request has been enqueued
// - autosave:end ({ response, statusText, xhr }) when an autosave request finished successfully
// - autosave:failure (Error) when an autosave request failed
//
export default class AutoSaveController {
constructor() {
this.timeoutDelay = 60000; // 1mn
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
// to overwrite newer data if the server does not respond in order.)
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);
}
const params = {
url: form.action,
type: form.method,
data: formData,
dataType: 'script'
};
return ajax(params)
.then(({ response }) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
// Time out the request after a while, to avoid recent requests not starting
// because an older one is stuck.
return timeoutable(autosavePromise, this.timeoutDelay);
}
// 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(
'input[type="file"]:not([disabled]), .editable-champ-piece_justificative input:not([disabled])'
);
fileInputs.forEach((fileInput) => (fileInput.disabled = true));
// 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
fileInputs.forEach((fileInput) => (fileInput.disabled = false));
}
}
_didEnqueue() {
fire(document, 'autosave:enqueue');
}
_didSucceed(response) {
fire(document, 'autosave:end', response);
}
_didFail(error) {
fire(document, 'autosave:error', error);
}
}