From 9e4ab53567c2c2e8df0ebac31ede4c1f1c8cbf9c Mon Sep 17 00:00:00 2001 From: ekes Date: Fri, 28 Nov 2025 14:22:08 +0000 Subject: [PATCH 1/7] Allow Title, Help and Description to be set (overriden). Fixes #122 --- src/Element/AddressLookupElement.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Element/AddressLookupElement.php b/src/Element/AddressLookupElement.php index 88dc0f4..5a933af 100644 --- a/src/Element/AddressLookupElement.php +++ b/src/Element/AddressLookupElement.php @@ -109,6 +109,17 @@ public static function processAddressLookupElement(&$element, FormStateInterface 'class' => ['js-address-searchstring'], ], ]; + // Display title, description and help on the active element. + $properties = [ + '#title' => '#title', + // phpcs:ignore DrupalPractice.General.DescriptionT.DescriptionT + '#description' => '#description', + '#help' => '#help', + ]; + $element['address_search']['address_searchstring'] = array_merge( + $element['address_search']['address_searchstring'], + array_intersect_key($element, $properties) + ); $element['address_search']['address_actions'] = [ '#type' => 'container', From 497ea9c5a6aa2f97f3165a8dc16223572661e339 Mon Sep 17 00:00:00 2001 From: ekes Date: Fri, 5 Dec 2025 20:02:48 +0100 Subject: [PATCH 2/7] Initial PoC to show after build callback works for #102 --- localgov_forms.module | 8 +++++ src/Hook/ThemeHooks.php | 73 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/Hook/ThemeHooks.php diff --git a/localgov_forms.module b/localgov_forms.module index a8f1b00..08e58d5 100644 --- a/localgov_forms.module +++ b/localgov_forms.module @@ -5,7 +5,9 @@ * Hook implementations. */ +use Drupal\Core\Hook\Attribute\LegacyHook; use Drupal\Core\Render\Element; +use Drupal\localgov_forms\Hook\ThemeHooks; /** * Implements hook_theme(). @@ -55,3 +57,9 @@ function template_preprocess_localgov_forms_uk_address(array &$variables): void function localgov_forms_preprocess_webform(array &$variables): void { $variables['#attached']['library'][] = 'localgov_forms/localgov_forms.form_errors'; } + +// @phpstan-ignore-next-line +#[LegacyHook] +function localgov_forms_element_info_alter(array &$types): void { + \Drupal::service(ThemeHooks::class)->elementInfoAlter($types); +} diff --git a/src/Hook/ThemeHooks.php b/src/Hook/ThemeHooks.php new file mode 100644 index 0000000..d8f0dd7 --- /dev/null +++ b/src/Hook/ThemeHooks.php @@ -0,0 +1,73 @@ +getFormObject() instanceof WebformSubmissionForm) { + if ($element['#type'] === 'checkbox') { + // If it is desired to add optional to single checkboxes there will be + // a single parent with the same name as the checkbox in #parents. + // A checkbox in a checkboxes list will have at least two parents. + return $element; + } + + if ( + $element['#required'] === FALSE && + isset($element['#title']) + ) { + $element['#title'] .= ' ' + . new TranslatableMarkup('(optional)') + . ''; + } + } + + return $element; + } + +} From dac55f4a0514d2c6187482a1012b5964795bef9e Mon Sep 17 00:00:00 2001 From: ekes Date: Mon, 8 Dec 2025 23:04:19 +0100 Subject: [PATCH 3/7] WIP: adds js to toggle (optional) on conditional, add opt in config. To Do: * Add more elements * Test those elements with JS * Add opt-in default for new installs * Test. --- ...form.third_party.localgov_forms.schema.yml | 7 +++ js/localgov_forms.state.js | 20 ++++++++ localgov_forms.libraries.yml | 7 +++ localgov_forms.module | 7 +++ localgov_forms.services.yml | 10 ++++ src/EventSubscriber/ConfigEventSubscriber.php | 49 +++++++++++++++++++ src/Hook/ThemeHooks.php | 37 ++++++++++++-- 7 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 config/schema/webform.third_party.localgov_forms.schema.yml create mode 100644 js/localgov_forms.state.js create mode 100644 src/EventSubscriber/ConfigEventSubscriber.php diff --git a/config/schema/webform.third_party.localgov_forms.schema.yml b/config/schema/webform.third_party.localgov_forms.schema.yml new file mode 100644 index 0000000..9b69937 --- /dev/null +++ b/config/schema/webform.third_party.localgov_forms.schema.yml @@ -0,0 +1,7 @@ +webform.admin_settings.third_party.localgov_forms: + type: mapping + label: 'LocalGov Forms settings' + mapping: + mark_optional: + type: boolean + label: 'Add optional to non-required element labels' diff --git a/js/localgov_forms.state.js b/js/localgov_forms.state.js new file mode 100644 index 0000000..8149690 --- /dev/null +++ b/js/localgov_forms.state.js @@ -0,0 +1,20 @@ +/** + * @file + * Additional JavaScript behaviors for webform #states. + */ + +(function ($, Drupal) { + + 'use strict'; + + const $document = $(document); + $document.on('state:required', (e) => { + // Add or remove '(optional)' from element label. + if (e.trigger) { + $(e.target) + .find('span.localgov-form-optional') + .html(e.value ? '' : Drupal.t('(optional)')); + } + }); + +})(jQuery, Drupal); diff --git a/localgov_forms.libraries.yml b/localgov_forms.libraries.yml index da1857b..e06b949 100644 --- a/localgov_forms.libraries.yml +++ b/localgov_forms.libraries.yml @@ -28,3 +28,10 @@ localgov_forms.address_change: - core/drupal - core/once - core/jquery + +localgov_forms.state: + js: + js/localgov_forms.state.js: {} + dependencies: + - core/drupal + - core/jquery diff --git a/localgov_forms.module b/localgov_forms.module index 08e58d5..8767816 100644 --- a/localgov_forms.module +++ b/localgov_forms.module @@ -5,6 +5,7 @@ * Hook implementations. */ +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Hook\Attribute\LegacyHook; use Drupal\Core\Render\Element; use Drupal\localgov_forms\Hook\ThemeHooks; @@ -63,3 +64,9 @@ function localgov_forms_preprocess_webform(array &$variables): void { function localgov_forms_element_info_alter(array &$types): void { \Drupal::service(ThemeHooks::class)->elementInfoAlter($types); } + +// @phpstan-ignore-next-line +#[LegacyHook] +function localgov_forms_webform_admin_third_party_settings_form_alter(&$form, FormStateInterface $form_state) { + \Drupal::service(ThemeHooks::class)->webformAdminForm($form, $form_state); +} diff --git a/localgov_forms.services.yml b/localgov_forms.services.yml index 959c86b..08d4df8 100644 --- a/localgov_forms.services.yml +++ b/localgov_forms.services.yml @@ -11,3 +11,13 @@ services: plugin.manager.pii_redactor: class: Drupal\localgov_forms\Plugin\PIIRedactorPluginManager parent: default_plugin_manager + + Drupal\localgov_forms\Hook\ThemeHooks: + class: Drupal\localgov_forms\Hook\ThemeHooks + arguments: ['@webform.third_party_settings_manager'] + + localgov_forms.event_subscriber: + class: Drupal\localgov_forms\EventSubscriber\ConfigEventSubscriber + arguments: ['@plugin.manager.element_info'] + tags: + - { name: event_subscriber } diff --git a/src/EventSubscriber/ConfigEventSubscriber.php b/src/EventSubscriber/ConfigEventSubscriber.php new file mode 100644 index 0000000..7be31ba --- /dev/null +++ b/src/EventSubscriber/ConfigEventSubscriber.php @@ -0,0 +1,49 @@ +getConfig()) && + ($config->getName() == 'webform.settings') && + $event->isChanged('third_party_settings.localgov_forms.mark_optional') + ) { + $this->pluginManagerElementInfo->clearCachedDefinitions(); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + ConfigEvents::SAVE => 'onConfigSave', + ]; + } + +} diff --git a/src/Hook/ThemeHooks.php b/src/Hook/ThemeHooks.php index d8f0dd7..f34b0e8 100644 --- a/src/Hook/ThemeHooks.php +++ b/src/Hook/ThemeHooks.php @@ -8,6 +8,7 @@ use Drupal\Core\Hook\Attribute\Hook; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\webform\WebformSubmissionForm; +use Drupal\webform\WebformThirdPartySettingsManager; /** * Theme related hooks. @@ -18,7 +19,7 @@ class ThemeHooks { * @var array * Element types to add (optional) to. */ - static array $optionalTypes = [ + public static array $optionalTypes = [ 'checkboxes', 'checkbox', 'radios', @@ -26,11 +27,36 @@ class ThemeHooks { 'select', ]; + /** + * Construct a new class. + * + * @param \Drupal\webform\WebformThirdPartySettingsManager $webformThirdPartySettings + * Webform third party settings manager. + */ + public function __construct(protected WebformThirdPartySettingsManager $webformThirdPartySettings) { + } + + #[Hook('webform_admin_third_party_settings_form_alter')] + public function webformAdminForm(&$form, FormStateInterface $form_state) { + $form['third_party_settings']['localgov_forms'] = [ + '#type' => 'details', + '#title' => new TranslatableMarkup('LocalGov Forms'), + ]; + $form['third_party_settings']['localgov_forms']['mark_optional'] = [ + '#type' => 'checkbox', + '#title' => new TranslatableMarkup("Add '(optional)' to non-required elements"), + '#description' => new TranslatableMarkup('If checked GDS forms style addition to the label title.'), + '#default_value' => $this->webformThirdPartySettings->getThirdPartySetting('localgov_forms', 'mark_optional') ?: FALSE, + ]; + } + #[Hook('element_info_alter')] public function elementInfoAlter(array &$types): void { - foreach (static::$optionalTypes as $type) { - if (isset($types[$type])) { - $types[$type]['#after_build'][] = [static::class, 'optionalElement']; + if ($this->webformThirdPartySettings->getThirdPartySetting('localgov_forms', 'mark_optional') ?: FALSE) { + foreach (static::$optionalTypes as $type) { + if (isset($types[$type])) { + $types[$type]['#after_build'][] = [static::class, 'optionalElement']; + } } } } @@ -57,6 +83,8 @@ static function optionalElement(array $element, FormStateInterface $form_state): return $element; } + // Seems conditionally required will trigger this, + // if default required, it's then disable with JS. if ( $element['#required'] === FALSE && isset($element['#title']) @@ -64,6 +92,7 @@ static function optionalElement(array $element, FormStateInterface $form_state): $element['#title'] .= ' ' . new TranslatableMarkup('(optional)') . ''; + $element['#attached']['library'][] = 'localgov_forms/localgov_forms.state'; } } From 74bf654b96a7293507c46a2b15e3aed7d5e3d03b Mon Sep 17 00:00:00 2001 From: ekes Date: Fri, 5 Dec 2025 20:02:48 +0100 Subject: [PATCH 4/7] Initial PoC to show after build callback works for #102 --- localgov_forms.module | 8 +++++ src/Hook/ThemeHooks.php | 73 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/Hook/ThemeHooks.php diff --git a/localgov_forms.module b/localgov_forms.module index a8f1b00..08e58d5 100644 --- a/localgov_forms.module +++ b/localgov_forms.module @@ -5,7 +5,9 @@ * Hook implementations. */ +use Drupal\Core\Hook\Attribute\LegacyHook; use Drupal\Core\Render\Element; +use Drupal\localgov_forms\Hook\ThemeHooks; /** * Implements hook_theme(). @@ -55,3 +57,9 @@ function template_preprocess_localgov_forms_uk_address(array &$variables): void function localgov_forms_preprocess_webform(array &$variables): void { $variables['#attached']['library'][] = 'localgov_forms/localgov_forms.form_errors'; } + +// @phpstan-ignore-next-line +#[LegacyHook] +function localgov_forms_element_info_alter(array &$types): void { + \Drupal::service(ThemeHooks::class)->elementInfoAlter($types); +} diff --git a/src/Hook/ThemeHooks.php b/src/Hook/ThemeHooks.php new file mode 100644 index 0000000..d8f0dd7 --- /dev/null +++ b/src/Hook/ThemeHooks.php @@ -0,0 +1,73 @@ +getFormObject() instanceof WebformSubmissionForm) { + if ($element['#type'] === 'checkbox') { + // If it is desired to add optional to single checkboxes there will be + // a single parent with the same name as the checkbox in #parents. + // A checkbox in a checkboxes list will have at least two parents. + return $element; + } + + if ( + $element['#required'] === FALSE && + isset($element['#title']) + ) { + $element['#title'] .= ' ' + . new TranslatableMarkup('(optional)') + . ''; + } + } + + return $element; + } + +} From ca7f3402457a3342b2eb8fbbe86d318f04102d9e Mon Sep 17 00:00:00 2001 From: ekes Date: Mon, 8 Dec 2025 23:04:19 +0100 Subject: [PATCH 5/7] WIP: adds js to toggle (optional) on conditional, add opt in config. To Do: * Add more elements * Test those elements with JS * Add opt-in default for new installs * Test. --- ...form.third_party.localgov_forms.schema.yml | 7 +++ js/localgov_forms.state.js | 20 ++++++++ localgov_forms.libraries.yml | 7 +++ localgov_forms.module | 7 +++ localgov_forms.services.yml | 10 ++++ src/EventSubscriber/ConfigEventSubscriber.php | 49 +++++++++++++++++++ src/Hook/ThemeHooks.php | 37 ++++++++++++-- 7 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 config/schema/webform.third_party.localgov_forms.schema.yml create mode 100644 js/localgov_forms.state.js create mode 100644 src/EventSubscriber/ConfigEventSubscriber.php diff --git a/config/schema/webform.third_party.localgov_forms.schema.yml b/config/schema/webform.third_party.localgov_forms.schema.yml new file mode 100644 index 0000000..9b69937 --- /dev/null +++ b/config/schema/webform.third_party.localgov_forms.schema.yml @@ -0,0 +1,7 @@ +webform.admin_settings.third_party.localgov_forms: + type: mapping + label: 'LocalGov Forms settings' + mapping: + mark_optional: + type: boolean + label: 'Add optional to non-required element labels' diff --git a/js/localgov_forms.state.js b/js/localgov_forms.state.js new file mode 100644 index 0000000..8149690 --- /dev/null +++ b/js/localgov_forms.state.js @@ -0,0 +1,20 @@ +/** + * @file + * Additional JavaScript behaviors for webform #states. + */ + +(function ($, Drupal) { + + 'use strict'; + + const $document = $(document); + $document.on('state:required', (e) => { + // Add or remove '(optional)' from element label. + if (e.trigger) { + $(e.target) + .find('span.localgov-form-optional') + .html(e.value ? '' : Drupal.t('(optional)')); + } + }); + +})(jQuery, Drupal); diff --git a/localgov_forms.libraries.yml b/localgov_forms.libraries.yml index da1857b..e06b949 100644 --- a/localgov_forms.libraries.yml +++ b/localgov_forms.libraries.yml @@ -28,3 +28,10 @@ localgov_forms.address_change: - core/drupal - core/once - core/jquery + +localgov_forms.state: + js: + js/localgov_forms.state.js: {} + dependencies: + - core/drupal + - core/jquery diff --git a/localgov_forms.module b/localgov_forms.module index 08e58d5..8767816 100644 --- a/localgov_forms.module +++ b/localgov_forms.module @@ -5,6 +5,7 @@ * Hook implementations. */ +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Hook\Attribute\LegacyHook; use Drupal\Core\Render\Element; use Drupal\localgov_forms\Hook\ThemeHooks; @@ -63,3 +64,9 @@ function localgov_forms_preprocess_webform(array &$variables): void { function localgov_forms_element_info_alter(array &$types): void { \Drupal::service(ThemeHooks::class)->elementInfoAlter($types); } + +// @phpstan-ignore-next-line +#[LegacyHook] +function localgov_forms_webform_admin_third_party_settings_form_alter(&$form, FormStateInterface $form_state) { + \Drupal::service(ThemeHooks::class)->webformAdminForm($form, $form_state); +} diff --git a/localgov_forms.services.yml b/localgov_forms.services.yml index 959c86b..08d4df8 100644 --- a/localgov_forms.services.yml +++ b/localgov_forms.services.yml @@ -11,3 +11,13 @@ services: plugin.manager.pii_redactor: class: Drupal\localgov_forms\Plugin\PIIRedactorPluginManager parent: default_plugin_manager + + Drupal\localgov_forms\Hook\ThemeHooks: + class: Drupal\localgov_forms\Hook\ThemeHooks + arguments: ['@webform.third_party_settings_manager'] + + localgov_forms.event_subscriber: + class: Drupal\localgov_forms\EventSubscriber\ConfigEventSubscriber + arguments: ['@plugin.manager.element_info'] + tags: + - { name: event_subscriber } diff --git a/src/EventSubscriber/ConfigEventSubscriber.php b/src/EventSubscriber/ConfigEventSubscriber.php new file mode 100644 index 0000000..7be31ba --- /dev/null +++ b/src/EventSubscriber/ConfigEventSubscriber.php @@ -0,0 +1,49 @@ +getConfig()) && + ($config->getName() == 'webform.settings') && + $event->isChanged('third_party_settings.localgov_forms.mark_optional') + ) { + $this->pluginManagerElementInfo->clearCachedDefinitions(); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + ConfigEvents::SAVE => 'onConfigSave', + ]; + } + +} diff --git a/src/Hook/ThemeHooks.php b/src/Hook/ThemeHooks.php index d8f0dd7..f34b0e8 100644 --- a/src/Hook/ThemeHooks.php +++ b/src/Hook/ThemeHooks.php @@ -8,6 +8,7 @@ use Drupal\Core\Hook\Attribute\Hook; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\webform\WebformSubmissionForm; +use Drupal\webform\WebformThirdPartySettingsManager; /** * Theme related hooks. @@ -18,7 +19,7 @@ class ThemeHooks { * @var array * Element types to add (optional) to. */ - static array $optionalTypes = [ + public static array $optionalTypes = [ 'checkboxes', 'checkbox', 'radios', @@ -26,11 +27,36 @@ class ThemeHooks { 'select', ]; + /** + * Construct a new class. + * + * @param \Drupal\webform\WebformThirdPartySettingsManager $webformThirdPartySettings + * Webform third party settings manager. + */ + public function __construct(protected WebformThirdPartySettingsManager $webformThirdPartySettings) { + } + + #[Hook('webform_admin_third_party_settings_form_alter')] + public function webformAdminForm(&$form, FormStateInterface $form_state) { + $form['third_party_settings']['localgov_forms'] = [ + '#type' => 'details', + '#title' => new TranslatableMarkup('LocalGov Forms'), + ]; + $form['third_party_settings']['localgov_forms']['mark_optional'] = [ + '#type' => 'checkbox', + '#title' => new TranslatableMarkup("Add '(optional)' to non-required elements"), + '#description' => new TranslatableMarkup('If checked GDS forms style addition to the label title.'), + '#default_value' => $this->webformThirdPartySettings->getThirdPartySetting('localgov_forms', 'mark_optional') ?: FALSE, + ]; + } + #[Hook('element_info_alter')] public function elementInfoAlter(array &$types): void { - foreach (static::$optionalTypes as $type) { - if (isset($types[$type])) { - $types[$type]['#after_build'][] = [static::class, 'optionalElement']; + if ($this->webformThirdPartySettings->getThirdPartySetting('localgov_forms', 'mark_optional') ?: FALSE) { + foreach (static::$optionalTypes as $type) { + if (isset($types[$type])) { + $types[$type]['#after_build'][] = [static::class, 'optionalElement']; + } } } } @@ -57,6 +83,8 @@ static function optionalElement(array $element, FormStateInterface $form_state): return $element; } + // Seems conditionally required will trigger this, + // if default required, it's then disable with JS. if ( $element['#required'] === FALSE && isset($element['#title']) @@ -64,6 +92,7 @@ static function optionalElement(array $element, FormStateInterface $form_state): $element['#title'] .= ' ' . new TranslatableMarkup('(optional)') . ''; + $element['#attached']['library'][] = 'localgov_forms/localgov_forms.state'; } } From f0f12223ef5803a58113f605e8398c5ea3ea24de Mon Sep 17 00:00:00 2001 From: ekes Date: Thu, 11 Dec 2025 11:13:54 +0100 Subject: [PATCH 6/7] Set up logic for when to add (optional), or rather when not. --- src/Element/AddressLookupElement.php | 4 +++ src/Element/UKAddressLookup.php | 6 +++-- src/Hook/ThemeHooks.php | 40 ++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/Element/AddressLookupElement.php b/src/Element/AddressLookupElement.php index 5a933af..1717ab0 100644 --- a/src/Element/AddressLookupElement.php +++ b/src/Element/AddressLookupElement.php @@ -108,6 +108,9 @@ public static function processAddressLookupElement(&$element, FormStateInterface '#attributes' => [ 'class' => ['js-address-searchstring'], ], + // Required validation is done on the element, based on required + // address parts. Display of required status is done on the collection. + '#required' => NULL, ]; // Display title, description and help on the active element. $properties = [ @@ -185,6 +188,7 @@ public static function processAddressLookupElement(&$element, FormStateInterface 'class' => ['js-address-select'], ], '#address_type' => $element['#address_type'] ?? 'residential', + '#required' => NULL, ]; if ($form_state->isProcessingInput()) { diff --git a/src/Element/UKAddressLookup.php b/src/Element/UKAddressLookup.php index 5850ef4..a8eb333 100644 --- a/src/Element/UKAddressLookup.php +++ b/src/Element/UKAddressLookup.php @@ -47,6 +47,7 @@ public static function getCompositeElements(array $element) { $element_list = []; $element_list['address_lookup'] = [ '#type' => 'localgov_forms_address_lookup', + '#title' => $element['#title'] ?? NULL, '#address_type' => $element['#address_type'] ?? 'residential', '#address_search_description' => $element['#address_search_description'] ?? NULL, '#address_select_title' => $element['#address_select_title'] ?? NULL, @@ -69,6 +70,7 @@ public static function getCompositeElements(array $element) { foreach ($extra_elements as $extra_element) { $element_list[$extra_element] = [ '#type' => 'hidden', + '#title' => $extra_element, '#default_value' => '', '#attributes' => [ 'class' => ['js-localgov-forms-webform-uk-address--' . $extra_element], @@ -76,8 +78,8 @@ public static function getCompositeElements(array $element) { ]; } - $element_list['#attached']['library'][] = 'localgov_forms/localgov_forms.address_select'; - $element_list['#attached']['drupalSettings']['centralHub']['isManualAddressEntryBtnAlwaysVisible'] = isset($element['#always_display_manual_address_entry_btn']) ? ($element['#always_display_manual_address_entry_btn'] === 'yes') : TRUE; + $element_list['address_lookup']['#attached']['library'][] = 'localgov_forms/localgov_forms.address_select'; + $element_list['address_lookup']['#attached']['drupalSettings']['centralHub']['isManualAddressEntryBtnAlwaysVisible'] = isset($element['#always_display_manual_address_entry_btn']) ? ($element['#always_display_manual_address_entry_btn'] === 'yes') : TRUE; return $element_list; } diff --git a/src/Hook/ThemeHooks.php b/src/Hook/ThemeHooks.php index f34b0e8..07f983b 100644 --- a/src/Hook/ThemeHooks.php +++ b/src/Hook/ThemeHooks.php @@ -4,6 +4,8 @@ namespace Drupal\localgov_forms\Hook; +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Render\Element; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Hook\Attribute\Hook; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -17,15 +19,9 @@ class ThemeHooks { /** * @var array - * Element types to add (optional) to. + * Input element types not to attach to. */ - public static array $optionalTypes = [ - 'checkboxes', - 'checkbox', - 'radios', - 'textfield', - 'select', - ]; + public static array $skipTypes = []; /** * Construct a new class. @@ -53,8 +49,8 @@ public function webformAdminForm(&$form, FormStateInterface $form_state) { #[Hook('element_info_alter')] public function elementInfoAlter(array &$types): void { if ($this->webformThirdPartySettings->getThirdPartySetting('localgov_forms', 'mark_optional') ?: FALSE) { - foreach (static::$optionalTypes as $type) { - if (isset($types[$type])) { + foreach ($types as $type => $info) { + if (($info['#input'] ?? FALSE) && !in_array($type, static::$skipTypes, TRUE)) { $types[$type]['#after_build'][] = [static::class, 'optionalElement']; } } @@ -76,13 +72,35 @@ public function elementInfoAlter(array &$types): void { */ static function optionalElement(array $element, FormStateInterface $form_state): array { if ($form_state->getFormObject() instanceof WebformSubmissionForm) { - if ($element['#type'] === 'checkbox') { + $type = $element['#type']; + if ($type === 'checkbox' || $type === 'radio') { // If it is desired to add optional to single checkboxes there will be // a single parent with the same name as the checkbox in #parents. // A checkbox in a checkboxes list will have at least two parents. return $element; } + $form = $form_state->getCompleteForm(); + $parents = $element['#array_parents']; + array_pop($parents); + $parent = NestedArray::getValue($form, $parents); + $parent_type = $parent['#type']; + + // Don't show optional on every field if whole address optional. + if ($parent_type === 'localgov_webform_uk_address') { + $all_optional = TRUE; + foreach (Element::children($parent) as $sibling_name) { + $sibling = $parent[$sibling_name]; + if ($sibling['#access'] && isset($sibling['#required']) && $sibling['#required']) { + $all_optional = FALSE; + break; + } + } + if ($all_optional) { + return $element; + } + } + // Seems conditionally required will trigger this, // if default required, it's then disable with JS. if ( From 374b446d02af6353a99ddd37288e940d9c4afc83 Mon Sep 17 00:00:00 2001 From: ekes Date: Thu, 11 Dec 2025 11:18:58 +0100 Subject: [PATCH 7/7] OOps! Tidy that merge conflict properly. --- src/Hook/ThemeHooks.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/Hook/ThemeHooks.php b/src/Hook/ThemeHooks.php index 34009ce..07f983b 100644 --- a/src/Hook/ThemeHooks.php +++ b/src/Hook/ThemeHooks.php @@ -4,11 +4,8 @@ namespace Drupal\localgov_forms\Hook; -<<<<<<< HEAD use Drupal\Component\Utility\NestedArray; use Drupal\Core\Render\Element; -======= ->>>>>>> dac55f4a0514d2c6187482a1012b5964795bef9e use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Hook\Attribute\Hook; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -22,21 +19,9 @@ class ThemeHooks { /** * @var array -<<<<<<< HEAD * Input element types not to attach to. */ public static array $skipTypes = []; -======= - * Element types to add (optional) to. - */ - public static array $optionalTypes = [ - 'checkboxes', - 'checkbox', - 'radios', - 'textfield', - 'select', - ]; ->>>>>>> dac55f4a0514d2c6187482a1012b5964795bef9e /** * Construct a new class. @@ -64,13 +49,8 @@ public function webformAdminForm(&$form, FormStateInterface $form_state) { #[Hook('element_info_alter')] public function elementInfoAlter(array &$types): void { if ($this->webformThirdPartySettings->getThirdPartySetting('localgov_forms', 'mark_optional') ?: FALSE) { -<<<<<<< HEAD foreach ($types as $type => $info) { if (($info['#input'] ?? FALSE) && !in_array($type, static::$skipTypes, TRUE)) { -======= - foreach (static::$optionalTypes as $type) { - if (isset($types[$type])) { ->>>>>>> dac55f4a0514d2c6187482a1012b5964795bef9e $types[$type]['#after_build'][] = [static::class, 'optionalElement']; } }