Merge pull request #5497 from betagouv/fix-autosave-error

Usager : correction d'erreurs lors de l'enregistrement automatique d'un dossier en brouillon après la suppression d'un bloc répétable
This commit is contained in:
Pierre de La Morinerie 2020-08-25 15:54:49 +02:00 committed by GitHub
commit 8b82bf3b88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 74 additions and 35 deletions

View file

@ -163,7 +163,7 @@ module Users
respond_to do |format|
format.html { render :brouillon }
format.json { render json: {}, status: :ok }
format.js { render :brouillon }
end
end

View file

@ -62,7 +62,11 @@ module ApplicationHelper
script = "(function() {";
script << "var el = document.querySelector('#{selector}');"
method = (inner ? "el.innerHTML = ''" : "el.parentNode.removeChild(el)")
script << "if (el) { setTimeout(function() { #{method}; }, #{timeout}); }";
if timeout.present? && timeout > 0
script << "if (el) { setTimeout(function() { #{method}; }, #{timeout}); }"
else
script << "if (el) { #{method} };"
end
script << "})();"
# rubocop:disable Rails/OutputSafety
raw(script);

View file

@ -1,8 +1,9 @@
import { delegate } from '@utils';
import { delegate, fire } from '@utils';
const CHAMP_SELECTOR = '.editable-champ';
const BUTTON_SELECTOR = '.button.remove-row';
const DESTROY_INPUT_SELECTOR = 'input[type=hidden][name*=_destroy]';
const CHAMP_SELECTOR = '.editable-champ';
const DOM_ID_INPUT_SELECTOR = 'input[type=hidden][name*=deleted_row_dom_ids]';
delegate('click', BUTTON_SELECTOR, (evt) => {
evt.preventDefault();
@ -13,10 +14,20 @@ delegate('click', BUTTON_SELECTOR, (evt) => {
input.disabled = false;
input.value = true;
}
row.querySelector(DOM_ID_INPUT_SELECTOR).disabled = false;
for (let champ of row.querySelectorAll(CHAMP_SELECTOR)) {
champ.remove();
}
evt.target.remove();
row.classList.remove('row');
// We could debounce the autosave request, so that row removal would be batched
// with the next changes.
// However *adding* a new repetition row isn't debounced (changes are immediately
// effective server-side).
// So, to avoid ordering issues, enqueue an autosave request as soon as the row
// is removed.
fire(row, 'autosave:trigger');
});

View file

@ -1,4 +1,4 @@
import { fire, timeoutable } from '@utils';
import { ajax, fire, timeoutable } from '@utils';
// Manages a queue of Autosave operations,
// and sends `autosave:*` events to indicate the state of the requests.
@ -34,21 +34,20 @@ export default class AutoSaveController {
return reject(formDataError);
}
const fetchOptions = {
method: form.method,
body: formData,
credentials: 'same-origin',
headers: { Accept: 'application/json' }
const params = {
url: form.action,
type: form.method,
data: formData,
dataType: 'script'
};
return window.fetch(form.action, fetchOptions).then((response) => {
if (response.ok) {
return ajax(params)
.then(({ response }) => {
resolve(response);
} else {
const message = `Network request failed (${response.status}, "${response.statusText}")`;
reject(new Error(message));
}
});
})
.catch((error) => {
reject(error);
});
});
// Time out the request after a while, to avoid recent requests not starting

View file

@ -16,26 +16,40 @@ const AUTOSAVE_STATUS_VISIBLE_DURATION = gon.autosave.status_visible_duration;
// Create a controller responsible for queuing autosave operations.
const autoSaveController = new AutoSaveController();
// Whenever a 'change' event is triggered on one of the form inputs, try to autosave.
const formSelector = 'form#dossier-edit-form.autosave-enabled';
const formInputsSelector = `${formSelector} input:not([type=file]), ${formSelector} select, ${formSelector} textarea`;
delegate(
'change',
formInputsSelector,
debounce(() => {
const form = document.querySelector(formSelector);
autoSaveController.enqueueAutosaveRequest(form);
}, AUTOSAVE_DEBOUNCE_DELAY)
);
delegate('click', '.autosave-retry', () => {
const form = document.querySelector(formSelector);
function enqueueAutosaveRequest() {
const form = document.querySelector(FORM_SELECTOR);
autoSaveController.enqueueAutosaveRequest(form);
}
//
// Whenever a 'change' event is triggered on one of the form inputs, try to autosave.
//
const FORM_SELECTOR = 'form#dossier-edit-form.autosave-enabled';
const INPUTS_SELECTOR = `${FORM_SELECTOR} input:not([type=file]), ${FORM_SELECTOR} select, ${FORM_SELECTOR} textarea`;
const RETRY_BUTTON_SELECTOR = '.autosave-retry';
// When an autosave is requested programatically, auto-save the form immediately
addEventListener('autosave:trigger', (event) => {
const form = event.target.closest('form');
if (form && form.classList.contains('autosave-enabled')) {
enqueueAutosaveRequest();
}
});
// When the "Retry" button is clicked, auto-save the form immediately
delegate('click', RETRY_BUTTON_SELECTOR, enqueueAutosaveRequest);
// When an input changes, batches changes for N seconds, then auto-save the form
delegate(
'change',
INPUTS_SELECTOR,
debounce(enqueueAutosaveRequest, AUTOSAVE_DEBOUNCE_DELAY)
);
//
// Display some UI during the autosave
//
addEventListener('autosave:enqueue', () => {
disable(document.querySelector('button.autosave-retry'));

View file

@ -1,7 +1,11 @@
- champs = champ.rows.last
- if champs.present?
- index = (champ.rows.size - 1) * champs.size
%div{ class: "row row-#{champs.first.row}" }
- row_dom_id = "row-#{SecureRandom.hex(4)}"
%div{ class: "row row-#{champs.first.row}", id: row_dom_id }
-# Tell the controller which DOM element should be removed once the row deletion is successful
= hidden_field_tag 'deleted_row_dom_ids[]', row_dom_id, disabled: true
- champs.each.with_index(index) do |champ, index|
= fields_for "#{attribute}[#{index}]", champ do |form|
= render partial: "shared/dossiers/editable_champs/editable_champ", locals: { champ: champ, form: form }

View file

@ -1,6 +1,10 @@
%div{ class: "repetition-#{form.index}" }
- champ.rows.each do |champs|
%div{ class: "row row-#{champs.first.row}" }
- row_dom_id = "row-#{SecureRandom.hex(4)}"
%div{ class: "row row-#{champs.first.row}", id: row_dom_id }
-# Tell the controller which DOM element should be removed once the row deletion is successful
= hidden_field_tag 'deleted_row_dom_ids[]', row_dom_id, disabled: true
- champs.each do |champ|
= form.fields_for :champs, champ do |form|
= render partial: 'shared/dossiers/editable_champs/editable_champ', locals: { champ: form.object, form: form }

View file

@ -0,0 +1,3 @@
<% (params['deleted_row_dom_ids'] || []).each do |deleted_row_dom_id| %>
<%= remove_element('#' + deleted_row_dom_id) %>
<% end %>