diff --git a/core/includes/form.inc b/core/includes/form.inc index 87f8edf..44ed906 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -979,44 +979,34 @@ function theme_fieldset($variables) { } /** - * Returns HTML for a details form element and its children. + * Prepares variables for details element templates. * - * @param $variables + * Default template: details.html.twig. + * + * @param array $variables * An associative array containing: * - element: An associative array containing the properties of the element. - * Properties used: #attributes, #children, #collapsed, #description, #id, - * #title, #value. + * Properties used: #attributes, #children, #collapsed, #collapsible, + * #description, #id, #title, #value. * * @ingroup themeable */ -function theme_details($variables) { +function template_preprocess_details(&$variables) { $element = $variables['element']; - element_set_attributes($element, array('id')); - _form_set_attributes($element, array('form-wrapper')); - - $output = ''; + $variables['attributes'] = $element['#attributes']; + $variables['summary_attributes'] = new Attribute(); if (!empty($element['#title'])) { - $summary_attributes = new Attribute(array( - 'role' => 'button', - )); + $variables['summary_attributes']['role'] = 'button'; if (!empty($element['#attributes']['id'])) { - $summary_attributes['aria-controls'] = $element['#attributes']['id']; + $variables['summary_attributes']['aria-controls'] = $element['#attributes']['id']; } - $summary_attributes['aria-expanded'] = empty($element['#attributes']['open']) ? FALSE : TRUE; - $summary_attributes['aria-pressed'] = $summary_attributes['aria-expanded']; - $output .= '' . $element['#title'] . ''; - } - $output .= '
'; - if (!empty($element['#description'])) { - $output .= '
' . $element['#description'] . '
'; + $variables['summary_attributes']['aria-expanded'] = empty($element['#attributes']['open']) ? FALSE : TRUE; + $variables['summary_attributes']['aria-pressed'] = $variables['summary_attributes']['aria-expanded']; } - $output .= $element['#children']; - if (isset($element['#value'])) { - $output .= $element['#value']; - } - $output .= '
'; - $output .= "\n"; - return $output; + $variables['title'] = (!empty($element['#title'])) ? $element['#title'] : ''; + $variables['description'] = (!empty($element['#description'])) ? $element['#description'] : ''; + $variables['children'] = (isset($element['#children'])) ? $element['#children'] : ''; + $variables['value'] = (isset($element['#value'])) ? $element['#value'] : ''; } /** @@ -1970,11 +1960,11 @@ function form_process_group(&$element, &$form_state) { * The modified element. */ function form_pre_render_details($element) { + element_set_attributes($element, array('id')); + // The .form-wrapper class is required for #states to treat details like // containers. - if (!isset($element['#attributes']['class'])) { - $element['#attributes']['class'] = array(); - } + _form_set_attributes($element, array('form-wrapper')); // Collapsible details. $element['#attached']['library'][] = array('system', 'drupal.collapse'); diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 5402dae..aec4169 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2664,6 +2664,7 @@ function drupal_common_theme() { ), 'details' => array( 'render element' => 'element', + 'template' => 'details', ), 'radios' => array( 'render element' => 'element', diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php index 750250d..1403c95 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php @@ -598,9 +598,7 @@ function testDrupalRenderChildrenPostRenderCache() { $element = array('#cache' => $element['#cache']); $cached_element = cache()->get(drupal_render_cid_create($element))->data; $expected_element = array( - '#markup' => '
Parent
Child
Subchild
-
-', + '#markup' => '', '#attached' => array( 'js' => array( array('type' => 'setting', 'data' => array('foo' => 'bar')) @@ -618,7 +616,19 @@ function testDrupalRenderChildrenPostRenderCache() { ) ), ); - $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); + + $dom = filter_dom_load($cached_element['#markup']); + $xpath = new \DOMXPath($dom); + $parent = $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Parent"]')->length; + $child = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length; + $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length; + + $this->assertTrue($parent && $child && $subchild, 'The correct data is cached: the stored #markup is not affected by #post_render_cache callbacks.'); + + unset($cached_element['#markup']); + unset($expected_element['#markup']); + + $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached: the stored #attached properties are not affected by #post_render_cache callbacks.'); // GET request: #cache enabled, cache hit. drupal_static_reset('_drupal_add_js'); @@ -674,9 +684,7 @@ function testDrupalRenderChildrenPostRenderCache() { $cached_parent_element = cache()->get(drupal_render_cid_create($element))->data; $cached_child_element = cache()->get(drupal_render_cid_create($element['child']))->data; $expected_parent_element = array( - '#markup' => '
Parent
Child
Subchild
-
-', + '#markup' => '', '#attached' => array( 'js' => array( array('type' => 'setting', 'data' => array('foo' => 'bar')) @@ -694,10 +702,20 @@ function testDrupalRenderChildrenPostRenderCache() { ) ), ); - $this->assertIdentical($cached_parent_element, $expected_parent_element, 'The correct data is cached for the parent: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); + + $dom = filter_dom_load($cached_parent_element['#markup']); + $xpath = new \DOMXPath($dom); + $parent = $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Parent"]')->length; + $child = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length; + $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length; + $this->assertTrue($parent && $child && $subchild, 'The correct data is cached for the parent: the stored #markup is not affected by #post_render_cache callbacks.'); + + unset($cached_parent_element['#markup']); + unset($expected_parent_element['#markup']); + $this->assertIdentical($cached_parent_element, $expected_parent_element, 'The correct data is cached for the parent: the stored #attached properties are not affected by #post_render_cache callbacks.'); + $expected_child_element = array( - '#markup' => '
Child
Subchild
-', + '#markup' => '', '#attached' => array( 'library' => array( array('system', 'drupal.collapse'), @@ -710,7 +728,16 @@ function testDrupalRenderChildrenPostRenderCache() { ) ), ); - $this->assertIdentical($cached_child_element, $expected_child_element, 'The correct data is cached for the child: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); + + $dom = filter_dom_load($cached_child_element['#markup']); + $xpath = new \DOMXPath($dom); + $child = $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length; + $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length; + $this->assertTrue($child && $subchild, 'The correct data is cached for the child: the stored #markup is not affected by #post_render_cache callbacks.'); + + unset($cached_child_element['#markup']); + unset($expected_child_element['#markup']); + $this->assertIdentical($cached_child_element, $expected_child_element, 'The correct data is cached for the child: the stored #attached properties are not affected by #post_render_cache callbacks.'); // GET request: #cache enabled, cache hit, parent element. drupal_static_reset('_drupal_add_js'); diff --git a/core/modules/system/templates/details.html.twig b/core/modules/system/templates/details.html.twig new file mode 100644 index 0000000..17ea820 --- /dev/null +++ b/core/modules/system/templates/details.html.twig @@ -0,0 +1,33 @@ +{# +/** + * @file + * Default theme implementation for a details element. + * + * Available variables + * - attributes: A list of HTML attributes for the details element. + * - title: (optional) The title of the element, may not be set. + * - description: (optional) The description of the element, may not be set. + * - children: (optional) The children of the element, may not be set. + * - value: (optional) The value of the element, may not be set. + * + * @see template_preprocess_details() + * + * @ingroup themeable + */ +#} + + {%- if title -%} + {{ title }} + {%- endif -%} +
+ {%- if description -%} +
{{ description }}
+ {%- endif -%} + {%- if children -%} + {{ children }} + {%- endif -%} + {%- if value -%} + {{ value }} + {%- endif -%} +
+