Merge pull request #7600 from tchak/fix-form-event-listeners

fix(dossier): decoralate dropdown behaviour from autosave
This commit is contained in:
Paul Chavard 2022-07-21 18:46:19 +02:00 committed by GitHub
commit 7578c278a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 195 additions and 92 deletions

View file

@ -40,7 +40,7 @@
.cell
= form.label :drop_down_list_value, "Options de la liste", for: dom_id(type_de_champ, :drop_down_list_value)
= form.text_area :drop_down_list_value, class: 'small-margin small width-100', rows: 7, id: dom_id(type_de_champ, :drop_down_list_value)
- if type_de_champ.drop_down_list_with_other?
- if type_de_champ.simple_drop_down_list?
.cell
= form.label :drop_down_other, for: dom_id(type_de_champ, :drop_down_other) do
Proposer une option « autre » avec un texte libre

View file

@ -31,11 +31,30 @@ module ChampHelper
champ.dossier&.brouillon? && !champ.repetition?
end
def autosave_controller(champ)
if autosave_available?(champ)
{ controller: 'autosave' }
elsif !champ.repetition? && champ.dossier&.en_construction?
{ controller: 'check-conditions' }
def editable_champ_controller(champ)
if !champ.repetition? && !champ.non_fillable?
# This is an editable champ. Lets find what controllers it might need.
controllers = []
# This is a public champ it can have an autosave controller.
if champ.public?
# This is a champ on dossier in draft state. Activate autosave.
if champ.dossier&.brouillon?
controllers << 'autosave'
# This is a champ on a dossier in en_construction state. Enable conditions checker.
elsif champ.public? && champ.dossier&.en_construction?
controllers << 'check-conditions'
end
end
# This is a dropdown champ. Activate special behaviours it might have.
if champ.simple_drop_down_list? || champ.linked_drop_down_list?
controllers << 'champ-dropdown'
end
if controllers.present?
{ controller: controllers.join(' ') }
end
end
end

View file

@ -4,13 +4,8 @@ import {
isSelectElement,
isCheckboxOrRadioInputElement,
isTextInputElement,
show,
hide,
enable,
disable,
getConfig
} from '@utils';
import { z } from 'zod';
import { ApplicationController } from './application_controller';
import { AutoUpload } from '../shared/activestorage/auto-upload';
@ -87,8 +82,6 @@ export class AutosaveController extends ApplicationController {
isSelectElement(target) ||
isCheckboxOrRadioInputElement(target)
) {
this.toggleOtherInput(target);
this.toggleLinkedSelect(target);
this.enqueueAutosaveRequest();
}
}
@ -206,77 +199,4 @@ export class AutosaveController extends ApplicationController {
}
return inputs.filter((element) => !element.disabled);
}
private toggleOtherInput(target: HTMLSelectElement | HTMLInputElement) {
const parent = target.closest('.editable-champ-drop_down_list');
const inputGroup = parent?.querySelector<HTMLElement>('.drop_down_other');
if (inputGroup) {
const input = inputGroup.querySelector('input');
if (input) {
if (target.value == '__other__') {
show(inputGroup);
input.disabled = false;
} else {
hide(inputGroup);
input.disabled = true;
}
}
}
}
private toggleLinkedSelect(target: HTMLSelectElement | HTMLInputElement) {
const secondaryOptions = target.dataset.secondaryOptions;
if (isSelectElement(target) && secondaryOptions) {
const parent = target.closest('.editable-champ-linked_drop_down_list');
const secondary = parent?.querySelector<HTMLSelectElement>(
'select[data-secondary]'
);
if (secondary) {
const options = parseOptions(secondaryOptions);
this.setSecondaryOptions(secondary, options[target.value]);
}
}
}
private setSecondaryOptions(
secondarySelectElement: HTMLSelectElement,
options: string[]
) {
const wrapper = secondarySelectElement.closest('.secondary');
const hidden = wrapper?.nextElementSibling as HTMLInputElement | null;
secondarySelectElement.innerHTML = '';
if (options.length) {
disable(hidden);
if (secondarySelectElement.required) {
secondarySelectElement.appendChild(makeOption(''));
}
for (const option of options) {
secondarySelectElement.appendChild(makeOption(option));
}
secondarySelectElement.selectedIndex = 0;
enable(secondarySelectElement);
show(wrapper);
} else {
hide(wrapper);
disable(secondarySelectElement);
enable(hidden);
}
}
}
const SecondaryOptions = z.record(z.string().array());
function parseOptions(options: string) {
return SecondaryOptions.parse(JSON.parse(options));
}
function makeOption(option: string) {
const element = document.createElement('option');
element.textContent = option;
element.value = option;
return element;
}

View file

@ -0,0 +1,100 @@
import {
isSelectElement,
isCheckboxOrRadioInputElement,
show,
hide,
enable,
disable
} from '@utils';
import { z } from 'zod';
import { ApplicationController } from './application_controller';
export class ChampDropdownController extends ApplicationController {
connect() {
this.on('change', (event) => this.onChange(event));
}
private onChange(event: Event) {
const target = event.target as HTMLInputElement;
if (!target.disabled) {
if (isSelectElement(target) || isCheckboxOrRadioInputElement(target)) {
this.toggleOtherInput(target);
this.toggleLinkedSelect(target);
}
}
}
private toggleOtherInput(target: HTMLSelectElement | HTMLInputElement) {
const parent = target.closest('.editable-champ-drop_down_list');
const inputGroup = parent?.querySelector<HTMLElement>('.drop_down_other');
if (inputGroup) {
const input = inputGroup.querySelector('input');
if (input) {
if (target.value == '__other__') {
show(inputGroup);
input.disabled = false;
} else {
hide(inputGroup);
input.disabled = true;
}
}
}
}
private toggleLinkedSelect(target: HTMLSelectElement | HTMLInputElement) {
const secondaryOptions = target.dataset.secondaryOptions;
if (isSelectElement(target) && secondaryOptions) {
const parent = target.closest('.editable-champ-linked_drop_down_list');
const secondary = parent?.querySelector<HTMLSelectElement>(
'select[data-secondary]'
);
if (secondary) {
const options = parseOptions(secondaryOptions);
this.setSecondaryOptions(secondary, options[target.value]);
}
}
}
private setSecondaryOptions(
secondarySelectElement: HTMLSelectElement,
options: string[]
) {
const wrapper = secondarySelectElement.closest('.secondary');
const hidden = wrapper?.nextElementSibling as HTMLInputElement | null;
secondarySelectElement.innerHTML = '';
if (options.length) {
disable(hidden);
if (secondarySelectElement.required) {
secondarySelectElement.appendChild(makeOption(''));
}
for (const option of options) {
secondarySelectElement.appendChild(makeOption(option));
}
secondarySelectElement.selectedIndex = 0;
enable(secondarySelectElement);
show(wrapper);
} else {
hide(wrapper);
disable(secondarySelectElement);
enable(hidden);
}
}
}
const SecondaryOptions = z.record(z.string().array());
function parseOptions(options: string) {
return SecondaryOptions.parse(JSON.parse(options));
}
function makeOption(option: string) {
const element = document.createElement('option');
element.textContent = option;
element.value = option;
return element;
}

View file

@ -49,6 +49,9 @@ class Champ < ApplicationRecord
:dossier_link?,
:titre_identite?,
:header_section?,
:simple_drop_down_list?,
:linked_drop_down_list?,
:non_fillable?,
:cnaf?,
:dgfip?,
:pole_emploi?,

View file

@ -205,18 +205,18 @@ class TypeDeChamp < ApplicationRecord
])
end
def drop_down_list_with_other?
def simple_drop_down_list?
type_champ == TypeDeChamp.type_champs.fetch(:drop_down_list)
end
def header_section?
type_champ == TypeDeChamp.type_champs.fetch(:header_section)
end
def linked_drop_down_list?
type_champ == TypeDeChamp.type_champs.fetch(:linked_drop_down_list)
end
def header_section?
type_champ == TypeDeChamp.type_champs.fetch(:header_section)
end
def exclude_from_view?
type_champ == TypeDeChamp.type_champs.fetch(:explication)
end

View file

@ -1,4 +1,4 @@
.editable-champ{ class: "editable-champ-#{champ.type_champ} #{champ.visible? ? '' : 'hidden'}", id: champ.input_group_id, data: autosave_controller(champ) }
.editable-champ{ class: "editable-champ-#{champ.type_champ} #{champ.visible? ? '' : 'hidden'}", id: champ.input_group_id, data: editable_champ_controller(champ) }
- if champ.repetition?
%h3.header-subsection= champ.libelle
- if champ.description.present?

View file

@ -0,0 +1,61 @@
describe ChampHelper, type: :helper do
describe "editable_champ_controller" do
let(:dossier) { create(:dossier) }
let(:champ) { create(:champ, dossier: dossier) }
let(:controllers) { [] }
let(:data) { { controller: controllers.join(' ') } }
context 'when an editable champ' do
let(:controllers) { ['autosave'] }
it { expect(editable_champ_controller(champ)).to eq(data) }
end
context 'when a repetition champ' do
let(:champ) { create(:champ_repetition, dossier: dossier) }
it { expect(editable_champ_controller(champ)).to eq(nil) }
end
context 'when a private champ' do
let(:champ) { create(:champ, dossier: dossier, private: true) }
it { expect(editable_champ_controller(champ)).to eq(nil) }
end
context 'when a dossier is en_construction' do
let(:controllers) { ['check-conditions'] }
let(:dossier) { create(:dossier, :en_construction) }
it { expect(editable_champ_controller(champ)).to eq(data) }
context 'when a public dropdown champ' do
let(:controllers) { ['check-conditions', 'champ-dropdown'] }
let(:champ) { create(:champ_drop_down_list, dossier: dossier) }
it { expect(editable_champ_controller(champ)).to eq(data) }
end
context 'when a private dropdown champ' do
let(:controllers) { ['champ-dropdown'] }
let(:champ) { create(:champ_drop_down_list, dossier: dossier, private: true) }
it { expect(editable_champ_controller(champ)).to eq(data) }
end
end
context 'when a public dropdown champ' do
let(:controllers) { ['autosave', 'champ-dropdown'] }
let(:champ) { create(:champ_drop_down_list, dossier: dossier) }
it { expect(editable_champ_controller(champ)).to eq(data) }
end
context 'when a private dropdown champ' do
let(:controllers) { ['champ-dropdown'] }
let(:champ) { create(:champ_drop_down_list, dossier: dossier, private: true) }
it { expect(editable_champ_controller(champ)).to eq(data) }
end
end
end