diff --git a/js/webform.element.radios.js b/js/webform.element.radios.js
new file mode 100644
index 00000000..0c696754
--- /dev/null
+++ b/js/webform.element.radios.js
@@ -0,0 +1,58 @@
+/**
+ * @file
+ * Javascript behaviors for radio buttons.
+ *
+ * Fix #states and #required for radios buttons.
+ *
+ * @see Issue #2856795: If radio buttons are required but not filled form is nevertheless submitted.
+ * @see Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset.
+ * @see Issue #2731991: Setting required on radios marks all options required.
+ * @see css/webform.form.css
+ * @see /core/misc/states.js
+ */
+
+(function ($, Drupal) {
+
+ 'use strict';
+
+ /**
+ * Attach handler to add .js-webform-radios-fieldset to radios wrapper fieldset.
+ *
+ * @type {Drupal~behavior}
+ */
+ Drupal.behaviors.webformRadios = {
+ attach: function (context) {
+ $('.js-webform-radios', context).closest('fieldset.form-composite').addClass('js-webform-radios-fieldset');
+ }
+ };
+
+ // Make absolutely sure the below event handlers are triggered after
+ // the /core/misc/states.js event handlers by attaching them after DOM load.
+ $(function () {
+ Drupal.behaviors.webformRadios.attach($(document));
+
+ function setRequired($target, required) {
+ if (!$target.hasClass('js-webform-radios-fieldset')) {
+ return;
+ }
+
+ if (required) {
+ $target.find('input[type="radio"]').attr({'required': 'required', 'aria-required': 'aria-required'})
+ $target.find('legend span').addClass('js-form-required form-required');
+ }
+ else {
+ $target.find('input[type="radio"]').removeAttr('required aria-required');
+ $target.find('legend span').removeClass('js-form-required form-required');
+ }
+ }
+
+ setRequired($('.form-composite[required="required"]'), true);
+
+ $(document).on('state:required', function (e) {
+ if (e.trigger) {
+ setRequired($(e.target), e.value);
+ }
+ });
+ });
+
+})(jQuery, Drupal);
diff --git a/js/webform.states.js b/js/webform.states.js
index 3f7ab9e5..3fd4c12d 100644
--- a/js/webform.states.js
+++ b/js/webform.states.js
@@ -8,7 +8,7 @@
'use strict';
// Make absolutely sure the below event handlers are triggered after
- // the state.js event handlers by attaching them after DOM load.
+ // the /core/misc/states.js event handlers by attaching them after DOM load.
$(function () {
var $document = $(document);
$document.on('state:visible', function (e) {
diff --git a/src/Plugin/WebformElement/Radios.php b/src/Plugin/WebformElement/Radios.php
index 139abe33..07c7194f 100644
--- a/src/Plugin/WebformElement/Radios.php
+++ b/src/Plugin/WebformElement/Radios.php
@@ -2,6 +2,8 @@
namespace Drupal\webform\Plugin\WebformElement;
+use Drupal\webform\WebformSubmissionInterface;
+
/**
* Provides a 'radios' element.
*
@@ -25,4 +27,17 @@ class Radios extends OptionsBase {
];
}
+ /**
+ * {@inheritdoc}
+ */
+ public function prepare(array &$element, WebformSubmissionInterface $webform_submission) {
+ parent::prepare($element, $webform_submission);
+
+ // Issue #2856795: If radio buttons are required but not filled form is
+ // nevertheless submitted.
+ // Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset.
+ $element['#attached']['library'][] = 'webform/webform.element.radios';
+ }
+
}
+
diff --git a/src/Tests/Element/WebformElementOtherTest.php b/src/Tests/Element/WebformElementOtherTest.php
index 24e021c6..affed20f 100644
--- a/src/Tests/Element/WebformElementOtherTest.php
+++ b/src/Tests/Element/WebformElementOtherTest.php
@@ -76,7 +76,6 @@ class WebformElementOtherTest extends WebformTestBase {
$this->assertRaw('');
// Check advanced radios_other w/ custom label.
- $this->assertRaw('
');
$this->assertRaw('');
$this->assertRaw('');
$this->assertRaw('');
diff --git a/tests/modules/webform_test/config/install/webform.webform.test_element_radios.yml b/tests/modules/webform_test/config/install/webform.webform.test_element_radios.yml
new file mode 100644
index 00000000..5c36edaa
--- /dev/null
+++ b/tests/modules/webform_test/config/install/webform.webform.test_element_radios.yml
@@ -0,0 +1,153 @@
+langcode: en
+status: open
+dependencies:
+ enforced:
+ module:
+ - webform_test
+open: null
+close: null
+uid: null
+template: false
+id: test_element_radios
+title: 'Test: Element: Radios'
+description: 'Test the radios.'
+elements: |
+ radios_required_example:
+ '#type': details
+ '#title': 'Radios required'
+ '#open': true
+ radios_required:
+ '#type': radios
+ '#title': radios_required
+ '#required': true
+ '#options': yes_no
+ radios_required_conditional_example:
+ '#type': details
+ '#title': 'Radios required conditional'
+ '#open': true
+ radios_required_conditional_trigger:
+ '#type': checkbox
+ '#title': 'Required the below radio button'
+ '#default_value': true
+ radios_required_conditional:
+ '#type': radios
+ '#title': radios_required
+ '#options': yes_no
+ '#states':
+ required:
+ ':input[name="radios_required_conditional_trigger"]':
+ checked: true
+ buttons_required_conditional_example:
+ '#type': details
+ '#title': 'Buttons required conditional'
+ '#open': true
+ buttons_required_conditional_trigger:
+ '#type': checkbox
+ '#title': 'Required the below buttons'
+ '#default_value': true
+ buttons_required_conditional:
+ '#type': webform_buttons
+ '#title': buttons_required
+ '#options': yes_no
+ '#states':
+ required:
+ ':input[name="buttons_required_conditional_trigger"]':
+ checked: true
+css: ''
+javascript: ''
+settings:
+ page: true
+ page_submit_path: ''
+ page_confirm_path: ''
+ form_submit_label: ''
+ form_submit_once: false
+ form_submit_attributes: { }
+ form_exception_message: ''
+ form_closed_message: ''
+ form_previous_submissions: true
+ form_confidential: false
+ form_confidential_message: ''
+ form_prepopulate: false
+ form_prepopulate_source_entity: false
+ form_disable_autocomplete: false
+ form_novalidate: false
+ form_unsaved: false
+ form_disable_back: false
+ form_autofocus: false
+ form_details_toggle: false
+ wizard_progress_bar: true
+ wizard_progress_pages: false
+ wizard_progress_percentage: false
+ wizard_next_button_label: ''
+ wizard_next_button_attributes: { }
+ wizard_prev_button_label: ''
+ wizard_prev_button_attributes: { }
+ wizard_start_label: ''
+ wizard_complete: true
+ wizard_complete_label: ''
+ preview: 0
+ preview_next_button_label: ''
+ preview_next_button_attributes: { }
+ preview_prev_button_label: ''
+ preview_prev_button_attributes: { }
+ preview_message: ''
+ draft: false
+ draft_auto_save: false
+ draft_button_label: ''
+ draft_button_attributes: { }
+ draft_saved_message: ''
+ draft_loaded_message: ''
+ confirmation_type: message
+ confirmation_title: ''
+ confirmation_message: ''
+ confirmation_url: ''
+ confirmation_attributes: { }
+ confirmation_back: true
+ confirmation_back_label: ''
+ confirmation_back_attributes: { }
+ limit_total: null
+ limit_total_message: ''
+ limit_user: null
+ limit_user_message: ''
+ purge: none
+ purge_days: null
+ entity_limit_total: null
+ entity_limit_user: null
+ results_disabled: true
+ results_disabled_ignore: false
+ token_update: false
+access:
+ create:
+ roles:
+ - anonymous
+ - authenticated
+ users: { }
+ view_any:
+ roles: { }
+ users: { }
+ update_any:
+ roles: { }
+ users: { }
+ delete_any:
+ roles: { }
+ users: { }
+ purge_any:
+ roles: { }
+ users: { }
+ view_own:
+ roles: { }
+ users: { }
+ update_own:
+ roles: { }
+ users: { }
+ delete_own:
+ roles: { }
+ users: { }
+handlers:
+ debug:
+ id: debug
+ label: Debug
+ handler_id: debug
+ status: true
+ weight: 1
+ settings: { }
diff --git a/webform.libraries.yml b/webform.libraries.yml
index b833e3c4..0856e74e 100644
--- a/webform.libraries.yml
+++ b/webform.libraries.yml
@@ -390,6 +390,15 @@ webform.element.other:
- core/jquery
- core/jquery.once
+webform.element.radios:
+ version: VERSION
+ js:
+ js/webform.element.radios.js: {}
+ dependencies:
+ - core/drupal
+ - core/jquery
+ - core/jquery.once
+
webform.element.range:
version: VERSION
css:
diff --git a/webform.module b/webform.module
index 4773eea4..fb63522c 100644
--- a/webform.module
+++ b/webform.module
@@ -587,7 +587,6 @@ function webform_theme_suggestions_alter(array &$suggestions, array $variables,
$suggestions[] = $hook . '__' . $webform->id();
}
}
-
/**
* Prepares variables for checkboxes templates.
*
@@ -607,6 +606,8 @@ function webform_preprocess_checkboxes(&$variables) {
*/
function webform_preprocess_radios(&$variables) {
webform_preprocess_checkboxes($variables);
+ // @see js/webform.element.radios.js
+ $variables['attributes']['class'][] = 'js-webform-radios';
}
/**