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:
commit
8b82bf3b88
8 changed files with 74 additions and 35 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
3
app/views/users/dossiers/brouillon.js.erb
Normal file
3
app/views/users/dossiers/brouillon.js.erb
Normal file
|
@ -0,0 +1,3 @@
|
|||
<% (params['deleted_row_dom_ids'] || []).each do |deleted_row_dom_id| %>
|
||||
<%= remove_element('#' + deleted_row_dom_id) %>
|
||||
<% end %>
|
Loading…
Reference in a new issue