Merge pull request #7590 from tchak/fix-linked-drop-down-lists

fix(champ): allow to submit when secondary options are empty
This commit is contained in:
Paul Chavard 2022-07-20 11:00:27 +02:00 committed by GitHub
commit c61e411328
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 53 deletions

View file

@ -6,8 +6,11 @@ import {
isTextInputElement, isTextInputElement,
show, show,
hide, 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';
@ -85,6 +88,7 @@ export class AutosaveController extends ApplicationController {
isCheckboxOrRadioInputElement(target) isCheckboxOrRadioInputElement(target)
) { ) {
this.toggleOtherInput(target); this.toggleOtherInput(target);
this.toggleLinkedSelect(target);
this.enqueueAutosaveRequest(); this.enqueueAutosaveRequest();
} }
} }
@ -219,4 +223,60 @@ export class AutosaveController extends ApplicationController {
} }
} }
} }
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

@ -18,8 +18,6 @@ import '../new_design/procedure-form';
import '../new_design/spinner'; import '../new_design/spinner';
import '../new_design/support'; import '../new_design/support';
import '../new_design/champs/linked-drop-down-list';
import { import {
toggleCondidentielExplanation, toggleCondidentielExplanation,
replaceSemicolonByComma replaceSemicolonByComma

View file

@ -1,32 +0,0 @@
import { delegate } from '@utils';
const PRIMARY_SELECTOR = 'select[data-secondary-options]';
const SECONDARY_SELECTOR = 'select[data-secondary]';
const CHAMP_SELECTOR = '.editable-champ';
delegate('change', PRIMARY_SELECTOR, (evt) => {
const primary = evt.target;
const secondary = primary
.closest(CHAMP_SELECTOR)
.querySelector(SECONDARY_SELECTOR);
const options = JSON.parse(primary.dataset.secondaryOptions);
selectOptions(secondary, options[primary.value]);
});
function makeOption(option) {
let element = document.createElement('option');
element.textContent = option;
element.value = option;
return element;
}
function selectOptions(selectElement, options) {
selectElement.innerHTML = '';
if (selectElement.required) {
selectElement.appendChild(makeOption(''));
}
for (let option of options) {
selectElement.appendChild(makeOption(option));
}
selectElement.selectedIndex = 0;
}

View file

@ -70,15 +70,15 @@ export function getConfig() {
return Gon.parse(window.gon); return Gon.parse(window.gon);
} }
export function show(el: HTMLElement | null) { export function show(el: Element | null) {
el?.classList.remove('hidden'); el?.classList.remove('hidden');
} }
export function hide(el: HTMLElement | null) { export function hide(el: Element | null) {
el?.classList.add('hidden'); el?.classList.add('hidden');
} }
export function toggle(el: HTMLElement | null, force?: boolean) { export function toggle(el: Element | null, force?: boolean) {
if (force == undefined) { if (force == undefined) {
el?.classList.toggle('hidden'); el?.classList.toggle('hidden');
} else if (force) { } else if (force) {
@ -88,11 +88,15 @@ export function toggle(el: HTMLElement | null, force?: boolean) {
} }
} }
export function enable(el: HTMLInputElement | HTMLButtonElement | null) { export function enable(
el: HTMLSelectElement | HTMLInputElement | HTMLButtonElement | null
) {
el && (el.disabled = false); el && (el.disabled = false);
} }
export function disable(el: HTMLInputElement | HTMLButtonElement | null) { export function disable(
el: HTMLSelectElement | HTMLInputElement | HTMLButtonElement | null
) {
el && (el.disabled = true); el && (el.disabled = true);
} }

View file

@ -83,13 +83,13 @@ class Champs::LinkedDropDownListChamp < Champ
[primary_value, secondary_value] [primary_value, secondary_value]
end end
def has_secondary_options_for_primary?
primary_value.present? && secondary_options[primary_value]&.any?(&:present?)
end
private private
def pack_value(primary, secondary) def pack_value(primary, secondary)
self.value = JSON.generate([primary, secondary]) self.value = JSON.generate([primary, secondary])
end end
def has_secondary_options_for_primary?
primary_value.present? && secondary_options[primary_value]&.any?(&:present?)
end
end end

View file

@ -4,13 +4,15 @@
{}, {},
{ data: { secondary_options: champ.secondary_options }, required: champ.mandatory?, id: champ.input_id, aria: { describedby: champ.describedby_id } } { data: { secondary_options: champ.secondary_options }, required: champ.mandatory?, id: champ.input_id, aria: { describedby: champ.describedby_id } }
= form.label :secondary_value, for: "#{champ.input_id}-secondary" do .secondary{ class: champ.has_secondary_options_for_primary? ? '' : 'hidden' }
= champ.drop_down_secondary_libelle.presence || "Valeur secondaire dépendant de la première" = form.label :secondary_value, for: "#{champ.input_id}-secondary" do
- if champ.mandatory? = champ.drop_down_secondary_libelle.presence || "Valeur secondaire dépendant de la première"
%span.mandatory * - if champ.mandatory?
- if champ.drop_down_secondary_description.present? %span.mandatory *
.notice{ id: "#{champ.describedby_id}-secondary" }= string_to_html(champ.drop_down_secondary_description) - if champ.drop_down_secondary_description.present?
= form.select :secondary_value, .notice{ id: "#{champ.describedby_id}-secondary" }= string_to_html(champ.drop_down_secondary_description)
champ.secondary_options[champ.primary_value], = form.select :secondary_value,
{}, champ.secondary_options[champ.primary_value],
{ data: { secondary: true }, required: champ.mandatory?, id: "#{champ.input_id}-secondary", aria: { describedby: "#{champ.describedby_id}-secondary" } } {},
{ data: { secondary: true }, disabled: !champ.has_secondary_options_for_primary?, required: champ.mandatory?, id: "#{champ.input_id}-secondary", aria: { describedby: "#{champ.describedby_id}-secondary" } }
= form.hidden_field :secondary_value, value: '', disabled: champ.has_secondary_options_for_primary?