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
|
.cell
|
||||||
= form.label :drop_down_list_value, "Options de la liste", for: dom_id(type_de_champ, :drop_down_list_value)
|
= 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)
|
= 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
|
.cell
|
||||||
= form.label :drop_down_other, for: dom_id(type_de_champ, :drop_down_other) do
|
= form.label :drop_down_other, for: dom_id(type_de_champ, :drop_down_other) do
|
||||||
Proposer une option « autre » avec un texte libre
|
Proposer une option « autre » avec un texte libre
|
||||||
|
|
|
@ -31,11 +31,30 @@ module ChampHelper
|
||||||
champ.dossier&.brouillon? && !champ.repetition?
|
champ.dossier&.brouillon? && !champ.repetition?
|
||||||
end
|
end
|
||||||
|
|
||||||
def autosave_controller(champ)
|
def editable_champ_controller(champ)
|
||||||
if autosave_available?(champ)
|
if !champ.repetition? && !champ.non_fillable?
|
||||||
{ controller: 'autosave' }
|
# This is an editable champ. Lets find what controllers it might need.
|
||||||
elsif !champ.repetition? && champ.dossier&.en_construction?
|
controllers = []
|
||||||
{ controller: 'check-conditions' }
|
|
||||||
|
# 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,8 @@ import {
|
||||||
isSelectElement,
|
isSelectElement,
|
||||||
isCheckboxOrRadioInputElement,
|
isCheckboxOrRadioInputElement,
|
||||||
isTextInputElement,
|
isTextInputElement,
|
||||||
show,
|
|
||||||
hide,
|
|
||||||
enable,
|
|
||||||
disable,
|
|
||||||
getConfig
|
getConfig
|
||||||
} from '@utils';
|
} from '@utils';
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { ApplicationController } from './application_controller';
|
import { ApplicationController } from './application_controller';
|
||||||
import { AutoUpload } from '../shared/activestorage/auto-upload';
|
import { AutoUpload } from '../shared/activestorage/auto-upload';
|
||||||
|
@ -87,8 +82,6 @@ export class AutosaveController extends ApplicationController {
|
||||||
isSelectElement(target) ||
|
isSelectElement(target) ||
|
||||||
isCheckboxOrRadioInputElement(target)
|
isCheckboxOrRadioInputElement(target)
|
||||||
) {
|
) {
|
||||||
this.toggleOtherInput(target);
|
|
||||||
this.toggleLinkedSelect(target);
|
|
||||||
this.enqueueAutosaveRequest();
|
this.enqueueAutosaveRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,77 +199,4 @@ export class AutosaveController extends ApplicationController {
|
||||||
}
|
}
|
||||||
return inputs.filter((element) => !element.disabled);
|
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?,
|
:dossier_link?,
|
||||||
:titre_identite?,
|
:titre_identite?,
|
||||||
:header_section?,
|
:header_section?,
|
||||||
|
:simple_drop_down_list?,
|
||||||
|
:linked_drop_down_list?,
|
||||||
|
:non_fillable?,
|
||||||
:cnaf?,
|
:cnaf?,
|
||||||
:dgfip?,
|
:dgfip?,
|
||||||
:pole_emploi?,
|
:pole_emploi?,
|
||||||
|
|
|
@ -205,18 +205,18 @@ class TypeDeChamp < ApplicationRecord
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
def drop_down_list_with_other?
|
def simple_drop_down_list?
|
||||||
type_champ == TypeDeChamp.type_champs.fetch(:drop_down_list)
|
type_champ == TypeDeChamp.type_champs.fetch(:drop_down_list)
|
||||||
end
|
end
|
||||||
|
|
||||||
def header_section?
|
|
||||||
type_champ == TypeDeChamp.type_champs.fetch(:header_section)
|
|
||||||
end
|
|
||||||
|
|
||||||
def linked_drop_down_list?
|
def linked_drop_down_list?
|
||||||
type_champ == TypeDeChamp.type_champs.fetch(:linked_drop_down_list)
|
type_champ == TypeDeChamp.type_champs.fetch(:linked_drop_down_list)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def header_section?
|
||||||
|
type_champ == TypeDeChamp.type_champs.fetch(:header_section)
|
||||||
|
end
|
||||||
|
|
||||||
def exclude_from_view?
|
def exclude_from_view?
|
||||||
type_champ == TypeDeChamp.type_champs.fetch(:explication)
|
type_champ == TypeDeChamp.type_champs.fetch(:explication)
|
||||||
end
|
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?
|
- if champ.repetition?
|
||||||
%h3.header-subsection= champ.libelle
|
%h3.header-subsection= champ.libelle
|
||||||
- if champ.description.present?
|
- 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