diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 3683d37..dccdcc0 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2061,9 +2061,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 @@ -2143,52 +2145,38 @@ 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 $number => $colgroup) { - $attributes = array(); +function template_preprocess_table(&$variables) { + // Flags. + $is_sticky = !empty($variables['sticky']); + $is_responsive = !empty($variables['responsive']); + $variables['attributes'] = new Attribute($variables['attributes']); + + // Table header. + if (!empty($variables['header']) && is_array($variables['header'])) { + $variables['header'] = array_values($variables['header']); + } + + $empty_message = (isset($variables['empty'])) ? $variables['empty'] : ''; + + // Remove 'empty' from template variables and set message to its own variable + // to make sure we don't conflict with Twig's reserved keyword of the same + // name. + unset($variables['empty']); - // Check if we're dealing with a simple or complex column + $variables['empty_message'] = $empty_message; + + if (!empty($variables['colgroups'])) { + foreach ($variables['colgroups'] as $colgroup_key => $colgroup) { + $colgroup_attributes = new Attribute(array()); + + // 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; } } } @@ -2196,25 +2184,24 @@ function theme_table($variables) { $cols = $colgroup; } - // Build colgroup - if (is_array($cols) && count($cols)) { - $output .= ' '; - $i = 0; - 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 not empty. + if (empty($variables['rows']) && !empty($empty_message)) { $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; } @@ -2222,96 +2209,172 @@ 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' => $empty_message, + '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, $header, $ts); - $output .= _theme_table_cell($cell, TRUE); + + $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['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'])) { + + // Add odd/even class. + // @todo Replace this because zebras are for the zoo. + // @see http://drupal.org/node/1649780 + $flip = array( + 'even' => 'odd', + 'odd' => 'even', + ); $class = 'even'; - foreach ($rows as $number => $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); + $cell = tablesort_cell($cell, $variables['header'], $ts, $i); + + // 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; } /** @@ -2619,47 +2682,6 @@ function theme_indentation($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 variable processors and templates. * * This function is called for theme hooks implemented as templates only, not @@ -3271,6 +3293,7 @@ function drupal_common_theme() { ), 'table' => array( 'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE, 'responsive' => TRUE, 'empty' => ''), + 'template' => 'table', ), 'meter' => array( 'variables' => array('display_value' => NULL, 'form' => NULL, 'high' => NULL, 'low' => NULL, 'max' => NULL, 'min' => NULL, 'optimum' => NULL, 'value' => NULL, 'percentage' => NULL, 'attributes' => array()), diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 1a5e009..8d05496 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\entity\Plugin\Core\Entity\EntityDisplay; use Drupal\taxonomy\Plugin\Core\Entity\Term; @@ -1123,7 +1124,12 @@ function template_preprocess_forum_topic_list(&$variables) { $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. + // @see http://drupal.org/node/1938906. + $_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 0dbedd7..a235849 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('111', '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..031e9a2 --- /dev/null +++ b/core/modules/system/templates/table.html.twig @@ -0,0 +1,87 @@ +{# +/** + * @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. + * 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: 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_message: The message to display in an extra row if table does not + * have any rows. + * + * @see template_preprocess() + * @see template_preprocess_table() + * + * @ingroup themeable + */ +#} + + {# Caption #} + {% if 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 -}} + + {%- endfor %} + + {% endfor %} + + {% endif %} +
tag. + * - colgroups: Column groups. Each group 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
{{ caption }}