diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 9b89250..fe548e9 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -573,6 +573,14 @@ function template_preprocess_datetime_wrapper(&$variables) { $variables['title'] = $element['#title']; } + // Pass elements #type and #name to template. + if (!empty($element['#type'])) { + $variables['type'] = $element['#type']; + } + if (!empty($element['#name'])) { + $variables['name'] = $element['#name']; + } + // Suppress error messages. $variables['errors'] = NULL; @@ -580,6 +588,13 @@ function template_preprocess_datetime_wrapper(&$variables) { $variables['description'] = $element['#description']; } + // For disabled datetime fields, the 'disabled' attribute should not be + // applied to the wrapper, but the 'form-disabled' class should be. + $variables['disabled'] = !empty($element['#attributes']['disabled']) ? $element['#attributes']['disabled'] : NULL; + if (isset($variables['attributes']['disabled'])) { + unset($variables['attributes']['disabled']); + } + $variables['required'] = FALSE; // For required datetime fields 'form-required' & 'js-form-required' classes // are appended to the label attributes. diff --git a/core/lib/Drupal/Core/Datetime/Element/Datetime.php b/core/lib/Drupal/Core/Datetime/Element/Datetime.php index 7637ad1..a544fcb 100644 --- a/core/lib/Drupal/Core/Datetime/Element/Datetime.php +++ b/core/lib/Drupal/Core/Datetime/Element/Datetime.php @@ -15,6 +15,8 @@ class Datetime extends DateElementBase { /** + * An example date. + * * @var \DateTimeInterface */ protected static $dateExample; @@ -28,11 +30,11 @@ public function getInfo() { // Date formats cannot be loaded during install or update. if (!defined('MAINTENANCE_MODE')) { if ($date_format_entity = DateFormat::load('html_date')) { - /** @var $date_format_entity \Drupal\Core\Datetime\DateFormatInterface */ + /* @var $date_format_entity \Drupal\Core\Datetime\DateFormatInterface */ $date_format = $date_format_entity->getPattern(); } if ($time_format_entity = DateFormat::load('html_time')) { - /** @var $time_format_entity \Drupal\Core\Datetime\DateFormatInterface */ + /* @var $time_format_entity \Drupal\Core\Datetime\DateFormatInterface */ $time_format = $time_format_entity->getPattern(); } } @@ -95,7 +97,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form ); } else { - $date = $element['#default_value']; + $date = isset($element['#default_value']) ? $element['#default_value'] : NULL; if ($date instanceof DrupalDateTime && !$date->hasErrors()) { $input = array( 'date' => $date->format($element['#date_date_format']), @@ -137,17 +139,17 @@ public static function valueCallback(&$element, $input, FormStateInterface $form * * Required settings: * - #default_value: A DrupalDateTime object, adjusted to the proper local - * timezone. Converting a date stored in the database from UTC to the local - * zone and converting it back to UTC before storing it is not handled here. - * This element accepts a date as the default value, and then converts the - * user input strings back into a new date object on submission. No timezone - * adjustment is performed. + * timezone. Converting a date stored in the database from UTC to the + * local zone and converting it back to UTC before storing it is not + * handled here. This element accepts a date as the default value, and + * then converts the user input strings back into a new date object on + * submission. No timezone adjustment is performed. * Optional properties include: * - #date_date_format: A date format string that describes the format that * should be displayed to the end user for the date. When using HTML5 * elements the format MUST use the appropriate HTML5 format for that - * element, no other format will work. See the format_date() function for a - * list of the possible formats and HTML5 standards for the HTML5 + * element, no other format will work. See the format_date() function for + * a list of the possible formats and HTML5 standards for the HTML5 * requirements. Defaults to the right HTML5 format for the chosen element * if a HTML5 element is used, otherwise defaults to * entity_load('date_format', 'html_date')->getPattern(). @@ -172,23 +174,24 @@ public static function valueCallback(&$element, $input, FormStateInterface $form * if a HTML5 element is used, otherwise defaults to * entity_load('date_format', 'html_time')->getPattern(). * - #date_time_callbacks: An array of optional callbacks for the time - * element. Can be used to add a jQuery timepicker or an 'All day' checkbox. + * element. Can be used to add a jQuery timepicker or an 'All day' + * checkbox. * - #date_year_range: A description of the range of years to allow, like * '1900:2050', '-3:+3' or '2000:+3', where the first value describes the * earliest year and the second the latest year in the range. A year * in either position means that specific year. A +/- value describes a * dynamic value that is that many years earlier or later than the current - * year at the time the form is displayed. Used in jQueryUI datepicker year - * range and HTML5 min/max date settings. Defaults to '1900:2050'. + * year at the time the form is displayed. Used in jQueryUI datepicker + * year range and HTML5 min/max date settings. Defaults to '1900:2050'. * - #date_increment: The increment to use for minutes and seconds, i.e. - * '15' would show only :00, :15, :30 and :45. Used for HTML5 step values and - * jQueryUI datepicker settings. Defaults to 1 to show every minute. - * - #date_timezone: The local timezone to use when creating dates. Generally - * this should be left empty and it will be set correctly for the user using - * the form. Useful if the default value is empty to designate a desired - * timezone for dates created in form processing. If a default date is - * provided, this value will be ignored, the timezone in the default date - * takes precedence. Defaults to the value returned by + * '15' would show only :00, :15, :30 and :45. Used for HTML5 step values + * and jQueryUI datepicker settings. Defaults to 1 to show every minute. + * - #date_timezone: The local timezone to use when creating dates. + * Generally this should be left empty and it will be set correctly for + * the user using the form. Useful if the default value is empty to + * designate a desired timezone for dates created in form processing. If a + * default date is provided, this value will be ignored, the timezone in + * the default date takes precedence. Defaults to the value returned by * drupal_get_user_timezone(). * * Example usage: @@ -227,6 +230,19 @@ public static function processDatetime(&$element, FormStateInterface $form_state $element['#tree'] = TRUE; + // Visibility states need to be applied to the element as a whole, but the + // other states apply to the individual parts. + if (isset($element['#states']) && is_array($element['#states'])) { + $wrapper_states = array_filter($element['#states'], function ($key) { + return in_array($key, ['visible', 'invisible']); + }, ARRAY_FILTER_USE_KEY); + $children_states = array_diff_key($element['#states'], $wrapper_states); + } + else { + $wrapper_states = $children_states = []; + } + $element['#states'] = $wrapper_states; + if ($element['#date_date_element'] != 'none') { $date_format = $element['#date_date_element'] != 'none' ? static::getHtml5DateFormat($element) : ''; @@ -264,6 +280,7 @@ public static function processDatetime(&$element, FormStateInterface $form_state '#size' => max(12, strlen($element['#value']['date'])), '#error_no_message' => TRUE, '#date_date_format' => $element['#date_date_format'], + '#states' => $children_states, ); // Allows custom callbacks to alter the element. @@ -296,6 +313,7 @@ public static function processDatetime(&$element, FormStateInterface $form_state '#required' => $element['#required'], '#size' => 12, '#error_no_message' => TRUE, + '#states' => $children_states, ); // Allows custom callbacks to alter the element. @@ -366,8 +384,10 @@ public static function validateDatetime(&$element, FormStateInterface $form_stat * This is centralized for a consistent method of creating these examples. * * @param string $format + * A format acceptable by PHP's date() function. * * @return string + * A formatted example date. */ public static function formatExample($format) { if (!static::$dateExample) { diff --git a/core/misc/states.js b/core/misc/states.js index 24374b6..2e18316 100644 --- a/core/misc/states.js +++ b/core/misc/states.js @@ -610,6 +610,9 @@ .prop('disabled', e.value) .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value) .find('select, input, textarea').prop('disabled', e.value); + // A complex form element, like 'datetime' (one made up of multiple + // sub-elements) may have a label for the element as a whole. + $(e.target).closest('.js-complex-form-item').toggleClass('form-disabled', e.value); // Note: WebKit nightlies don't reflect that change correctly. // See https://bugs.webkit.org/show_bug.cgi?id=23789 @@ -621,13 +624,20 @@ if (e.value) { var label = 'label' + (e.target.id ? '[for=' + e.target.id + ']' : ''); var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find(label); + // A complex form element, like 'datetime' (one made up of multiple + // sub-elements) may have a label for the element as a whole. + var $complexLabel = $(e.target).closest('.js-complex-form-item').children('label'); // Avoids duplicate required markers on initialization. if (!$label.hasClass('js-form-required').length) { $label.addClass('js-form-required form-required'); } + if (!$complexLabel.hasClass('js-form-required').length) { + $complexLabel.addClass('js-form-required form-required'); + } } else { $(e.target).removeAttr('required aria-required').closest('.js-form-item, .js-form-wrapper').find('label.js-form-required').removeClass('js-form-required form-required'); + $(e.target).closest('.js-complex-form-item').children('label').removeClass('js-form-required form-required'); } } }); diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php index df23426..0b28ccf 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php +++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php @@ -22,9 +22,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen // identifying this item in error messages. We do not want to display this // title because the actual title display is handled at a higher level by // the Field module. - $element['#theme_wrappers'][] = 'datetime_wrapper'; - $element['#attributes']['class'][] = 'container-inline'; $element['value'] = array( '#type' => 'datetime', diff --git a/core/modules/datetime/src/Tests/DateTimeFieldTest.php b/core/modules/datetime/src/Tests/DateTimeFieldTest.php index 3ed9df9..ba32dc0 100644 --- a/core/modules/datetime/src/Tests/DateTimeFieldTest.php +++ b/core/modules/datetime/src/Tests/DateTimeFieldTest.php @@ -118,7 +118,7 @@ function testDateField() { // Display creation form. $this->drupalGet('entity_test/add'); $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date element found.'); - $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]/h4[contains(@class, "js-form-required")]', TRUE, 'Required markup found'); + $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]/div/label[contains(@class, "js-form-required")]', TRUE, 'Required markup found'); $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Time element not found.'); // Build up a date in the UTC timezone. diff --git a/core/modules/datetime/tests/modules/datetime_states/datetime_states.info.yml b/core/modules/datetime/tests/modules/datetime_states/datetime_states.info.yml new file mode 100644 index 0000000..75e139f --- /dev/null +++ b/core/modules/datetime/tests/modules/datetime_states/datetime_states.info.yml @@ -0,0 +1,8 @@ +name: 'Datetime #states test' +type: module +description: 'Provides an example demonstrating how form #states affect datetime elements.' +package: Testing +version: VERSION +core: 8.x +dependencies: + - datetime diff --git a/core/modules/datetime/tests/modules/datetime_states/datetime_states.routing.yml b/core/modules/datetime/tests/modules/datetime_states/datetime_states.routing.yml new file mode 100644 index 0000000..f5e44fe --- /dev/null +++ b/core/modules/datetime/tests/modules/datetime_states/datetime_states.routing.yml @@ -0,0 +1,6 @@ +datetime_states.example: + path: '/datetime/states-example' + defaults: + _form: '\Drupal\datetime_states\Form\StateForm' + requirements: + _access: 'TRUE' diff --git a/core/modules/datetime/tests/modules/datetime_states/src/Form/StateForm.php b/core/modules/datetime/tests/modules/datetime_states/src/Form/StateForm.php new file mode 100644 index 0000000..9b00a6d --- /dev/null +++ b/core/modules/datetime/tests/modules/datetime_states/src/Form/StateForm.php @@ -0,0 +1,70 @@ + 'checkbox', + '#title' => $this->t('Invisible'), + ); + $form['toggle_disabled'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Disabled'), + ); + $form['toggle_required'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Required'), + ); + + + $form['date'] = array( + '#type' => 'datetime', + '#title' => $this->t('Datetime'), + '#description' => $this->t('A datetime form element.'), + '#states' => [ + 'invisible' => [ + ':input[name="toggle_invisible"]' => ['checked' => TRUE], + ], + 'disabled' => [ + ':input[name="toggle_disabled"]' => ['checked' => TRUE], + ], + 'required' => [ + ':input[name="toggle_required"]' => ['checked' => TRUE], + ], + ], + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Save'), + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + } + +} diff --git a/core/modules/system/templates/datetime-wrapper.html.twig b/core/modules/system/templates/datetime-wrapper.html.twig index 8430baa..3b25687 100644 --- a/core/modules/system/templates/datetime-wrapper.html.twig +++ b/core/modules/system/templates/datetime-wrapper.html.twig @@ -16,18 +16,25 @@ */ #} {% + set container_classes = [ + 'js-complex-form-item', + ] +%} +{% set title_classes = [ required ? 'js-form-required', required ? 'form-required', ] %} -{% if title %} - {{ title }} -{% endif %} -{{ content }} -{% if errors %} -
- {{ errors }} -
-{% endif %} -{{ description }} + + {% if title %} + {{ title }} + {% endif %} + {{ content }} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {{ description }} + \ No newline at end of file diff --git a/core/themes/classy/templates/form/datetime-wrapper.html.twig b/core/themes/classy/templates/form/datetime-wrapper.html.twig index 3f6aa59..069f918 100644 --- a/core/themes/classy/templates/form/datetime-wrapper.html.twig +++ b/core/themes/classy/templates/form/datetime-wrapper.html.twig @@ -14,21 +14,34 @@ */ #} {% + set container_classes = [ + 'js-form-item', + 'form-item', + 'js-form-type-' ~ type|clean_class, + 'form-type-' ~ type|clean_class, + 'js-form-item-' ~ name|clean_class, + 'form-item-' ~ name|clean_class, + 'js-complex-form-item', + disabled == 'disabled' ? 'form-disabled', + ] +%} +{% set title_classes = [ - 'label', required ? 'js-form-required', required ? 'form-required', ] %} -{% if title %} - {{ title }} -{% endif %} -{{ content }} -{% if errors %} -
- {{ errors }} -
-{% endif %} -{% if description %} -
{{ description }}
-{% endif %} + + {% if title %} + {{ title }} + {% endif %} + {{ content }} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {% if description %} +
{{ description }}
+ {% endif %} + diff --git a/core/themes/seven/css/components/form.css b/core/themes/seven/css/components/form.css index f42d59b..162a579 100644 --- a/core/themes/seven/css/components/form.css +++ b/core/themes/seven/css/components/form.css @@ -63,6 +63,8 @@ label[for] { .form-disabled input.form-number, .form-disabled input.form-color, .form-disabled input.form-file, +.form-disabled input.form-date, +.form-disabled input.form-time, .form-disabled textarea.form-textarea, .form-disabled select.form-select { border-color: #d4d4d4; diff --git a/core/themes/stable/templates/form/datetime-wrapper.html.twig b/core/themes/stable/templates/form/datetime-wrapper.html.twig index 3c37ffd..afdd5e2 100644 --- a/core/themes/stable/templates/form/datetime-wrapper.html.twig +++ b/core/themes/stable/templates/form/datetime-wrapper.html.twig @@ -14,18 +14,25 @@ */ #} {% + set container_classes = [ + 'js-complex-form-item', + ] +%} +{% set title_classes = [ required ? 'js-form-required', required ? 'form-required', ] %} -{% if title %} - {{ title }} -{% endif %} -{{ content }} -{% if errors %} -
- {{ errors }} -
-{% endif %} -{{ description }} + + {% if title %} + {{ title }} + {% endif %} + {{ content }} + {% if errors %} +
+ {{ errors }} +
+ {% endif %} + {{ description }} + \ No newline at end of file