diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc index 48a8658..4359eff 100644 --- a/core/includes/tablesort.inc +++ b/core/includes/tablesort.inc @@ -70,35 +70,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 f98aadd..f5d1ecd 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1479,9 +1479,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 @@ -1563,52 +1565,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']; +function template_preprocess_table(&$variables) { + // Flags. + $is_sticky = !empty($variables['sticky']); + $is_responsive = !empty($variables['responsive']); - // 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'; + // Table header. + if (!empty($variables['header']) && is_array($variables['header'])) { + $variables['header'] = array_values($variables['header']); } - $output = '\n"; + if (!empty($variables['colgroups'])) { + foreach ($variables['colgroups'] as $colgroup_key => $colgroup) { + $colgroup_attributes = new Attribute(); - if (isset($caption)) { - $output .= '' . $caption . "\n"; - } - - // Format the table columns: - if (count($colgroups)) { - foreach ($colgroups as $colgroup) { - $attributes = array(); - - // Check if we're dealing with a simple or complex column + // 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; } } } @@ -1616,24 +1594,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; } @@ -1641,96 +1619,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(); + } + + // 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; + } + } + // 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'; + } + // 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; } /** @@ -1910,47 +1963,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 @@ -2598,6 +2610,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/system/lib/Drupal/system/Tests/Theme/TableTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php index 1a9d3d1..30c5952 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TableTest.php @@ -27,7 +27,7 @@ public static function getInfo() { function testThemeTableStickyHeaders() { $header = array('one', 'two', 'three'); $rows = array(array(1,2,3), array(4,5,6), array(7,8,9)); - $this->content = theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => TRUE)); + $this->content = drupal_render(array('#theme' => 'table', '#header' => $header, '#rows' => $rows, '#sticky' => TRUE)); $js = _drupal_add_js(); $this->assertTrue(isset($js['core/misc/tableheader.js']), 'tableheader.js was included when $sticky = TRUE.'); $this->assertRaw('sticky-enabled', 'Table has a class of sticky-enabled when $sticky = TRUE.'); @@ -43,7 +43,7 @@ function testThemeTableNoStickyHeaders() { $attributes = array(); $caption = NULL; $colgroups = array(); - $this->content = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => $attributes, 'caption' => $caption, 'colgroups' => $colgroups, 'sticky' => FALSE)); + $this->content = drupal_render(array('#theme' => 'table', '#header' => $header, '#rows' => $rows, '#attributes' => $attributes, '#caption' => $caption, '#colgroups' => $colgroups, '#sticky' => FALSE)); $js = _drupal_add_js(); $this->assertFalse(isset($js['core/misc/tableheader.js']), 'tableheader.js was not included because $sticky = FALSE.'); $this->assertNoRaw('sticky-enabled', 'Table does not have a class of sticky-enabled because $sticky = FALSE.'); @@ -62,9 +62,9 @@ function testThemeTableWithEmptyMessage() { 'colspan' => 2, ), ); - $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->content = drupal_render(array('#theme' => 'table', '#header' => $header, '#rows' => array(), '#empty' => t('No strings available.'))); + $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.'); } /** @@ -77,8 +77,24 @@ function testThemeTableWithNoStriping() { 'no_striping' => TRUE, ), ); - $this->content = theme('table', array('rows' => $rows)); + $this->content = drupal_render(array('#theme' => 'table', '#rows' => $rows)); $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 = drupal_render(array('#theme' => 'table', '#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..0135898 --- /dev/null +++ b/core/modules/system/templates/table.html.twig @@ -0,0 +1,85 @@ +{# +/** + * @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. + * 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 %} + + {% 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 }}