Merge pull request #7600 from tchak/fix-form-event-listeners
fix(dossier): decoralate dropdown behaviour from autosave
This commit is contained in:
commit
7578c278a4
8 changed files with 195 additions and 92 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
100
app/javascript/controllers/champ_dropdown_controller.ts
Normal file
100
app/javascript/controllers/champ_dropdown_controller.ts
Normal 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;
|
||||
}
|
|
@ -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?,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
61
spec/helpers/champ_helper_spec.rb
Normal file
61
spec/helpers/champ_helper_spec.rb
Normal 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
|
Loading…
Reference in a new issue