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'; } /**