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:
commit
c61e411328
6 changed files with 85 additions and 53 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?
|
||||||
|
|
Loading…
Add table
Reference in a new issue