diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc index ee50869006..44bdc474f5 100644 --- a/core/includes/tablesort.inc +++ b/core/includes/tablesort.inc @@ -56,8 +56,16 @@ function tablesort_header(&$cell_content, array &$cell_attributes, array $header $image = \Drupal::service('renderer')->render($tablesort_indicator); } else { - // If the user clicks a different header, we want to sort ascending initially. - $ts['sort'] = 'asc'; + // This determines the sort order when the column gets first clicked by + // the user. It is "asc" by default but the sort can be changed if + // $cell['initial_click_sort'] is defined, the possible values are 'asc' + // or 'desc'. + if (isset($cell_attributes['initial_click_sort']) && in_array(strtolower($cell_attributes['initial_click_sort']), ['asc', 'desc'])) { + $ts['sort'] = strtolower($cell_attributes['initial_click_sort']); + } + else { + $ts['sort'] = 'asc'; + } $image = ''; } $cell_content = \Drupal::l(new FormattableMarkup('@cell_content@image', ['@cell_content' => $cell_content, '@image' => $image]), new Url('', [], [ @@ -68,7 +76,7 @@ function tablesort_header(&$cell_content, array &$cell_attributes, array $header ]), ])); - unset($cell_attributes['field'], $cell_attributes['sort']); + unset($cell_attributes['field'], $cell_attributes['sort'], $cell_attributes['initial_click_sort']); } } @@ -134,14 +142,21 @@ function tablesort_get_sort($headers) { if ($query->has('sort')) { return (strtolower($query->get('sort')) == 'desc') ? 'desc' : 'asc'; } - // The user has not specified a sort. Use the default for the currently sorted - // header if specified; otherwise use "asc". + // The user has not specified a sort. Use the initial_click_sort param if + // defined if not use the default "asc". else { // Find out which header is currently being sorted. $ts = tablesort_get_order($headers); foreach ($headers as $header) { - if (is_array($header) && isset($header['data']) && $header['data'] == $ts['name'] && isset($header['sort'])) { - return $header['sort']; + if (is_array($header) && isset($header['data']) && $header['data'] == $ts['name']) { + + if (isset($header['sort'])) { + return $header['sort']; + } + + if (isset($header['initial_click_sort'])) { + return (strtolower($header['initial_click_sort']) == 'desc') ? 'desc' : 'asc'; + } } } } diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 30e5aafc6c..c9e8be9ccf 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -822,6 +822,8 @@ function template_preprocess_image(&$variables) { * - 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. + * - initial_click_sort: Set the initial sort of the column when clicked, by + * default is asc. * - 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 @@ -975,7 +977,8 @@ function template_preprocess_table(&$variables) { tablesort_header($cell_content, $cell, $variables['header'], $ts); - // tablesort_header() removes the 'sort' and 'field' keys. + // tablesort_header() removes the 'sort', 'initial_click_sort' and + // 'field' keys. $cell_attributes = new Attribute($cell); } $variables['header'][$col_key] = []; diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/TableSortExtenderTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/TableSortExtenderTest.php index 4291179f40..fccf7802e4 100644 --- a/core/tests/Drupal/KernelTests/Core/Render/Element/TableSortExtenderTest.php +++ b/core/tests/Drupal/KernelTests/Core/Render/Element/TableSortExtenderTest.php @@ -136,6 +136,120 @@ public function testTableSortInit() { $ts = tablesort_init($headers); $this->verbose(strtr('$ts:
!ts
', ['!ts' => Html::escape(var_export($ts, TRUE))])); $this->assertEqual($ts, $expected_ts, 'Complex table headers plus $_GET parameters sorted correctly.'); - } + // Test the initial_click_sort parameter. + $headers = [ + 'foo', + [ + 'data' => '1', + 'field' => 'one', + 'initial_click_sort' => 'desc', + 'colspan' => 1, + ], + [ + 'data' => '2', + 'field' => 'two', + ], + [ + 'data' => '3', + 'field' => 'three', + 'initial_click_sort' => 'desc', + 'sort' => 'asc', + ], + [ + 'data' => '4', + 'field' => 'four', + 'initial_click_sort' => 'asc', + ], + [ + 'data' => '5', + 'field' => 'five', + 'initial_click_sort' => 'foo', + ], + ]; + $request = Request::createFromGlobals(); + $request->query->replace([ + 'order' => '1', + ]); + \Drupal::getContainer()->get('request_stack')->push($request); + $ts = tablesort_init($headers); + $this->verbose(strtr('$ts:
!ts
', ['!ts' => Html::escape(var_export($ts, TRUE))])); + $expected_ts = [ + 'name' => '1', + 'sql' => 'one', + 'sort' => 'desc', + 'query' => [], + ]; + $this->verbose(strtr('$ts:
!ts
', ['!ts' => Html::escape(var_export($ts, TRUE))])); + $this->assertEqual($ts, $expected_ts, 'Complex table headers using the initial_click_sort parameter are sorted correctly.'); + + // Test that if the initial_click_sort parameter is not defined the default + // must be used instead which is 'asc'. + $request = Request::createFromGlobals(); + $request->query->replace([ + 'order' => '2', + ]); + \Drupal::getContainer()->get('request_stack')->push($request); + $ts = tablesort_init($headers); + $expected_ts = [ + 'name' => '2', + 'sql' => 'two', + 'sort' => 'asc', + 'query' => [], + ]; + $this->verbose(strtr('$ts:
!ts
', ['!ts' => Html::escape(var_export($ts, TRUE))])); + $this->assertEqual($ts, $expected_ts, 'Complex table headers without using the initial_click_sort parameter are sorted correctly.'); + + // Test that if the initial_click_sort parameter is defined and the sort + // parameter is defined as well the sort parameter should be has precedence. + $request = Request::createFromGlobals(); + $request->query->replace([ + 'order' => '3', + ]); + \Drupal::getContainer()->get('request_stack')->push($request); + $ts = tablesort_init($headers); + $expected_ts = [ + 'name' => '3', + 'sql' => 'three', + 'sort' => 'asc', + 'query' => [], + ]; + $this->verbose(strtr('$ts:
!ts
', ['!ts' => Html::escape(var_export($ts, TRUE))])); + $this->assertEqual($ts, $expected_ts, 'Complex table headers using the initial_click_sort and sort parameters are sorted correctly.'); + + // Test that if the initial_click_sort parameter is defined and the value + // is asc it should be sorted correctly. + $request = Request::createFromGlobals(); + $request->query->replace([ + 'order' => '4', + ]); + \Drupal::getContainer()->get('request_stack')->push($request); + $ts = tablesort_init($headers); + $expected_ts = [ + 'name' => '4', + 'sql' => 'four', + 'sort' => 'asc', + 'query' => [], + ]; + $this->verbose(strtr('$ts:
!ts
', ['!ts' => Html::escape(var_export($ts, TRUE))])); + $this->assertEqual($ts, $expected_ts, 'Complex table headers with the initial_click_sort set as ASC are sorted correctly.'); + + // Test that if the initial_click_sort parameter is defined and the value + // is none of the expected values then the default value should be used + // instead. + $request = Request::createFromGlobals(); + $request->query->replace([ + 'order' => '5', + ]); + \Drupal::getContainer()->get('request_stack')->push($request); + $ts = tablesort_init($headers); + $expected_ts = [ + 'name' => '5', + 'sql' => 'five', + 'sort' => 'asc', + 'query' => [], + ]; + $this->verbose(strtr('$ts:
!ts
', ['!ts' => Html::escape(var_export($ts, TRUE))])); + $this->assertEqual($ts, $expected_ts, 'Complex table headers with the initial_click_sort set as an unexpected value are sorted correctly.'); + } }