From c4e99382a37cee984c84e94bf03337abaeb4c172 Mon Sep 17 00:00:00 2001 From: richardgaunt <57734756+richardgaunt@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:54:26 +1100 Subject: [PATCH] Issue #3486893, #3478484, #3483064, #3415662, by joshua1234511, richardgaunt, fionamorrison23, febdao, gargsuchi, sonam.chaturvedi, mwjansen, alex.skrypnyk: Add opt-in for controlling what form elements are rendered via civictheme system. (#1306) --- .../form_elements.core_form_elements.feature | 383 ++++++++++++++++++ ...orm_elements.webform_form_elements.feature | 265 ++++++++++++ .../civictheme_dev/civictheme_dev.module | 4 - .../civictheme_dev/civictheme_dev.routing.yml | 14 + ...webform.civictheme_test_webform_fields.yml | 383 ++++++++++++++++++ .../src/Form/CoreFormElementsForm.php | 311 ++++++++++++++ .../contrib/civictheme/includes/form.inc | 57 ++- .../civictheme/includes/form_element.inc | 127 +++++- .../contrib/civictheme/includes/utilities.inc | 2 +- .../civictheme/src/CivicthemeConstants.php | 5 + ...--form-element--civictheme-field.html.twig | 2 + .../form-element--civictheme-field.html.twig | 2 + 12 files changed, 1511 insertions(+), 44 deletions(-) create mode 100644 tests/behat/features/form_elements.core_form_elements.feature create mode 100644 tests/behat/features/form_elements.webform_form_elements.feature create mode 100644 web/modules/custom/civictheme_dev/civictheme_dev.routing.yml create mode 100644 web/modules/custom/civictheme_dev/config/install/webform.webform.civictheme_test_webform_fields.yml create mode 100644 web/modules/custom/civictheme_dev/src/Form/CoreFormElementsForm.php diff --git a/tests/behat/features/form_elements.core_form_elements.feature b/tests/behat/features/form_elements.core_form_elements.feature new file mode 100644 index 000000000..5a6acf95a --- /dev/null +++ b/tests/behat/features/form_elements.core_form_elements.feature @@ -0,0 +1,383 @@ +@p1 @civictheme @civictheme_form_updates @civictheme_core_form +Feature: Test a sample of the core form elements + + @api + Scenario: Fields appear as expected in civictheme + Given I am an anonymous user + When I visit "/civictheme-core-form-elements" + + Then I should see "CivicTheme Core Form Elements Form" in the ".ct-banner__title" element + + # Button (button) + And should see an "[name='op']" element + And should not see an "[name='op'].required" element + And should not see an "[name='op'][disabled]" element + And should see an "[value='Button (button)']" element + And should see an "[data-component-name='button']" element + And should see an "[type='submit']" element + And I should see an ".ct-button.ct-theme-light.ct-button--primary.ct-button--submit.ct-button--regular" element + And I should see an ".button.js-form-submit.form-submit" element + + # Checkbox (checkbox) + And I should see an "[name='checkbox']" element + And I should see an "[id='edit-checkbox']" element + And I should see an ".ct-checkbox.ct-theme-light.ct-field__control.form-checkbox" element + + # Checkboxes (checkboxes) + And I should see an "[name='checkboxes[option1]']" element + And I should see an "[id='edit-checkboxes-option1']" element + And I should see an ".ct-checkbox.ct-theme-light.ct-field__control.form-checkbox" element + And I should see an "[name='checkboxes[option2]']" element + And I should see an "[id='edit-checkboxes-option2']" element + And I should see an ".ct-checkbox.ct-theme-light.ct-field__control.form-checkbox" element + And I should see an "[name='checkboxes[option3]']" element + And I should see an "[id='edit-checkboxes-option3']" element + And I should see an ".ct-checkbox.ct-theme-light.ct-field__control.form-checkbox" element + + # Date (date) + And I should see an "[name='date']" element + And I should see an "[id='edit-date']" element + And I should see an ".form-date.form-element.form-element--type-date.form-element--api-date.form-control" element + + # Datelist (datelist) + And I should see an "[name='datelist[year]']" element + And I should see an "[id='edit-datelist-year']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select" element + And I should see an "[name='datelist[month]']" element + And I should see an "[id='edit-datelist-month']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select" element + And I should see an "[name='datelist[day]']" element + And I should see an "[id='edit-datelist-day']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select" element + And I should see an "[name='datelist[hour]']" element + And I should see an "[id='edit-datelist-hour']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select" element + And I should see an "[name='datelist[minute]']" element + And I should see an "[id='edit-datelist-minute']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select" element + + # Datetime (datetime) + And I should see an "[name='datetime[date]']" element + And I should see an "[id='edit-datetime-date']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-date" element + And I should see an "[name='datetime[time]']" element + And I should see an "[id='edit-datetime-time']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-time" element + + # Email (email) + And I should see an "[name='email']" element + And I should see an "[id='edit-email']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-email" element + And I should see an ".form-email.form-element.form-element--type-email.form-element--api-email.form-control" element + + # Entity Autocomplete (entity_autocomplete) + And I should see an "[name='entity_autocomplete']" element + And I should see an "[id='edit-entity-autocomplete']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-autocomplete" element + + # File (file) + And I should see an "[name='files[file]']" element + And I should see an "[id='edit-file']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-file" element + + # Image Button (image_button) + And I should see an "[name='op'][type='image']" element + And I should see an "[id='edit-1--2']" element + And I should see an "[src='/themes/contrib/civictheme/assets/icons/download.svg']" element + + # Linkit (linkit) + And I should see an "[name='linkit']" element + And I should see an "[id='edit-linkit']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-text" element + + # Machine Name (machine_name) + And I should see an "[name='machine_name']" element + And I should see an "[id='edit-machine-name']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-text" element + + # Number (number) + And I should see an "[name='number']" element + And I should see an "[id='edit-number']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-number" element + And I should see an ".form-number.form-element.form-element--type-number.form-element--api-number.form-control" element + + # Password (password) + And I should see an "[name='password']" element + And I should see an "[id='edit-password']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-text" element + + # Password Confirm (password_confirm) + And I should see an "[name='password_confirm[pass1]']" element + And I should see an "[id='edit-password-confirm-pass1']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-text" element + And I should see an "[name='password_confirm[pass2]']" element + And I should see an "[id='edit-password-confirm-pass2']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-text" element + And I should see an ".form-text.form-element.form-element--type-password.form-element--api-password.form-control" element + And I should see an ".password-confirm.js-password-confirm" element + + # Path (path) + And I should see an "[name='path']" element + And I should see an "[id='edit-path']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-text" element + + # Radio (radio) + And I should see an "[name='radio']" element + And I should see an "[id='edit-radio']" element + And I should see an ".ct-radio.ct-theme-light.ct-field__control.form-radio" element + + # Radios (radios) + And I should see an "[name='radios']" element + And I should see an "[id='edit-radios-option1']" element + And I should see an ".ct-radio.ct-theme-light.ct-field__control.form-radio" element + And I should see an "[name='radios']" element + And I should see an "[id='edit-radios-option2']" element + And I should see an ".ct-radio.ct-theme-light.ct-field__control.form-radio" element + And I should see an "[name='radios']" element + And I should see an "[id='edit-radios-option3']" element + And I should see an ".ct-radio.ct-theme-light.ct-field__control.form-radio" element + + # Range (range) + And I should see an "[name='range']" element + And I should see an "[id='edit-range']" element + And I should not see an ".ct-input.ct-theme-light.ct-field__control.form-range" element + + # Search (search) + And I should see an "[name='search']" element + And I should see an "[id='edit-search']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-search" element + + # Select (select) + And I should not see an "[name='select']" element + And I should not see an "[id='edit-select']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select" element + + # Submit (submit) + And I should see an "[name='op'][type='submit']" element + And I should see an "[id='edit-1--3']" element + And I should see an ".ct-button.ct-theme-light.ct-button--primary.ct-button--submit" element + + # Table (table) + And I should see an "[id='edit-table']" element + And I should see an ".ct-table.ct-theme-light.ct-table--caption-before" element + + # Tel (tel) + And I should see an "[name='tel']" element + And I should see an "[id='edit-tel']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-tel" element + And I should see an ".form-tel.form-element.form-element--type-tel.form-element--api-tel.form-control" element + + # Textarea (textarea) + And I should see an "[name='textarea']" element + And I should see an "[id='edit-textarea']" element + And I should see an ".ct-textarea.ct-theme-light.ct-field__control.form-element" element + + # Textfield (textfield) + And I should see an "[name='textfield']" element + And I should see an "[id='edit-textfield']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text" element + + # Url (url) + And I should see an "[name='url']" element + And I should see an "[id='edit-url']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-url" element + And I should see an ".form-url.form-element.form-element--type-url.form-element--api-url.form-control" element + + # Weight (weight) + And I should see an "[name='weight']" element + And I should see an "[id='edit-weight']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select" element + + # Markup within a form + And I should see "Markup within a form" in the "h2" element + And I should see "Test content 1" in the "p" element + And I should see an ".ct-basic-content.ct-theme-light ul li" element + And I should see the text "List Item 1" + And I should see the text "List Item 2" + And I should see the text "List Item 3" + And I should see an ".ct-link" element + And I should see the link "Examples" with "https://www.drupal.org/docs/8/api/form-api/examples" + + @api + Scenario: Fields appear as expected in admin theme + Given I am logged in as a user with the "Site Administrator" role + When I visit "/admin/structure/civictheme-core-form-elements" + + Then I should see "Core Form Elements Form (Admin Theme)" in the ".page-title" element + + # Button (button) + And should see an "[name='op']" element + And should not see an "[name='op'].required" element + And should not see an "[name='op'][disabled]" element + And should see an "[value='Button (button)']" element + And should not see an "[data-component-name='button']" element + And should see an "[type='submit']" element + And I should not see an ".ct-button.ct-theme-light.ct-button--primary.ct-button--submit.ct-button--regular" element + And I should see an ".button.js-form-submit.form-submit" element + + # Checkbox (checkbox) + And I should see an "[name='checkbox']" element + And I should see an "[id='edit-checkbox']" element + And I should see an ".form-checkbox.form-boolean.form-boolean--type-checkbox" element + + # Checkboxes (checkboxes) + And I should see an "[name='checkboxes[option1]']" element + And I should see an "[id='edit-checkboxes-option1']" element + And I should see an ".form-checkbox.form-boolean.form-boolean--type-checkbox" element + And I should see an "[name='checkboxes[option2]']" element + And I should see an "[id='edit-checkboxes-option2']" element + And I should see an ".form-checkbox.form-boolean.form-boolean--type-checkbox" element + And I should see an "[name='checkboxes[option3]']" element + And I should see an "[id='edit-checkboxes-option3']" element + And I should see an ".form-checkbox.form-boolean.form-boolean--type-checkbox" element + + # Datelist (datelist) + And I should see an "[name='datelist[year]']" element + And I should see an "[id='edit-datelist-year']" element + And I should see an ".form-select.form-element.form-element--type-select" element + And I should see an "[name='datelist[month]']" element + And I should see an "[id='edit-datelist-month']" element + And I should see an ".form-select.form-element.form-element--type-select" element + And I should see an "[name='datelist[day]']" element + And I should see an "[id='edit-datelist-day']" element + And I should see an ".form-select.form-element.form-element--type-select" element + And I should see an "[name='datelist[hour]']" element + And I should see an "[id='edit-datelist-hour']" element + And I should see an ".form-select.form-element.form-element--type-select" element + And I should see an "[name='datelist[minute]']" element + And I should see an "[id='edit-datelist-minute']" element + And I should see an ".form-select.form-element.form-element--type-select" element + + # Datetime (datetime) + And I should see an "[name='datetime[date]']" element + And I should see an "[id='edit-datetime-date']" element + And I should see an ".form-date.form-element.form-element--type-date" element + And I should see an "[name='datetime[time]']" element + And I should see an "[id='edit-datetime-time']" element + And I should see an ".form-time.form-element.form-element--type-time" element + + # Email (email) + And I should see an "[name='email']" element + And I should see an "[id='edit-email']" element + And I should see an ".form-email.form-element.form-element--type-email" element + + # Entity Autocomplete (entity_autocomplete) + And I should see an "[name='entity_autocomplete']" element + And I should see an "[id='edit-entity-autocomplete']" element + And I should see an ".form-autocomplete.form-text.form-element.form-element--type-text" element + + # File (file) + And I should see an "[name='files[file]']" element + And I should see an "[id='edit-file']" element + And I should see an ".form-file.form-element.form-element--type-file" element + + # Image Button (image_button) + And I should see an "[name='op'][type='image']" element + And I should see an "[id='edit-1--2']" element + And I should see an "[src='/themes/contrib/civictheme/assets/icons/download.svg']" element + + # Linkit (linkit) + And I should see an "[name='linkit']" element + And I should see an "[id='edit-linkit']" element + And I should see an ".form-text.form-element.form-element--type-text" element + + # Machine Name (machine_name) + And I should see an "[name='machine_name']" element + And I should see an "[id='edit-machine-name']" element + And I should see an ".form-text.required.form-element.form-element--type-text" element + + # Number (number) + And I should see an "[name='number']" element + And I should see an "[id='edit-number']" element + And I should see an ".form-number.form-element.form-element--type-number" element + + # Password (password) + And I should see an "[name='password']" element + And I should see an "[id='edit-password']" element + And I should see an ".form-text.form-element.form-element--type-password" element + + # Password Confirm (password_confirm) + And I should see an "[name='password_confirm[pass1]']" element + And I should see an "[id='edit-password-confirm-pass1']" element + And I should see an ".form-text.form-element.form-element--type-password" element + And I should see an "[name='password_confirm[pass2]']" element + And I should see an "[id='edit-password-confirm-pass2']" element + And I should see an ".form-text.form-element.form-element--type-password" element + + # Path (path) + And I should see an "[name='path']" element + And I should see an "[id='edit-path']" element + And I should see an ".form-text.form-element.form-element--type-text" element + + # Radio (radio) + And I should see an "[name='radio']" element + And I should see an "[id='edit-radio']" element + And I should see an ".form-radio.form-boolean.form-boolean--type-radio" element + + # Radios (radios) + And I should see an "[name='radios']" element + And I should see an "[id='edit-radios-option1']" element + And I should see an ".form-radio.form-boolean.form-boolean--type-radio" element + And I should see an "[name='radios']" element + And I should see an "[id='edit-radios-option2']" element + And I should see an ".form-radio.form-boolean.form-boolean--type-radio" element + And I should see an "[name='radios']" element + And I should see an "[id='edit-radios-option3']" element + And I should see an ".form-radio.form-boolean.form-boolean--type-radio" element + + # Range (range) + And I should see an "[name='range']" element + And I should see an "[id='edit-range']" element + And I should see an ".form-range" element + + # Search (search) + And I should see an "[name='search']" element + And I should see an "[id='edit-search']" element + And I should see an ".form-search.form-element.form-element--type-search" element + + # Select (select) + And I should see an "[name='select']" element + And I should see an "[id='edit-select']" element + And I should see an ".form-select.form-element.form-element--type-select" element + + # Submit (submit) + And I should see an "[name='op'][type='submit']" element + And I should see an "[id='edit-1--3']" element + And I should see an ".button.js-form-submit.form-submit" element + + # Table (table) + And I should see an "[id='edit-table']" element + And I should see an ".responsive-enabled" element + + # Tel (tel) + And I should see an "[name='tel']" element + And I should see an "[id='edit-tel']" element + And I should see an ".form-tel.form-element.form-element--type-tel" element + + # Textarea (textarea) + And I should see an "[name='textarea']" element + And I should see an "[id='edit-textarea']" element + And I should see an ".form-textarea.resize-vertical.form-element.form-element--type-textarea" element + + # Textfield (textfield) + And I should see an "[name='textfield']" element + And I should see an "[id='edit-textfield']" element + And I should see an ".form-text.form-element.form-element--type-text" element + + # Url (url) + And I should see an "[name='url']" element + And I should see an "[id='edit-url']" element + And I should see an ".form-url.form-element.form-element--type-url" element + + # Weight (weight) + And I should see an "[name='weight']" element + And I should see an "[id='edit-weight']" element + And I should see an ".form-select.form-element.form-element--type-select" element + + # Markup within a form + And I should see the text "Markup within a form" + And I should see the text "Test content 1" + And I should see the text "List Item 1" + And I should see the text "List Item 2" + And I should see the text "List Item 3" + And I should see the link "Examples" with "https://www.drupal.org/docs/8/api/form-api/examples" diff --git a/tests/behat/features/form_elements.webform_form_elements.feature b/tests/behat/features/form_elements.webform_form_elements.feature new file mode 100644 index 000000000..627315847 --- /dev/null +++ b/tests/behat/features/form_elements.webform_form_elements.feature @@ -0,0 +1,265 @@ +@p1 @civictheme @civictheme_form_updates @civictheme_webform +Feature: Test a sample of the webform elements in the webform + + @api + Scenario: Fields appear as expected + Given I am an anonymous user + When I visit "form/civictheme-test-webform-fields" + + Then I should see "Civictheme Test Webform - Fields" in the ".ct-banner__title" element + + # Checkbox (checkbox) + And I should see an "[name='checkbox']" element + And I should see an "[id='edit-checkbox']" element + And I should see an ".ct-checkbox.ct-theme-light.ct-field__control" element + And I should see an ".form-checkbox" element + + # Text field (textfield) + And I should see an "[name='text_field']" element + And I should see an "[id='edit-text-field']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control" element + And I should see an ".form-text" element + + # Radio buttons (select_to_display_textarea) + And I should see an "[name='select_to_display_textarea']" element + And I should see an "[id='edit-select-to-display-textarea-Display-textarea']" element + And I should see an "[id='edit-select-to-display-textarea-Do-not-display-textarea']" element + And I should see an ".ct-radio.ct-theme-light.ct-field__control" element + + # Textarea (textarea) + And I should see an "[name='textarea']" element + And I should see an "[id='edit-textarea']" element + And I should see an ".ct-textarea.ct-theme-light.ct-field__control" element + + # Checkbox to display textfield (checkbox_visible_text_field_1) + And I should see an "[name='checkbox_visible_text_field_1']" element + And I should see an "[id='edit-checkbox-visible-text-field-1']" element + And I should see an ".ct-checkbox.ct-theme-light.ct-field__control.form-checkbox" element + + # Text field 1 (text_field_1) + And I should see an "[name='text_field_1']" element + And I should see an "[id='edit-text-field-1']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text" element + + # Autocomplete (autocomplete) + And I should see an "[name='autocomplete']" element + And I should see an "[id='edit-autocomplete']" element + And I should see an ".js-form-item.form-item.js-form-type-webform-autocomplete.form-item-autocomplete.js-form-item-autocomplete" element + + # Color (color) + And I should see an "[name='color']" element + And I should see an "[id='edit-color']" element + And I should see an ".ct-field.ct-theme-light.ct-field--color.ct-field--vertical.form-item.js-form-item.js-form-type-color.form-type-color.form-type--color.js-form-item-color.form-item-color.form-item--color" element + + # Email (email) + And I should see an "[name='email']" element + And I should see an "[id='edit-email']" element + And I should see an ".ct-field.ct-theme-light.ct-field--email.ct-field--vertical.form-item.js-form-item.js-form-type-email.form-type-email.form-type--email.js-form-item-email.form-item-email.form-item--email" element + + # Email confirm (email_confirm) + And I should see an "[name='email_confirm[mail_1]']" element + And I should see an "[id='edit-email-confirm-mail-1']" element + And I should see an ".ct-field.ct-theme-light.ct-field--email.ct-field--vertical.form-item.js-form-item.js-form-type-email.form-type-email.form-type--email.js-form-item-email-confirm-mail-1.form-item-email-confirm-mail-1.form-item--email-confirm-mail-1" element + And I should see an "[name='email_confirm[mail_2]']" element + And I should see an "[id='edit-email-confirm-mail-2']" element + And I should see an ".ct-field.ct-theme-light.ct-field--email.ct-field--vertical.form-item.js-form-item.js-form-type-email.form-type-email.form-type--email.js-form-item-email-confirm-mail-2.form-item-email-confirm-mail-2.form-item--email-confirm-mail-2" element + + # Email multiple (email_multiple) + And I should see an "[name='email_multiple']" element + And I should see an "[id='edit-email-multiple']" element + And I should see an ".js-form-item.form-item.js-form-type-webform-email-multiple.form-item-email-multiple.js-form-item-email-multiple" element + + # Number (number) + And I should see an "[name='number']" element + And I should see an "[id='edit-number']" element + And I should see an ".ct-field.ct-theme-light.ct-field--number.ct-field--vertical.form-item.js-form-item.js-form-type-number.form-type-number.form-type--number.js-form-item-number.form-item-number.form-item--number" element + + # Number - Max 10 - Min 4 (number_max_10_min_4) + And I should see an "[name='number_max_10_min_4']" element + And I should see an "[id='edit-number-max-10-min-4']" element + And I should see an ".ct-field.ct-theme-light.ct-field--number.ct-field--vertical.form-item.js-form-item.js-form-type-number.form-type-number.form-type--number.js-form-item-number-max-10-min-4.form-item-number-max-10-min-4.form-item--number-max-10-min-4" element + + # Range (range) + And I should see an "[name='range']" element + And I should see an "[id='edit-range']" element + And I should see an ".js-form-item.form-item.js-form-type-range.form-item-range.js-form-item-range" element + + # Range Min 10 Max 100 Steps 10 (range_min_10_max_100_steps_10) + And I should see an "[name='range_min_10_max_100_steps_10']" element + And I should see an "[id='edit-range-min-10-max-100-steps-10']" element + And I should see an ".js-form-item.form-item.js-form-type-range.form-item-range-min-10-max-100-steps-10.js-form-item-range-min-10-max-100-steps-10" element + + # Rating (rating) + And I should see an "[name='rating']" element + And I should see an "[id='edit-rating']" element + And I should see an ".js-form-item.form-item.js-form-type-webform-rating.form-item-rating.js-form-item-rating" element + + # Telephone (telephone) + And I should see an "[name='telephone']" element + And I should see an "[id='edit-telephone']" element + And I should see an ".ct-field.ct-theme-light.ct-field--tel.ct-field--vertical.form-item.js-form-item.js-form-type-tel.form-type-tel.form-type--tel.js-form-item-telephone.form-item-telephone.form-item--telephone" element + + # Terms of service (terms_of_service) + And I should see an "[name='terms_of_service']" element + And I should see an "[id='edit-terms-of-service']" element + And I should see an ".ct-field.ct-theme-light.ct-field--checkbox.ct-field--vertical.form-type-webform-terms-of-service.js-form-type-webform-terms-of-service.form-item.js-form-item.js-form-type-checkbox.form-type-checkbox.form-type--checkbox.js-form-item-terms-of-service.form-item-terms-of-service.form-item--terms-of-service" element + + # URL (url) + And I should see an "[name='url']" element + And I should see an "[id='edit-url']" element + And I should see an ".ct-field.ct-theme-light.ct-field--url.ct-field--vertical.form-item.js-form-item.js-form-type-url.form-type-url.form-type--url.js-form-item-url.form-item-url.form-item--url" element + + # Address (address) + And I should see an "[name='address[address]']" element + And I should see an "[id='edit-address-address']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='address[address_2]']" element + And I should see an "[id='edit-address-address-2']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='address[city]']" element + And I should see an "[id='edit-address-city']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='address[state_province]']" element + And I should see an "[id='edit-address-state-province']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select.form-element.form-element--type-select.form-element--api-select.form-control" element + + And I should see an "[name='address[postal_code]']" element + And I should see an "[id='edit-address-postal-code']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='address[country]']" element + And I should see an "[id='edit-address-country']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select.form-element.form-element--type-select.form-element--api-select.form-control" element + + # Contact (contact) + And I should see an "[name='contact[name]']" element + And I should see an "[id='edit-contact-name']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='contact[company]']" element + And I should see an "[id='edit-contact-company']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='contact[email]']" element + And I should see an "[id='edit-contact-email']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-email.form-element.form-element--type-email.form-element--api-email.form-control" element + + And I should see an "[name='contact[phone]']" element + And I should see an "[id='edit-contact-phone']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-tel.form-element.form-element--type-tel.form-element--api-tel.form-control" element + + And I should see an "[name='contact[address]']" element + And I should see an "[id='edit-contact-address']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='contact[address_2]']" element + And I should see an "[id='edit-contact-address-2']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='contact[city]']" element + And I should see an "[id='edit-contact-city']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='contact[state_province]']" element + And I should see an "[id='edit-contact-state-province']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select.form-element.form-element--type-select.form-element--api-select.form-control" element + + And I should see an "[name='contact[postal_code]']" element + And I should see an "[id='edit-contact-postal-code']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='contact[country]']" element + And I should see an "[id='edit-contact-country']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select.form-element.form-element--type-select.form-element--api-select.form-control" element + + # Link (link) + And I should see an "[name='link[title]']" element + And I should see an "[id='edit-link-title']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='link[url]']" element + And I should see an "[id='edit-link-url']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-url.form-element.form-element--type-url.form-element--api-url.form-control" element + + # Name (name) + And I should see an "[name='name[title][select]']" element + And I should see an "[id='edit-name-title-select']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-element.form-element--type-select.form-element--api-select.form-control" element + + And I should see an "[name='name[first]']" element + And I should see an "[id='edit-name-first']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='name[middle]']" element + And I should see an "[id='edit-name-middle']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='name[last]']" element + And I should see an "[id='edit-name-last']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='name[suffix]']" element + And I should see an "[id='edit-name-suffix']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + And I should see an "[name='name[degree]']" element + And I should see an "[id='edit-name-degree']" element + And I should see an ".ct-textfield.ct-theme-light.ct-field__control.form-text.form-element.form-element--type-textfield.form-element--api-textfield.form-control" element + + # Telephone advanced (telephone_advanced) + And I should see an "[name='telephone_advanced[type]']" element + And I should see an "[id='edit-telephone-advanced-type']" element + And I should see an ".ct-select.ct-theme-light.ct-field__control.form-select.form-element.form-element--type-select.form-element--api-select.form-control" element + + And I should see an "[name='telephone_advanced[phone]']" element + And I should see an "[id='edit-telephone-advanced-phone']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.js-webform-telephone-international.webform-webform-telephone-international.form-tel.form-element.form-element--type-tel.form-element--api-tel.form-control" element + + And I should see an "[name='telephone_advanced[ext]']" element + And I should see an "[id='edit-telephone-advanced-ext']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-number.form-element.form-element--type-number.form-element--api-number.form-control" element + # Audio file + And I should see an "[name='files[audio_file]']" element + And I should see an "[id='edit-audio-file-upload']" element + And I should see an ".ct-button.ct-theme-light.ct-button--primary.ct-button--submit.ct-button--regular.js-hide.button.js-form-submit.form-submit" element + + # Document file + And I should see an "[name='files[document_file]']" element + And I should see an "[id='edit-document-file-upload']" element + And I should see an ".ct-button.ct-theme-light.ct-button--primary.ct-button--submit.ct-button--regular.js-hide.button.js-form-submit.form-submit" element + + # File + And I should see an "[name='files[file]']" element + And I should see an "[id='edit-file-upload']" element + And I should see an ".ct-button.ct-theme-light.ct-button--primary.ct-button--submit.ct-button--regular.js-hide.button.js-form-submit.form-submit" element + + # Image file + And I should see an "[name='files[image_file]']" element + And I should see an "[id='edit-image-file-upload']" element + And I should see an ".ct-button.ct-theme-light.ct-button--primary.ct-button--submit.ct-button--regular.js-hide.button.js-form-submit.form-submit" element + + # Video file + And I should see an "[name='files[video_file]']" element + And I should see an "[id='edit-video-file-upload']" element + And I should see an ".ct-button.ct-theme-light.ct-button--primary.ct-button--submit.ct-button--regular.js-hide.button.js-form-submit.form-submit" element + # Date (date) + And I should see an "[name='date']" element + And I should see an "[id='edit-date']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-date" element + + # Date/time (date_time) + And I should see an "[name='date_time[date]']" element + And I should see an "[id='edit-date-time-date']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-date" element + And I should see an "[name='date_time[time]']" element + And I should see an "[id='edit-date-time-time']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control.form-time" element + + # Entity autocomplete (entity_autocomplete) + And I should see an "[name='entity_autocomplete']" element + And I should see an "[id='edit-entity-autocomplete']" element + And I should see an ".ct-input.ct-theme-light.ct-field__control" element + And I should see an ".form-autocomplete.form-text.form-element.form-element--type-entity_autocomplete.form-element--api-entity_autocomplete.form-control" element diff --git a/web/modules/custom/civictheme_dev/civictheme_dev.module b/web/modules/custom/civictheme_dev/civictheme_dev.module index 95a30f7cd..1bda0f600 100644 --- a/web/modules/custom/civictheme_dev/civictheme_dev.module +++ b/web/modules/custom/civictheme_dev/civictheme_dev.module @@ -39,10 +39,6 @@ function civictheme_dev_preprocess_html(array &$variables): void { * Implements hook_styleguide_alter(). */ function civictheme_dev_styleguide_alter(array &$items): void { - // Remove password_confirm field from the form as CivicTheme does not support - // it. - unset($items['form']['content']['password_confirm']); - _civictheme_dev_styleguide__form_test__textfield($items); _civictheme_dev_styleguide__form_test__textarea($items); _civictheme_dev_styleguide__form_test__select($items); diff --git a/web/modules/custom/civictheme_dev/civictheme_dev.routing.yml b/web/modules/custom/civictheme_dev/civictheme_dev.routing.yml new file mode 100644 index 000000000..3581f5c99 --- /dev/null +++ b/web/modules/custom/civictheme_dev/civictheme_dev.routing.yml @@ -0,0 +1,14 @@ +civictheme_dev.styleguide_form: + path: '/civictheme-core-form-elements' + defaults: + _form: 'Drupal\civictheme_dev\Form\CoreFormElementsForm' + _title: 'CivicTheme Core Form Elements Form' + requirements: + _permission: 'access content' +civictheme_dev.admin_styleguide_form: + path: '/admin/structure/civictheme-core-form-elements' + defaults: + _form: 'Drupal\civictheme_dev\Form\CoreFormElementsForm' + _title: 'Core Form Elements Form (Admin Theme)' + requirements: + _permission: 'view the administration theme' diff --git a/web/modules/custom/civictheme_dev/config/install/webform.webform.civictheme_test_webform_fields.yml b/web/modules/custom/civictheme_dev/config/install/webform.webform.civictheme_test_webform_fields.yml new file mode 100644 index 000000000..037263cca --- /dev/null +++ b/web/modules/custom/civictheme_dev/config/install/webform.webform.civictheme_test_webform_fields.yml @@ -0,0 +1,383 @@ +langcode: en +status: open +dependencies: { } +weight: 0 +open: null +close: null +uid: 1 +template: false +archive: false +id: civictheme_test_webform_fields +title: 'Civictheme Test Webform - Fields' +description: '

This form will be used to test the available webform fields, including conditional form elements.

' +categories: { } +elements: |- + basic_elements_container: + '#type': container + checkbox: + '#type': checkbox + '#title': Checkbox + hidden: + '#type': hidden + '#title': Hidden + text_field: + '#type': textfield + '#title': 'Text field' + conditional_container: + '#type': container + select_to_display_textarea: + '#type': radios + '#title': 'Select to display textarea' + '#options': + 'Display textarea': 'Display textarea' + 'Do not display textarea': 'Do not display textarea' + textarea: + '#type': textarea + '#title': Textarea + '#states': + visible: + ':input[name="checkbox_visible_text_field_1"]': + checked: true + checkbox_visible_text_field_1: + '#type': checkbox + '#title': 'Check to display textfield' + text_field_1: + '#type': textfield + '#title': 'Text field 1' + '#states': + visible: + ':input[name="checkbox_visible_text_field_1"]': + checked: true + advanced_elements_container: + '#type': container + autocomplete: + '#type': webform_autocomplete + '#title': Autocomplete + '#autocomplete_items': gender + color: + '#type': color + '#title': Color + email: + '#type': email + '#title': Email + email_confirm: + '#type': webform_email_confirm + '#title': 'Email confirm' + '#confirm__title': 'Email confirm title - Test' + '#confirm__description': '

Email confirm description - test

' + '#confirm__placeholder': 'Email confirm placeholder- Test' + email_multiple: + '#type': webform_email_multiple + '#title': 'Email multiple' + number: + '#type': number + '#title': Number + number_max_10_min_4: + '#type': number + '#title': 'Number - Max 10 - Min 4' + '#min': 4 + '#max': 10 + range: + '#type': range + '#title': Range + range_min_10_max_100_steps_10: + '#type': range + '#title': 'Range Min 10 Max 100 Steps 10' + '#min': 10 + '#step': 10 + rating: + '#type': webform_rating + '#title': Rating + telephone: + '#type': tel + '#title': Telephone + terms_of_service: + '#type': webform_terms_of_service + '#terms_title': 'Terms of service' + '#terms_content': '

Terms of service test content

' + url: + '#type': url + '#title': URL + composite_elements_container: + '#type': container + address: + '#type': webform_address + '#title': Address + contact: + '#type': webform_contact + '#title': Contact + link: + '#type': webform_link + '#title': Link + name: + '#type': webform_name + '#title': Name + telephone_advanced: + '#type': webform_telephone + '#title': 'Telephone advanced' + markup_container: + '#type': container + advanced_html_text: + '#type': processed_text + '#text': "

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

" + '#format': civictheme_rich_text + label: + '#type': label + '#title': Label + more: + '#type': webform_more + file_uploads_container: + '#type': container + audio_file: + '#type': webform_audio_file + '#title': 'Audio file' + document_file: + '#type': webform_document_file + '#title': 'Document file' + file: + '#type': managed_file + '#title': File + image_file: + '#type': webform_image_file + '#title': 'Image file' + video_file: + '#type': webform_video_file + '#title': 'Video file' + options_element_container: + '#type': container + checkboxes: + '#type': checkboxes + '#title': Checkboxes + '#options': + 'Test Option 1': 'Test Option 1' + 'Test Option 2': 'Test Option 2' + 'Test Option 3': 'Test Option 3' + '#options_randomize': true + '#options_all': true + '#options_none': true + checkboxes_other: + '#type': webform_checkboxes_other + '#title': 'Checkboxes other' + '#options': education + '#options_randomize': true + radios: + '#type': radios + '#title': Radios + '#options': marital_status + radios_other: + '#type': webform_radios_other + '#title': 'Radios other' + '#options': phone_types + select: + '#type': select + '#title': Select + '#options': country_names + select_other: + '#type': webform_select_other + '#title': 'Select other' + '#options': ethnicity + datetime_elements_container: + '#type': container + date: + '#type': date + '#title': Date + date_time: + '#type': datetime + '#title': Date/time + entity_reference_container: + '#type': container + entity_autocomplete: + '#type': entity_autocomplete + '#title': 'Entity autocomplete' + '#target_type': node + '#selection_handler': 'default:node' + '#selection_settings': + target_bundles: + civictheme_alert: civictheme_alert + civictheme_event: civictheme_event + civictheme_page: civictheme_page +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + ajax_progress_type: '' + ajax_effect: '' + ajax_speed: null + page: true + page_submit_path: '' + page_confirm_path: '' + page_theme_name: '' + form_title: both + form_submit_once: false + form_open_message: '' + form_close_message: '' + form_exception_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_disable_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_autofocus: false + form_details_toggle: false + form_reset: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + form_attributes: { } + form_method: '' + form_action: '' + share: false + share_node: false + share_theme_name: '' + share_title: true + share_page_body_attributes: { } + submission_label: '' + submission_exception_message: '' + submission_locked_message: '' + submission_log: false + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_progress_states: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_auto_forward: true + wizard_auto_forward_hide_next_button: false + wizard_keyboard: true + wizard_track: '' + wizard_prev_button_label: '' + wizard_next_button_label: '' + wizard_toggle: false + wizard_toggle_show_label: '' + wizard_toggle_hide_label: '' + wizard_page_type: container + wizard_page_title_tag: h2 + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + draft_pending_single_message: '' + draft_pending_multiple_message: '' + confirmation_type: page + confirmation_url: '' + confirmation_title: '' + confirmation_message: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + confirmation_update: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + results_customize: false + token_view: false + token_update: false + token_delete: false + serial_disabled: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } +variants: { } diff --git a/web/modules/custom/civictheme_dev/src/Form/CoreFormElementsForm.php b/web/modules/custom/civictheme_dev/src/Form/CoreFormElementsForm.php new file mode 100644 index 000000000..5415b3c67 --- /dev/null +++ b/web/modules/custom/civictheme_dev/src/Form/CoreFormElementsForm.php @@ -0,0 +1,311 @@ +get('plugin.manager.element_info'), + $container->get('entity_type.manager'), + $container->get('theme_handler'), + ); + } + + /** + * {@inheritDoc} + */ + public function getFormId(): string { + return 'styleguide_form'; + } + + /** + * {@inheritDoc} + * + * @phpstan-ignore-next-line + * @SuppressWarnings(PHPMD.StaticAccess) + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + // Getting form element plugin manager. + $plugin_manager = $this->elementInfoManager; + + // Getting all defaults form element types. + $element_types = $plugin_manager->getDefinitions(); + $element_types = array_filter($element_types, static function (array $element) : bool { + $provider_list = [ + 'core', + 'linkit', + ]; + return in_array($element['provider'], $provider_list); + }); + ksort($element_types); + $form_element_groups = array_chunk($element_types, 5); + foreach ($form_element_groups as $key => $form_element_group) { + $group_id = 'form_element_group_' . $key; + $form[$group_id] = [ + '#type' => 'container', + '#civictheme_form_element' => TRUE, + ]; + // Creating form fields for each element. + foreach ($form_element_group as $form_element_type) { + $form_element_id = $form_element_type['id'] ?? NULL; + if ($form_element_id === NULL) { + continue; + } + $element = $plugin_manager->createInstance($form_element_id); + if (!$element instanceof FormElement) { + continue; + } + $form[$group_id][$form_element_id] = $this->createFormElement($form_element_id, $element); + } + } + + // Test non-form element html elements in form. + // Add more types as we support more non-form elements in forms. + // @see _civictheme_form_alter__non_form_elements. + $form['non_form_elements'] = [ + '#type' => 'container', + ]; + $form['non_form_elements']['markup'] = [ + '#type' => 'markup', + '#markup' => '

Markup within a form

Test content 1

', + ]; + $form['non_form_elements']['link'] = [ + '#type' => 'link', + '#title' => $this->t('Examples'), + '#url' => Url::fromUri('https://www.drupal.org/docs/8/api/form-api/examples'), + ]; + + return $form; + } + + /** + * Create form element. + * + * @param string $id + * Element id. + * @param \Drupal\Core\Render\Element\FormElement $element + * Actual element. + * + * @return array + * Form element. + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + protected function createFormElement(string $id, FormElement $element): array { + $form_element = [ + '#type' => $id, + '#title' => ucwords(str_replace('_', ' ', $id)) . ' (' . $id . ')', + '#description' => $this->t('Test of @id element.', ['@id' => $id]), + ]; + + switch ($id) { + case 'entity_autocomplete': + $form_element = $this->createEntityAutoCompleteFormElement($form_element, $element); + break; + + case 'radios': + case 'checkboxes': + $form_element = $this->createOptionsFormElement($form_element, $element); + break; + + case 'tableselect': + case 'table': + $form_element = $this->createTableFormElement($form_element, $element); + break; + + case 'image_button': + $form_element = $this->createImageButton($form_element, $element); + break; + + case 'button': + case 'submit': + $form_element = $this->createButton($form_element, $element); + break; + + case 'machine_name': + $form_element += [ + '#default_value' => '', + '#machine_name' => [ + 'exists' => function ($value) : bool { + return $this->entityTypeManager->getStorage('node_type')->load($value) !== NULL; + }, + ], + '#description' => $this->t("A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores."), + ]; + break; + } + + return $form_element; + } + + /** + * {@inheritDoc} + * + * @phpstan-ignore-next-line + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + } + + /** + * Generates entity autocomplete form element. + * + * @param array $form_element + * Form element. + * @param \Drupal\Core\Render\Element\FormElement $element + * Form element. + * + * @return array + * Form element. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function createEntityAutoCompleteFormElement(array $form_element, FormElement $element): array { + $form_element['#type'] = 'entity_autocomplete'; + $form_element['#target_type'] = 'node'; + $form_element['#selection_settings'] = [ + 'target_bundles' => ['civictheme_page'], + ]; + + return $form_element; + } + + /** + * Generates options-style form elements. + * + * @param array $form_element + * Form element. + * @param \Drupal\Core\Render\Element\FormElement $element + * Form element. + * + * @return array + * Form element. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function createOptionsFormElement(array $form_element, FormElement $element): array { + $form_element['#options'] = [ + 'option1' => 'Option 1', + 'option2' => 'Option 2', + 'option3' => 'Option 3', + ]; + + return $form_element; + } + + /** + * Create table form element helper. + * + * @param array $form_element + * Form element. + * @param \Drupal\Core\Render\Element\FormElement $element + * Form element. + * + * @return array + * Form element. + * + * @SuppressWarnings(PHPMD.ElseExpression) + */ + protected function createTableFormElement(array $form_element, FormElement $element): array { + $form_element['#header'] = ['Header 1', 'Header 2', 'Header 3']; + if ($element->getPluginId() === 'tableselect') { + $form_element['#options'] = [ + 'row1' => ['Row 1', 'Row 1', 'Row 1'], + 'row2' => ['Row 2', 'Row 2', 'Row 2'], + 'row3' => ['Row 3', 'Row 3', 'Row 3'], + 'row4' => ['Row 4', 'Row 4', 'Row 4'], + ]; + } + else { + $form_element['#rows'] = [ + ['Header 1' => 'Row 1', 'Header 2' => 'Row 1', 'Header 3' => 'Row 1'], + ['Header 1' => 'Row 1', 'Header 2' => 'Row 1', 'Header 3' => 'Row 1'], + ['Header 1' => 'Row 1', 'Header 2' => 'Row 1', 'Header 3' => 'Row 1'], + ['Header 1' => 'Row 1', 'Header 2' => 'Row 1', 'Header 3' => 'Row 1'], + ]; + } + + return $form_element; + } + + /** + * Create image button helper. + * + * @param array $form_element + * Form element. + * @param \Drupal\Core\Render\Element\FormElement $element + * Form element. + * + * @return array + * Form element. + * + * @phpstan-ignore-next-line + */ + protected function createImageButton(array $form_element, FormElement $element): array { + $theme_path = $this->themeHandler->getTheme('civictheme')->getPath(); + $icon_path = '/' . $theme_path . '/assets/icons/download.svg'; + $form_element['#src'] = $icon_path; + + return $this->createButton($form_element, $element); + } + + /** + * Create button helper. + * + * @param array $form_element + * Form element. + * @param \Drupal\Core\Render\Element\FormElement $element + * Form element. + * + * @return array + * Form element. + * + * @phpstan-ignore-next-line + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function createButton(array $form_element, FormElement $element): array { + $form_element['#value'] = $form_element['#title']; + + return [ + [ + '#type' => 'html_tag', + '#tag' => 'h6', + '#value' => $form_element['#title'], + ], + $form_element, + ]; + } + +} diff --git a/web/themes/contrib/civictheme/includes/form.inc b/web/themes/contrib/civictheme/includes/form.inc index da2000d4d..01f0d54b7 100644 --- a/web/themes/contrib/civictheme/includes/form.inc +++ b/web/themes/contrib/civictheme/includes/form.inc @@ -53,28 +53,35 @@ function _civictheme_form_alter__webform_submission(array &$form, FormStateInter * if required. * * @SuppressWarnings(PHPMD.StaticAccess) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ function _civictheme_form_alter__non_form_elements(array &$elements): void { $element_keys = Element::children($elements); - foreach ($element_keys as $element_key) { - if (empty($elements[$element_key]['#type'])) { - continue; - } - - $nested_elements = Element::children($elements[$element_key]); - if (!empty($nested_elements)) { - _civictheme_form_alter__non_form_elements($elements[$element_key]); + $element_type = $elements[$element_key]['#type'] ?? NULL; + if (is_array($elements[$element_key])) { + $nested_elements = Element::children($elements[$element_key]); + if (!empty($nested_elements)) { + _civictheme_form_alter__non_form_elements($elements[$element_key]); + } } - - // Add elements to modify to this switch. - switch ($elements[$element_key]['#type']) { - case 'link': - _civictheme_form_alter__non_form_elements__link($elements[$element_key]); - break; - - default: - continue 2; + if ($element_type !== NULL) { + // Add elements to modify to this switch. + switch ($elements[$element_key]['#type']) { + case 'link': + _civictheme_form_alter__non_form_elements__link($elements[$element_key]); + break; + + case 'html_tag': + case 'markup': + case 'processed_text': + case 'webform_markup': + _civictheme_form_element__non_form_elements__markup($elements[$element_key]); + break; + + default: + continue 2; + } } } } @@ -92,7 +99,21 @@ function _civictheme_form_alter__non_form_elements__link(array &$element): void 'ct-button--secondary', 'ct-theme-' . $element_theme, ]); + return; } + $element['#attributes']['class'][] = 'ct-link'; +} + +/** + * Preprocesses markup render elements rendered in forms. + */ +function _civictheme_form_element__non_form_elements__markup(array &$element): void { + $element = [ + '#content' => $element, + '#component_theme' => _civictheme_form_element_get_component_theme($element), + '#vertical_spacing' => 'none', + '#theme' => 'civictheme_basic_content', + ]; } /** @@ -100,7 +121,7 @@ function _civictheme_form_alter__non_form_elements__link(array &$element): void * * @SuppressWarnings(PHPMD.StaticAccess) */ -function _civictheme_form_preprocess_hidden_form_elements(array $variables): void { +function _civictheme_form_preprocess_hidden_form_elements(array &$variables): void { if (empty($variables['form'])) { return; } diff --git a/web/themes/contrib/civictheme/includes/form_element.inc b/web/themes/contrib/civictheme/includes/form_element.inc index e39cf88fb..25e10bdc4 100644 --- a/web/themes/contrib/civictheme/includes/form_element.inc +++ b/web/themes/contrib/civictheme/includes/form_element.inc @@ -91,6 +91,7 @@ declare(strict_types=1); use Drupal\civictheme\CivicthemeConstants; use Drupal\Component\Utility\Html; use Drupal\Core\Render\Element; +use Drupal\Core\Template\Attribute; /** * Implements hook_theme_suggestions_form_element_alter(). @@ -102,8 +103,75 @@ function civictheme_theme_suggestions_form_element_alter(array &$suggestions, ar // Note that sub-themes can opt-out from this behavior by implementing // hook_theme_suggestions_form_element_alter() and removing the suggestions // added here based on some conditions. - $suggestions[] = 'form_element__civictheme_field'; - $suggestions[] = 'form_element__civictheme_field__' . $variables['element']['#type']; + // @see _civictheme_supported_form_element() for a list of supported types. + $supported_form_element = isset($variables['element']['#type']) && _civictheme_supported_form_element($variables['element']); + if ($supported_form_element) { + $suggestions[] = 'form_element__civictheme_field'; + $suggestions[] = 'form_element__civictheme_field__' . $variables['element']['#type']; + } +} + +/** + * Check whether form render element is supported directly by CivicTheme. + * + * @param array $form_element + * The form element. + * + * This list has been derived with testing elements that extend + * \Drupal\Core\Render\Element\FormElement. + * + * @return bool + * Whether the form element is directly supported by CivicTheme. + */ +function _civictheme_supported_form_element(array $form_element): bool { + // Form elements can be explictly opted with `#civictheme_form_element` + // property. + if (!empty($form_element[CivicthemeConstants::CIVICTHEME_FORM_ELEMENT_PROPERTY])) { + return TRUE; + } + // This is a list of form elements that CivicTheme works with currently + // This list may change with changes to Drupal and updates to CivicTheme. + $form_element_type = $form_element['#type']; + $supported_core_types = [ + 'button', + 'checkbox', + 'checkboxes', + 'color', + 'date', + 'email', + 'entity_autocomplete', + 'file', + 'image_button', + 'linkit', + 'machine_name', + 'number', + 'password', + 'path', + 'radio', + 'radios', + 'search', + 'select', + 'tableselect', + 'textarea', + 'textfield', + 'url', + 'weight', + ]; + + $supported_webform_types = [ + 'tel', + 'webform_date', + 'webform_email', + 'webform_number', + 'webform_select', + 'webform_textarea', + 'webform_time', + 'webform_url', + ]; + + $supported_types = array_merge($supported_core_types, $supported_webform_types); + + return in_array($form_element_type, $supported_types); } /** @@ -183,7 +251,7 @@ function civictheme_preprocess_fieldset__form_element__civictheme_field__checkbo // Prepare control array. $control = []; foreach ($element['#options'] as $value => $label) { - $name = isset($element['#attributes']['name']) ? $element['#attributes']['name'] : (isset($element['#name']) ? $element['#name'] : ''); + $name = $element['#attributes']['name'] ?? ($element['#name'] ?? ''); $control[] = [ 'label' => $label, 'value' => $value, @@ -221,7 +289,7 @@ function civictheme_preprocess_fieldset__form_element__civictheme_field__radios( // Prepare control array. $control = []; foreach ($element['#options'] as $value => $label) { - $name = isset($element['#attributes']['name']) ? $element['#attributes']['name'] : (isset($element['#name']) ? $element['#name'] : ''); + $name = $element['#attributes']['name'] ?? ($element['#name'] ?? ''); $control[] = [ 'label' => $label, 'value' => $value, @@ -279,9 +347,9 @@ function civictheme_preprocess_input__submit(array &$variables): void { */ function _civictheme_preprocess_form_element__generic(array &$variables): void { $element = $variables['element']; - $variables['type'] = isset($element['#type']) ? $element['#type'] : 'input'; + $variables['type'] = $element['#type'] ?? 'input'; - $variables['title'] = isset($element['#title']) ? $element['#title'] : ''; + $variables['title'] = $element['#title'] ?? ''; $title_display = $element['#_title_display'] ?? $element['#title_display'] ?? $variables['title_display'] ?? 'visible'; $title_display = $title_display == 'none' ? 'hidden' : $title_display; @@ -293,12 +361,14 @@ function _civictheme_preprocess_form_element__generic(array &$variables): void { $variables['orientation'] = $variables['orientation'] ?? $title_display == 'inline' ? 'horizontal' : 'vertical'; - $variables['is_required'] = isset($element['#required']) ? $element['#required'] : FALSE; + $variables['is_required'] = $element['#required'] ?? FALSE; $variables['is_disabled'] = isset($element['#attributes']['disabled']); - $variables['description'] = isset($element['#description']) ? $element['#description'] : ''; - $variables['message'] = isset($element['#errors']) ? $element['#errors'] : ''; + $variables['description'] = $element['#description'] ?? ''; + $variables['message'] = $element['#errors'] ?? ''; $variables['is_invalid'] = isset($element['#errors']) && !empty($element['#errors']); - $variables['placeholder'] = isset($element['#placeholder']) ? $element['#placeholder'] : ''; + $variables['placeholder'] = $element['#placeholder'] ?? ''; + $variables['prefix'] = $element['#field_prefix'] ?? $element['#prefix'] ?? ''; + $variables['suffix'] = $element['#field_suffix'] ?? $element['#suffix'] ?? ''; } /** @@ -306,6 +376,7 @@ function _civictheme_preprocess_form_element__generic(array &$variables): void { * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.StaticAccess) */ function _civictheme_preprocess_form_element__control(array &$variables): void { $element = $variables['element']; @@ -331,27 +402,34 @@ function _civictheme_preprocess_form_element__control(array &$variables): void { } } } - $attributes_string = implode(' ', array_map(function ($key, $value) { - return $key . '="' . $value . '"'; - }, array_keys($attributes), $attributes)); - $modifier_class = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : []; + $modifier_class = $element['#attributes']['class'] ?? []; + $modifier_class[] = 'form-element'; + $modifier_class[] = 'form-element--type-' . $variables['type']; + $modifier_class[] = 'form-element--api-' . $element['#type']; + $modifier_class[] = 'form-control'; + if (in_array($variables['type'], ['checkbox', 'radio'])) { + $modifier_class[] = 'form-boolean'; + $modifier_class[] = Html::getClass('form-boolean--type-' . $variables['type']); + } if (is_array($modifier_class)) { $modifier_class = implode(' ', $modifier_class); } + $attributes = new Attribute($attributes); + $variables['control'][] = [ - 'name' => isset($element['#attributes']['name']) ? $element['#attributes']['name'] : (isset($element['#name']) ? $element['#name'] : ''), - 'value' => isset($element['#value']) ? $element['#value'] : '', - 'id' => isset($element['#id']) ? $element['#id'] : '', + 'name' => $element['#attributes']['name'] ?? ($element['#name'] ?? ''), + 'value' => $element['#value'] ?? '', + 'id' => $element['#id'] ?? '', 'options' => $variables['options'] ?? [], - 'is_multiple' => isset($variables['is_multiple']) ? $variables['is_multiple'] : FALSE, - 'attributes' => $attributes_string, + 'is_multiple' => $variables['is_multiple'] ?? FALSE, + 'attributes' => $attributes, 'modifier_class' => $modifier_class, ]; if ($element['#type'] == 'checkbox') { - $variables['control'][0]['is_checked'] = isset($element['#default_value']) ? $element['#default_value'] : FALSE; + $variables['control'][0]['is_checked'] = $element['#default_value'] ?? FALSE; } } @@ -391,17 +469,24 @@ function _civictheme_preprocess_form_element__classes(array &$variables): void { // Note that we are only adding JS classes for 3rd-parties to bind to. Any // other classes added by core or contrib modules may affect the look and feel // of the form. + $variables['attributes']['class'][] = 'form-item'; + $variables['attributes']['class'][] = 'js-form-item'; if (!empty($variables['type'])) { $variables['attributes']['class'][] = 'js-form-type-' . Html::getClass($variables['type']); + $variables['attributes']['class'][] = 'form-type-' . Html::getClass($variables['type']); + $variables['attributes']['class'][] = 'form-type--' . Html::getClass($variables['type']); } if (!empty($variables['name'])) { $variables['attributes']['class'][] = 'js-form-item-' . Html::getClass($variables['name']); + $variables['attributes']['class'][] = 'form-item-' . Html::getClass($variables['name']); + $variables['attributes']['class'][] = 'form-item--' . Html::getClass($variables['name']); } elseif (!empty($variables['element']['#name'])) { $variables['attributes']['class'][] = 'js-form-item-' . Html::getClass($variables['element']['#name']); + $variables['attributes']['class'][] = 'form-item-' . Html::getClass($variables['element']['#name']); } if (!empty($variables['errors'])) { @@ -420,7 +505,7 @@ function _civictheme_preprocess_form_element__description(array &$variables): vo /** * Preprocesses form element wrapper classes. */ -function _civictheme_preprocess_form_element__wrapper_classes(array &$variables){ +function _civictheme_preprocess_form_element__wrapper_classes(array &$variables) { if (!empty($variables['element']['#wrapper_attributes'])) { $variables['attributes'] = $variables['element']['#wrapper_attributes']; } diff --git a/web/themes/contrib/civictheme/includes/utilities.inc b/web/themes/contrib/civictheme/includes/utilities.inc index 5e527f70e..41c8fc9d5 100644 --- a/web/themes/contrib/civictheme/includes/utilities.inc +++ b/web/themes/contrib/civictheme/includes/utilities.inc @@ -160,7 +160,7 @@ function civictheme_media_image_get_variables(MediaInterface $media, string $ima $variables = civictheme_media_get_variables($media); - if($variables['ext'] == 'svg') { + if ($variables['ext'] == 'svg') { $image_style = NULL; } diff --git a/web/themes/contrib/civictheme/src/CivicthemeConstants.php b/web/themes/contrib/civictheme/src/CivicthemeConstants.php index 72488337e..0343bd1df 100644 --- a/web/themes/contrib/civictheme/src/CivicthemeConstants.php +++ b/web/themes/contrib/civictheme/src/CivicthemeConstants.php @@ -191,4 +191,9 @@ final class CivicthemeConstants { */ const OPTOUT_SUMMARY_HIDE_ELLIPSIS = 'CivicThemeOptoutSummaryHideEllipsis'; + /** + * Form property to add to opt-in a form element for rendering by CivicTheme. + */ + const CIVICTHEME_FORM_ELEMENT_PROPERTY = '#civictheme_form_element'; + } diff --git a/web/themes/contrib/civictheme/templates/form/fieldset--form-element--civictheme-field.html.twig b/web/themes/contrib/civictheme/templates/form/fieldset--form-element--civictheme-field.html.twig index 3b7727b20..64a5e97de 100644 --- a/web/themes/contrib/civictheme/templates/form/fieldset--form-element--civictheme-field.html.twig +++ b/web/themes/contrib/civictheme/templates/form/fieldset--form-element--civictheme-field.html.twig @@ -20,4 +20,6 @@ control: control, attributes: attributes, modifier_class: modifier_class, + prefix: prefix, + suffix: suffix, } only %} diff --git a/web/themes/contrib/civictheme/templates/form/form-element--civictheme-field.html.twig b/web/themes/contrib/civictheme/templates/form/form-element--civictheme-field.html.twig index 6fb61a0a1..62016bf47 100644 --- a/web/themes/contrib/civictheme/templates/form/form-element--civictheme-field.html.twig +++ b/web/themes/contrib/civictheme/templates/form/form-element--civictheme-field.html.twig @@ -20,4 +20,6 @@ control: control, attributes: attributes, modifier_class: modifier_class, + prefix: prefix, + suffix: suffix, } only %}