autosave: remove the repetition row after deletion
Before, when autosaving a draft, removing a repetition row would send `_destroy` inputs to the controller – but not remove the row from the DOM. This led to the `_destroy` inputs being sent again on the next autosave request, which made the controller raise (because the row fields were already deleted before). To fix this, we let the controller response remove the deleted row(s) from the DOM. Doing it using a controller response avoids the need to keep track of operations on the Javascript side: the controller can easily know which row was just deleted, and emit the relevant changes for the DOM. This keeps the autosave requests robust: even if a request is skipped (e.g. because of a network interruption), the next request will still contain the relevant informations to succeed, and not let the form in an unstable state. Fix #5470
This commit is contained in:
parent
e157a289e1
commit
96037069ff
6 changed files with 30 additions and 17 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
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { delegate } 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,6 +14,8 @@ 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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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