diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc
index 1f6962b..3ad1dc1 100644
--- a/core/includes/tablesort.inc
+++ b/core/includes/tablesort.inc
@@ -65,35 +65,6 @@ function tablesort_header($cell, $header, $ts) {
}
/**
- * Formats a table cell.
- *
- * Adds a class attribute to all cells in the currently active column.
- *
- * @param $cell
- * The cell to format.
- * @param $header
- * An array of column headers in the format described in theme_table().
- * @param $ts
- * The current table sort context as returned from tablesort_init().
- * @param $i
- * The index of the cell's table column.
- *
- * @return
- * A properly formatted cell, ready for _theme_table_cell().
- */
-function tablesort_cell($cell, $header, $ts, $i) {
- if (isset($header[$i]['data']) && $header[$i]['data'] == $ts['name'] && !empty($header[$i]['field'])) {
- if (is_array($cell)) {
- $cell['class'][] = 'active';
- }
- else {
- $cell = array('data' => $cell, 'class' => array('active'));
- }
- }
- return $cell;
-}
-
-/**
* Composes a URL query parameter array for table sorting links.
*
* @return
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 46113c6..3f83da1 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1462,9 +1462,11 @@ function drupal_pre_render_table(array $element) {
}
/**
- * Returns HTML for a table.
+ * Prepares variables for table templates.
*
- * @param $variables
+ * Default template: table.html.twig.
+ *
+ * @param array $variables
* An associative array containing:
* - header: An array containing the table headers. Each element of the array
* can be either a localized string or an associative array with the
@@ -1546,52 +1548,28 @@ function drupal_pre_render_table(array $element) {
* - empty: The message to display in an extra row if table does not have any
* rows.
*/
-function theme_table($variables) {
- $header = $variables['header'];
- $rows = $variables['rows'];
- $attributes = $variables['attributes'];
- $caption = $variables['caption'];
- $colgroups = $variables['colgroups'];
- $sticky = $variables['sticky'];
- $responsive = $variables['responsive'];
- $empty = $variables['empty'];
-
- // Add sticky headers, if applicable.
- if (count($header) && $sticky) {
- drupal_add_library('system', 'drupal.tableheader');
- // Add 'sticky-enabled' class to the table to identify it for JS.
- // This is needed to target tables constructed by this function.
- $attributes['class'][] = 'sticky-enabled';
- }
- // If the table has headers and it should react responsively to columns hidden
- // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
- // and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors.
- if (count($header) && $responsive) {
- drupal_add_library('system', 'drupal.tableresponsive');
- // Add 'responsive-enabled' class to the table to identify it for JS.
- // This is needed to target tables constructed by this function.
- $attributes['class'][] = 'responsive-enabled';
- }
-
- $output = '
\n";
-
- if (isset($caption)) {
- $output .= '' . $caption . "\n";
- }
-
- // Format the table columns:
- if (count($colgroups)) {
- foreach ($colgroups as $colgroup) {
- $attributes = array();
+function template_preprocess_table(&$variables) {
+ // Flags.
+ $is_sticky = !empty($variables['sticky']);
+ $is_responsive = !empty($variables['responsive']);
- // Check if we're dealing with a simple or complex column
+ // Table header.
+ if (!empty($variables['header']) && is_array($variables['header'])) {
+ $variables['header'] = array_values($variables['header']);
+ }
+
+ if (!empty($variables['colgroups'])) {
+ foreach ($variables['colgroups'] as $colgroup_key => $colgroup) {
+ $colgroup_attributes = new Attribute();
+
+ // Support simple and complex colgroups.
if (isset($colgroup['data'])) {
foreach ($colgroup as $key => $value) {
if ($key == 'data') {
$cols = $value;
}
else {
- $attributes[$key] = $value;
+ $colgroup_attributes[$key] = $value;
}
}
}
@@ -1599,24 +1577,24 @@ function theme_table($variables) {
$cols = $colgroup;
}
- // Build colgroup
- if (is_array($cols) && count($cols)) {
- $output .= ' ';
- foreach ($cols as $col) {
- $output .= ' ';
+ $variables['colgroups'][$colgroup_key]['attributes'] = $colgroup_attributes;
+
+ // Build colgroups and assign attributes
+ if (is_array($cols) && !empty($cols)) {
+ $variables['colgroups'][$colgroup_key]['cols'] = array();
+ foreach ($cols as $col_key => $col) {
+ $col_attributes = new Attribute($col);
+ $variables['colgroups'][$colgroup_key]['cols'][$col_key]['attributes'] = $col_attributes;
}
- $output .= " \n";
- }
- else {
- $output .= ' \n";
}
}
}
- // Add the 'empty' row message if available.
- if (!count($rows) && $empty) {
+ // Add one row spanning all columns for the 'empty' message if there are no
+ // data rows and the empty message is set.
+ if (empty($variables['rows']) && isset($variables['empty'])) {
$header_count = 0;
- foreach ($header as $header_cell) {
+ foreach ($variables['header'] as $header_cell) {
if (is_array($header_cell)) {
$header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1;
}
@@ -1624,96 +1602,171 @@ function theme_table($variables) {
$header_count++;
}
}
- $rows[] = array(array('data' => $empty, 'colspan' => $header_count, 'class' => array('empty', 'message')));
+ // Add one row for the empty message spanning all columns.
+ $variables['rows'][] = array(array(
+ 'data' => $variables['empty'],
+ 'attributes' => new Attribute(array(
+ 'colspan' => $header_count,
+ 'class' => array('empty', 'message'),
+ ))
+ ));
}
- $responsive = array();
- // Format the table header:
- if (count($header)) {
- $ts = tablesort_init($header);
- // HTML requires that the thead tag has tr tags in it followed by tbody
- // tags. Using ternary operator to check and see if we have any rows.
- $output .= (count($rows) ? ' ' : '
');
- $i = 0;
- foreach ($header as $cell) {
- $i++;
+ // Build an associative array of responsive classes keyed by column.
+ $responsive_classes = array();
+
+ // Preprocess table header.
+ if (!empty($variables['header'])) {
+
+ // Add sticky headers, if applicable.
+ if ($is_sticky) {
+ drupal_add_library('system', 'drupal.tableheader');
+ // Add 'sticky-enabled' class to the table to identify it for JavaScript.
+ $variables['attributes']['class'][] = 'sticky-enabled';
+ }
+
+ // If the table is responsive, set a class and add the necessary library.
+ if ($is_responsive) {
+ drupal_add_library('system', 'drupal.tableresponsive');
+ // Add 'responsive-enabled' class to the table to identify it
+ // for JavaScript.
+ $variables['attributes']['class'][] = 'responsive-enabled';
+ }
+
+ // Initialize table sorting.
+ $ts = tablesort_init($variables['header']);
+
+ foreach ($variables['header'] as $col_key => $cell) {
+ // If the cell is not an array, make it one.
+ if (!is_array($cell)) {
+ $cell = array(
+ 'data' => $cell,
+ 'attributes' => array(),
+ );
+ }
+
+ if (!isset($cell['attributes'])) {
+ $cell['attributes'] = array();
+ }
+
// Track responsive classes for each column as needed. Only the header
// cells for a column are marked up with the responsive classes by a
// module developer or themer. The responsive classes on the header cells
// must be transferred to the content cells.
if (!empty($cell['class']) && is_array($cell['class'])) {
if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) {
- $responsive[$i] = RESPONSIVE_PRIORITY_MEDIUM;
+ $responsive_classes[$col_key] = RESPONSIVE_PRIORITY_MEDIUM;
}
elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) {
- $responsive[$i] = RESPONSIVE_PRIORITY_LOW;
+ $responsive_classes[$col_key] = RESPONSIVE_PRIORITY_LOW;
+ }
+ }
+
+ $cell = tablesort_header($cell, $variables['header'], $ts);
+
+ // Move cell properties that are not data or attributes into attributes.
+ // Doing this after tablesort_header as it removes its sort and field
+ // values.
+ foreach ($cell as $key => $value) {
+ if ($key != 'data' && $key != 'attributes') {
+ $cell['attributes'][$key] = $value;
}
}
- $cell = tablesort_header($cell, $header, $ts);
- $output .= _theme_table_cell($cell, TRUE);
+
+ $cell['attributes'] = new Attribute($cell['attributes']);
+
+ // Flag the cell as a header.
+ $cell['header'] = TRUE;
+
+ // Update the header cell with attributes and properties.
+ $variables['header'][$col_key] = $cell;
}
- // Using ternary operator to close the tags based on whether or not there are rows
- $output .= (count($rows) ? "
\n" : "\n");
}
else {
$ts = array();
}
- // Format the table rows:
- if (count($rows)) {
- $output .= "\n";
- $flip = array('even' => 'odd', 'odd' => 'even');
+ if (!empty($variables['rows'])) {
+ $flip = array(
+ 'even' => 'odd',
+ 'odd' => 'even',
+ );
$class = 'even';
- foreach ($rows as $row) {
- // Check if we're dealing with a simple or complex row
+ 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'.
- $attributes = $row;
- unset($attributes['data']);
- unset($attributes['no_striping']);
+ $row_attributes = $row;
+ unset($row_attributes['data']);
+ unset($row_attributes['no_striping']);
}
else {
$cells = $row;
- $attributes = array();
+ $row_attributes = array();
$no_striping = FALSE;
}
- if (count($cells)) {
- // Add odd/even class
- if (!$no_striping) {
- $class = $flip[$class];
- $attributes['class'][] = $class;
- }
- // Build row
- $output .= ' ';
+ // Add odd/even class.
+ if (!$no_striping) {
+ $class = $flip[$class];
+ $row_attributes['class'][] = $class;
+ }
+
+ // Build row.
+ if (!empty($cells)) {
+ $variables['rows'][$row_key] = array();
+ $variables['rows'][$row_key]['attributes'] = new Attribute($row_attributes);
+ $variables['rows'][$row_key]['cells'] = array();
$i = 0;
foreach ($cells as $cell) {
- $i++;
+ // If the cell is not an array, make it one.
+ if (!is_array($cell)) {
+ $cell = array(
+ 'data' => $cell,
+ 'attributes' => array(),
+ );
+ }
+
+ if (!isset($cell['attributes'])) {
+ $cell['attributes'] = array();
+ }
+
// Add active class if needed for sortable tables.
- $cell = tablesort_cell($cell, $header, $ts, $i);
+ if (isset($variables['header'][$i]['data']) && $variables['header'][$i]['data'] == $ts['name'] && !empty($variables['header'][$i]['field'])) {
+ $cell['attributes']['class'][] = 'active';
+ }
+
+ // Move cell properties that are not data, attributes or header
+ // property into attributes.
+ foreach ($cell as $key => $value) {
+ if ($key != 'data' && $key != 'attributes' && $key != 'header') {
+ $cell['attributes'][$key] = $value;
+ }
+ }
+
// Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM
// class from header to cell as needed.
- if (isset($responsive[$i])) {
- if (is_array($cell)) {
- $cell['class'][] = $responsive[$i];
- }
- else {
- $cell = array('data' => $cell, 'class' => $responsive[$i]);
- }
+ if (isset($responsive_classes[$i])) {
+ $cell['attributes']['class'][] = $responsive_classes[$i];
+ }
+
+ $cell['attributes'] = new Attribute($cell['attributes']);
+
+ // Make sure that zeros get printed as '0'.
+ if (isset($cell['data']) && is_int($cell['data'])) {
+ $cell['data'] = (string) $cell['data'];
}
- $output .= _theme_table_cell($cell);
+
+ // Add the cell to the row.
+ $variables['rows'][$row_key]['cells'][$i] = $cell;
+ $i++;
}
- $output .= "
\n";
}
}
- $output .= "\n";
}
-
- $output .= "
\n";
- return $output;
}
/**
@@ -1936,47 +1989,6 @@ function theme_container($variables) {
*/
/**
- * Returns HTML output for a single table cell for theme_table().
- *
- * @param $cell
- * Array of cell information, or string to display in cell.
- * @param bool $header
- * TRUE if this cell is a table header cell, FALSE if it is an ordinary
- * table cell. If $cell is an array with element 'header' set to TRUE, that
- * will override the $header parameter.
- *
- * @return
- * HTML for the cell.
- */
-function _theme_table_cell($cell, $header = FALSE) {
- $attributes = '';
-
- if (is_array($cell)) {
- $data = isset($cell['data']) ? $cell['data'] : '';
- // Cell's data property can be a string or a renderable array.
- if (is_array($data)) {
- $data = drupal_render($data);
- }
- $header |= isset($cell['header']);
- unset($cell['data']);
- unset($cell['header']);
- $attributes = new Attribute($cell);
- }
- else {
- $data = $cell;
- }
-
- if ($header) {
- $output = "$data | ";
- }
- else {
- $output = "$data | ";
- }
-
- return $output;
-}
-
-/**
* Adds a default set of helper variables for preprocessors and templates.
*
* This function is called for theme hooks implemented as templates only, not
@@ -2610,6 +2622,7 @@ function drupal_common_theme() {
),
'table' => array(
'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => FALSE, 'responsive' => TRUE, 'empty' => ''),
+ 'template' => 'table',
),
'tablesort_indicator' => array(
'variables' => array('style' => NULL),
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 2271f56..1e8ae18 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -6,6 +6,7 @@
*/
use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Template\Attribute;
use Drupal\field\Field;
/**
@@ -729,10 +730,15 @@ function template_preprocess_forum_topic_list(&$variables) {
$header = '';
if (!empty($forum_topic_list_header)) {
// Create the tablesorting header.
+ // @see http://drupal.org/node/1938906.
$ts = tablesort_init($forum_topic_list_header);
foreach ($forum_topic_list_header as $cell) {
$cell = tablesort_header($cell, $forum_topic_list_header, $ts);
- $header .= _theme_table_cell($cell, TRUE);
+ // @todo Take care of this in the conversion to #type => table.
+ $_cell = $cell;
+ unset($_cell['data']);
+ $cell['attributes'] = new Attribute($_cell);
+ $header .= "" . drupal_render($cell['data']) . " | ";
}
}
$variables['header'] = $header;
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php
index 1a9d3d1..d9fea54 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php
@@ -63,8 +63,8 @@ function testThemeTableWithEmptyMessage() {
),
);
$this->content = theme('table', array('header' => $header, 'rows' => array(), 'empty' => t('No strings available.')));
- $this->assertRaw('No strings available. | ', 'Correct colspan was set on empty message.');
- $this->assertRaw('Header 1 | ', 'Table header was printed.');
+ $this->assertTrue($this->xpath('//tr[@class="odd"]/td[@colspan="3" and @class="empty message"]'), 'Correct colspan was set on empty message.');
+ $this->assertTrue($this->xpath('//thead/tr/th'), 'Table header was printed.');
}
/**
@@ -81,4 +81,20 @@ function testThemeTableWithNoStriping() {
$this->assertNoRaw('class="odd"', 'Odd/even classes were not added because $no_striping = TRUE.');
$this->assertNoRaw('no_striping', 'No invalid no_striping HTML attribute was printed.');
}
+
+ /**
+ * Tests that the 'header' option in cells works correctly.
+ */
+ function testThemeTableHeaderCellOption() {
+ $rows = array(
+ array(
+ array('data' => 1, 'header' => TRUE),
+ array('data' => 1, 'header' => FALSE),
+ array('data' => 1),
+ ),
+ );
+
+ $this->content = theme('table', array('rows' => $rows));
+ $this->assertRaw('1 | 1 | 1 | ', 'The th and td tags was printed correctly.');
+ }
}
diff --git a/core/modules/system/templates/table.html.twig b/core/modules/system/templates/table.html.twig
new file mode 100644
index 0000000..5f3ca0a
--- /dev/null
+++ b/core/modules/system/templates/table.html.twig
@@ -0,0 +1,86 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a table.
+ *
+ * Available variables:
+ * - attributes: HTML attributes to apply to the tag.
+ * - caption: A localized string for the tag.
+ * - colgroups: Column groups. Each group contains the following properties:
+ * - attributes: HTML attributes to apply to the tag.
+ * Note: Drupal currently supports only one table header row, see
+ * http://drupal.org/node/893530 and
+ * http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109.
+ * - header: Table header cells. Each cell contains the following properties:
+ * - attributes: HTML attributes to apply to the tag.
+ * - data: A localized string for the title of the column.
+ * - field: Field name (required for column sorting).
+ * - sort: Default sort order for this column ("asc" or "desc").
+ * - sticky: A flag indicating whether to use a "sticky" table header.
+ * - rows: Table rows. Each row contains the following properties:
+ * - attributes: HTML attributes to apply to the | tag.
+ * - data: Table cells.
+ * - no_striping: A flag indicating that the row should receive no
+ * 'even / odd' styling. Defaults to FALSE.
+ * @todo Deprecate no_striping?
+ * A cell can be either a string or may contain the following keys:
+ * - data: The string to display in the table cell.
+ * - header: Indicates this cell is a header.
+ * - attributes: Any HTML attributes, such as "colspan", to apply to the
+ * table cell.
+ * - empty: The message to display in an extra row if table does not have
+ * any rows.
+ *
+ * @see template_preprocess_table()
+ *
+ * @ingroup themeable
+ */
+#}
+
+ {# Caption #}
+ {% if caption %}
+ {{ caption }}
+ {% endif %}
+
+ {# Columns #}
+ {% for colgroup in colgroups %}
+ {% if colgroup.cols %}
+
+ {% for col in colgroup.cols %}
+
+ {% endfor %}
+
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+
+ {# Header #}
+ {% if header %}
+
+
+ {% for cell in header -%}
+
+ {{- cell.data -}}
+ |
+ {%- endfor %}
+
+
+ {% endif %}
+
+ {# Rows #}
+ {% if rows %}
+
+ {% for row in rows %}
+
+ {% for cell in row.cells -%}
+ {%- set cell_tag = cell.header ? 'th' : 'td' -%}
+ <{{ cell_tag }}{{ cell.attributes }}>
+ {{- cell.data -}}
+ {{ cell_tag }}>
+ {%- endfor %}
+
+ {% endfor %}
+
+ {% endif %}
+
---|