From e85fe718874164997a91fe7b5065a97e7fbdcd6f Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 19 Jul 2022 19:01:53 +0200 Subject: [PATCH] fix(champ): allow to submit when secondary options are empty --- .../controllers/autosave_controller.ts | 60 +++++++++++++++++++ app/javascript/entrypoints/application.js | 2 - .../champs/linked-drop-down-list.js | 32 ---------- app/javascript/shared/utils.ts | 14 +++-- .../champs/linked_drop_down_list_champ.rb | 8 +-- .../_linked_drop_down_list.html.haml | 22 +++---- 6 files changed, 85 insertions(+), 53 deletions(-) delete mode 100644 app/javascript/new_design/champs/linked-drop-down-list.js diff --git a/app/javascript/controllers/autosave_controller.ts b/app/javascript/controllers/autosave_controller.ts index 8ca973446..296a0938b 100644 --- a/app/javascript/controllers/autosave_controller.ts +++ b/app/javascript/controllers/autosave_controller.ts @@ -6,8 +6,11 @@ import { isTextInputElement, show, hide, + enable, + disable, getConfig } from '@utils'; +import { z } from 'zod'; import { ApplicationController } from './application_controller'; import { AutoUpload } from '../shared/activestorage/auto-upload'; @@ -85,6 +88,7 @@ export class AutosaveController extends ApplicationController { isCheckboxOrRadioInputElement(target) ) { this.toggleOtherInput(target); + this.toggleLinkedSelect(target); 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( + '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; } diff --git a/app/javascript/entrypoints/application.js b/app/javascript/entrypoints/application.js index e4dd8c125..7e115803a 100644 --- a/app/javascript/entrypoints/application.js +++ b/app/javascript/entrypoints/application.js @@ -18,8 +18,6 @@ import '../new_design/procedure-form'; import '../new_design/spinner'; import '../new_design/support'; -import '../new_design/champs/linked-drop-down-list'; - import { toggleCondidentielExplanation, replaceSemicolonByComma diff --git a/app/javascript/new_design/champs/linked-drop-down-list.js b/app/javascript/new_design/champs/linked-drop-down-list.js deleted file mode 100644 index b9f5e8797..000000000 --- a/app/javascript/new_design/champs/linked-drop-down-list.js +++ /dev/null @@ -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; -} diff --git a/app/javascript/shared/utils.ts b/app/javascript/shared/utils.ts index a55f507b7..4b8b0dc66 100644 --- a/app/javascript/shared/utils.ts +++ b/app/javascript/shared/utils.ts @@ -70,15 +70,15 @@ export function getConfig() { return Gon.parse(window.gon); } -export function show(el: HTMLElement | null) { +export function show(el: Element | null) { el?.classList.remove('hidden'); } -export function hide(el: HTMLElement | null) { +export function hide(el: Element | null) { el?.classList.add('hidden'); } -export function toggle(el: HTMLElement | null, force?: boolean) { +export function toggle(el: Element | null, force?: boolean) { if (force == undefined) { el?.classList.toggle('hidden'); } 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); } -export function disable(el: HTMLInputElement | HTMLButtonElement | null) { +export function disable( + el: HTMLSelectElement | HTMLInputElement | HTMLButtonElement | null +) { el && (el.disabled = true); } diff --git a/app/models/champs/linked_drop_down_list_champ.rb b/app/models/champs/linked_drop_down_list_champ.rb index e013d1c24..351ce893a 100644 --- a/app/models/champs/linked_drop_down_list_champ.rb +++ b/app/models/champs/linked_drop_down_list_champ.rb @@ -83,13 +83,13 @@ class Champs::LinkedDropDownListChamp < Champ [primary_value, secondary_value] end + def has_secondary_options_for_primary? + primary_value.present? && secondary_options[primary_value]&.any?(&:present?) + end + private def pack_value(primary, secondary) self.value = JSON.generate([primary, secondary]) end - - def has_secondary_options_for_primary? - primary_value.present? && secondary_options[primary_value]&.any?(&:present?) - end end diff --git a/app/views/shared/dossiers/editable_champs/_linked_drop_down_list.html.haml b/app/views/shared/dossiers/editable_champs/_linked_drop_down_list.html.haml index aa3531c10..8f4f5e610 100644 --- a/app/views/shared/dossiers/editable_champs/_linked_drop_down_list.html.haml +++ b/app/views/shared/dossiers/editable_champs/_linked_drop_down_list.html.haml @@ -4,13 +4,15 @@ {}, { 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 - = champ.drop_down_secondary_libelle.presence || "Valeur secondaire dépendant de la première" - - if champ.mandatory? - %span.mandatory * - - if champ.drop_down_secondary_description.present? - .notice{ id: "#{champ.describedby_id}-secondary" }= string_to_html(champ.drop_down_secondary_description) - = 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" } } + .secondary{ class: champ.has_secondary_options_for_primary? ? '' : 'hidden' } + = form.label :secondary_value, for: "#{champ.input_id}-secondary" do + = champ.drop_down_secondary_libelle.presence || "Valeur secondaire dépendant de la première" + - if champ.mandatory? + %span.mandatory * + - if champ.drop_down_secondary_description.present? + .notice{ id: "#{champ.describedby_id}-secondary" }= string_to_html(champ.drop_down_secondary_description) + = form.select :secondary_value, + champ.secondary_options[champ.primary_value], + {}, + { 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?