diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 81c328c74c8..678a1e104a3 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1428,32 +1428,32 @@ function drupal_pre_render_table(array $element) {
* - header: An array containing the table headers. Each element of the array
* can be either a localized string or an associative array with the
* following keys:
- * - "data": The localized title of the table column.
- * - "field": The database field represented in the table column (required
+ * - data: The localized title of the table column.
+ * - field: The database field represented in the table column (required
* if user is to be able to sort on this column).
- * - "sort": A default sort order for this column ("asc" or "desc"). Only
- * one column should be given a default sort order because table sorting
- * only applies to one column at a time.
- * - "class": An array of values for the 'class' attribute. In particular,
- * the least important columns that can be hidden on narrow and medium
- * width screens should have a 'priority-low' class, referenced with the
- * RESPONSIVE_PRIORITY_LOW constant. Columns that should be shown on
- * medium+ wide screens should be marked up with a class of
- * 'priority-medium', referenced by with the RESPONSIVE_PRIORITY_MEDIUM
- * constant. Themes may hide columns with one of these two classes on
- * narrow viewports to save horizontal space.
+ * - sort: A default sort order for this column ("asc" or "desc"). Only
+ * one column should be given a default sort order because table sorting
+ * only applies to one column at a time.
+ * - class: An array of values for the 'class' attribute. In particular,
+ * the least important columns that can be hidden on narrow and medium
+ * width screens should have a 'priority-low' class, referenced with the
+ * RESPONSIVE_PRIORITY_LOW constant. Columns that should be shown on
+ * medium+ wide screens should be marked up with a class of
+ * 'priority-medium', referenced by with the RESPONSIVE_PRIORITY_MEDIUM
+ * constant. Themes may hide columns with one of these two classes on
+ * narrow viewports to save horizontal space.
* - Any HTML attributes, such as "colspan", to apply to the column header
* cell.
* - rows: An array of table rows. Every row is an array of cells, or an
* associative array with the following keys:
- * - "data": an array of cells
+ * - data: An array of cells.
* - Any HTML attributes, such as "class", to apply to the table row.
- * - "no_striping": a boolean indicating that the row should receive no
+ * - no_striping: A Boolean indicating that the row should receive no
* 'even / odd' styling. Defaults to FALSE.
* Each cell can be either a string or an associative array with the
* following keys:
- * - "data": The string to display in the table cell.
- * - "header": Indicates this cell is a header.
+ * - data: The string to display in the table cell.
+ * - header: Indicates this cell is a header.
* - Any HTML attributes, such as "colspan", to apply to the table cell.
* Here's an example for $rows:
* @code
@@ -1465,9 +1465,11 @@ function drupal_pre_render_table(array $element) {
* // Row with attributes on the row and some of its cells.
* array(
* 'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => array('funky')
- * )
+ * ),
* );
* @endcode
+ * - footer: An array of table rows which will be printed within a
+ * tag, in the same format as the rows element (see above).
* - attributes: An array of HTML attributes to apply to the table tag.
* - caption: A localized string to use for the tag.
* - colgroups: An array of column groups. Each element of the array can be
@@ -1612,77 +1614,81 @@ function template_preprocess_table(&$variables) {
}
}
- if (!empty($variables['rows'])) {
- $flip = array('even' => 'odd', 'odd' => 'even');
- $class = 'even';
- foreach ($variables['rows'] as $row_key => $row) {
- // Check if we're dealing with a simple or complex row
- if (isset($row['data'])) {
- $cells = $row['data'];
- $no_striping = isset($row['no_striping']) ? $row['no_striping'] : FALSE;
-
- // Set the attributes array and exclude 'data' and 'no_striping'.
- $row_attributes = $row;
- unset($row_attributes['data']);
- unset($row_attributes['no_striping']);
- }
- else {
+ // Rows and footer have the same structure.
+ $sections = array('rows' , 'footer');
+ foreach ($sections as $section) {
+ if (!empty($variables[$section])) {
+ $flip = array('even' => 'odd', 'odd' => 'even');
+ $class = 'even';
+ foreach ($variables[$section] as $row_key => $row) {
$cells = $row;
$row_attributes = array();
- $no_striping = FALSE;
- }
+ $no_striping = $section === 'footer';
- // Add odd/even class.
- if (!$no_striping) {
- $class = $flip[$class];
- $row_attributes['class'][] = $class;
- }
+ // Check if we're dealing with a simple or complex row
+ if (isset($row['data'])) {
+ $cells = $row['data'];
+ $no_striping = isset($row['no_striping']) ? $row['no_striping'] : $no_striping;
- // Build row.
- $variables['rows'][$row_key] = array();
- $variables['rows'][$row_key]['attributes'] = new Attribute($row_attributes);
- $variables['rows'][$row_key]['cells'] = array();
- if (!empty($cells)) {
- // Reset the responsive index.
- $responsive_index = -1;
- foreach ($cells as $col_key => $cell) {
- // Increase the responsive index.
- $responsive_index++;
+ // Set the attributes array and exclude 'data' and 'no_striping'.
+ $row_attributes = $row;
+ unset($row_attributes['data']);
+ unset($row_attributes['no_striping']);
+ }
- if (!is_array($cell)) {
- $cell_content = $cell;
- $cell_attributes = array();
- $is_header = FALSE;
- }
- else {
- $cell_content = '';
- if (isset($cell['data'])) {
- $cell_content = $cell['data'];
- unset($cell['data']);
+ // Add odd/even class.
+ if (!$no_striping) {
+ $class = $flip[$class];
+ $row_attributes['class'][] = $class;
+ }
+
+
+ // Build row.
+ $variables[$section][$row_key] = array();
+ $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes);
+ $variables[$section][$row_key]['cells'] = array();
+ if (!empty($cells)) {
+ // Reset the responsive index.
+ $responsive_index = -1;
+ foreach ($cells as $col_key => $cell) {
+ // Increase the responsive index.
+ $responsive_index++;
+
+ if (!is_array($cell)) {
+ $cell_content = $cell;
+ $cell_attributes = array();
+ $is_header = FALSE;
}
- // Flag the cell as a header or not and remove the flag.
- $is_header = !empty($cell['header']);
- unset($cell['header']);
+ else {
+ $cell_content = '';
+ if (isset($cell['data'])) {
+ $cell_content = $cell['data'];
+ unset($cell['data']);
+ }
- $cell_attributes = $cell;
+ // Flag the cell as a header or not and remove the flag.
+ $is_header = !empty($cell['header']);
+ unset($cell['header']);
- if (is_array($cell_content)) {
- $cell_content = drupal_render($cell_content);
+ $cell_attributes = $cell;
+
+ if (is_array($cell_content)) {
+ $cell_content = drupal_render($cell_content);
+ }
}
+ // Add active class if needed for sortable tables.
+ if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) {
+ $cell_attributes['class'][] = 'active';
+ }
+ // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM
+ // class from header to cell as needed.
+ if (isset($responsive_classes[$responsive_index])) {
+ $cell_attributes['class'][] = $responsive_classes[$responsive_index];
+ }
+ $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td';
+ $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes);
+ $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content;
}
- // Add active class if needed for sortable tables.
- if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) {
- $cell_attributes['class'][] = 'active';
- }
- // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM
- // class from header to cell as needed.
- if (isset($responsive_classes[$responsive_index])) {
- $cell_attributes['class'][] = $responsive_classes[$responsive_index];
- }
-
- $variables['rows'][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td';
- $variables['rows'][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes);
- $variables['rows'][$row_key]['cells'][$col_key]['content'] = $cell_content;
}
}
}
@@ -2561,7 +2567,7 @@ function drupal_common_theme() {
'template' => 'breadcrumb',
),
'table' => array(
- 'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => FALSE, 'responsive' => TRUE, 'empty' => ''),
+ 'variables' => array('header' => NULL, 'rows' => NULL, 'footer' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => FALSE, 'responsive' => TRUE, 'empty' => ''),
'template' => 'table',
),
'tablesort_indicator' => array(
diff --git a/core/modules/system/src/Tests/Theme/TableTest.php b/core/modules/system/src/Tests/Theme/TableTest.php
index 435e79fe83f..b781d20d6f2 100644
--- a/core/modules/system/src/Tests/Theme/TableTest.php
+++ b/core/modules/system/src/Tests/Theme/TableTest.php
@@ -110,6 +110,28 @@ class TableTest extends DrupalUnitTestBase {
$this->assertNoRaw('no_striping', 'No invalid no_striping HTML attribute was printed.');
}
+ /**
+ * Test that the 'footer' option works correctly.
+ */
+ function testThemeTableFooter() {
+ $footer = array(
+ array(
+ 'data' => array(1),
+ ),
+ array('Foo'),
+ );
+
+ $table = array(
+ '#type' => 'table',
+ '#rows' => array(),
+ '#footer' => $footer,
+ );
+
+ $this->render($table);
+ $this->removeWhiteSpace();
+ $this->assertRaw('1 |
Foo |
', 'Table footer found.');
+ }
+
/**
* Tests that the 'header' option in cells works correctly.
*/
diff --git a/core/modules/system/templates/table.html.twig b/core/modules/system/templates/table.html.twig
index ce69286108b..9c1cb05328b 100644
--- a/core/modules/system/templates/table.html.twig
+++ b/core/modules/system/templates/table.html.twig
@@ -28,6 +28,7 @@
* - attributes: Any HTML attributes, such as "colspan", to apply to the
* table cell.
* - content: The string to display in the table cell.
+ * - footer: Table footer rows, in the same format as the rows variable.
* - empty: The message to display in an extra row if table does not have
* any rows.
*
@@ -78,4 +79,17 @@
{% endfor %}
{% endif %}
+ {% if footer %}
+
+ {% for row in footer %}
+
+ {% for cell in row.cells %}
+ <{{ cell.tag }}{{ cell.attributes }}>
+ {{- cell.content -}}
+ {{ cell.tag }}>
+ {% endfor %}
+
+ {% endfor %}
+
+ {% endif %}