Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / pager / CodexTablePager.php
blob5b27257d3d42b31729f009718aaeb1b69451c98c
1 <?php
2 /**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
18 * @file
21 namespace MediaWiki\Pager;
23 use InvalidArgumentException;
24 use MediaWiki\Context\IContextSource;
25 use MediaWiki\Html\Html;
26 use MediaWiki\Linker\LinkRenderer;
27 use MediaWiki\Parser\ParserOutput;
29 /**
30 * Codex Table display of sortable data with pagination.
32 * @stable to extend
33 * @ingroup Pager
35 abstract class CodexTablePager extends TablePager {
36 protected string $mCaption;
38 /**
39 * @stable to call
41 * @param string $caption Text for visually-hidden caption element
42 * @param ?IContextSource $context
43 * @param ?LinkRenderer $linkRenderer
45 public function __construct(
46 string $caption,
47 ?IContextSource $context = null,
48 ?LinkRenderer $linkRenderer = null
49 ) {
50 if ( trim( $caption ) === '' ) {
51 throw new InvalidArgumentException( 'Table caption is required.' );
53 $this->mCaption = $caption;
55 $this->getOutput()->addModules( 'mediawiki.pager.codex' );
57 parent::__construct( $context, $linkRenderer );
59 $this->getOutput()->addJsConfigVars( [
60 'wgCodexTablePagerLimit' => $this->mLimit,
61 ] );
64 /**
65 * Get the entire Codex table markup, including the wrapper element, pagers, table wrapper
66 * (which enables horizontal scroll), and the table element.
68 * @since 1.44
70 public function getFullOutput(): ParserOutput {
71 // Pagers.
72 $navigation = $this->getNavigationBar();
73 // `<table>` element and its contents.
74 $body = parent::getBody();
76 $pout = new ParserOutput();
77 $pout->setRawText(
78 Html::openElement( 'div', [ 'class' => 'cdx-table' ] ) . "\n" .
79 // In the future, a visible caption + header content could go here.
80 $navigation . "\n" .
81 Html::openElement( 'div', [ 'class' => 'cdx-table__table-wrapper' ] ) . "\n" .
82 $body . "\n" .
83 Html::closeElement( 'div' ) . "\n" .
84 // In the future, footer content could go here.
85 $navigation . "\n" .
86 Html::closeElement( 'div' )
88 $pout->addModuleStyles( $this->getModuleStyles() );
89 return $pout;
92 /**
93 * Generate the `<thead>` element.
95 * This creates a thead with a single tr and includes sort buttons if applicable. To customize
96 * the thead layout, override this method.
98 * @stable to override
100 protected function getThead(): string {
101 $theadContent = '';
102 $fields = $this->getFieldNames();
104 // Build each th element.
105 foreach ( $fields as $field => $name ) {
106 if ( $name === '' ) {
107 // th with no label (not advised).
108 $theadContent .= Html::rawElement( 'th', $this->getCellAttrs( $field, $name ), "\u{00A0}" ) . "\n";
109 } elseif ( $this->isFieldSortable( $field ) ) {
110 // Sortable column.
111 $query = [ 'sort' => $field, 'limit' => $this->mLimit ];
112 $sortIconClasses = [ 'cdx-table__table__sort-icon' ];
114 if ( $this->mSort == $field ) {
115 // Set data for the currently sorted column.
116 if ( $this->mDefaultDirection == IndexPager::DIR_DESCENDING ) {
117 $sortIconClasses[] = 'cdx-table__table__sort-icon--desc';
118 $query['asc'] = '1';
119 $query['desc'] = '';
120 } else {
121 $sortIconClasses[] = 'cdx-table__table__sort-icon--asc';
122 $query['asc'] = '';
123 $query['desc'] = '1';
125 } else {
126 $sortIconClasses[] = 'cdx-table__table__sort-icon--unsorted';
129 // Build the label and icon span that go inside the link.
130 $linkContents = Html::rawElement( 'span',
131 [ 'class' => 'cdx-table__table__sort-label' ],
132 htmlspecialchars( $name )
133 ) . "\n" .
134 Html::rawElement( 'span',
135 [ 'class' => $sortIconClasses, 'aria-hidden' => true ]
136 ) . "\n";
138 // Build the link that goes inside the th.
139 $link = Html::rawElement( 'a',
141 'class' => [ 'cdx-table__table__sort-button' ],
142 'role' => 'button',
143 'href' => $this->getTitle()->getLinkURL( $query + $this->getDefaultQuery() ),
145 $linkContents
148 // Build the th.
149 $thAttrs = $this->getCellAttrs( $field, $name );
150 $thAttrs[ 'class' ][] = $this->getSortHeaderClass();
151 $theadContent .= Html::rawElement( 'th', $thAttrs, $link ) . "\n";
152 } else {
153 // Unsortable column.
154 $theadContent .= Html::element( 'th', $this->getCellAttrs( $field, $name ), $name ) . "\n";
157 return Html::rawElement( 'thead', [], Html::rawElement( 'tr', [], "\n" . $theadContent . "\n" ) );
161 * Append text to the caption if any fields are sortable.
163 * @param string $captionText Caption provided for the table
165 private function getFullCaption( string $captionText ): string {
166 $fields = $this->getFieldNames();
168 // Make table header
169 foreach ( $fields as $field => $name ) {
170 if ( $this->isFieldSortable( $field ) === true ) {
171 return $this->msg( 'cdx-table-sort-caption', $captionText )->text();
175 return $captionText;
179 * Get the opening table tag through the opening tbody tag.
181 * This method should generally not be overridden: use getThead() to create a custom `<thead>`
182 * and getTableClass to set additional classes on the `<table>` element.
184 * @stable to override
186 protected function getStartBody(): string {
187 $ret = Html::openElement( 'table', [
188 'class' => $this->getTableClass() ]
190 $ret .= Html::rawElement( 'caption', [], $this->getFullCaption( $this->mCaption ) );
191 $ret .= $this->getThead();
192 $ret .= Html::openElement( 'tbody' ) . "\n";
194 return $ret;
198 * Override to add a `<tfoot>` element.
200 * @stable to override
202 protected function getTfoot(): string {
203 return '';
207 * Get the closing tbody tag through the closing table tag.
209 * @stable to override
211 protected function getEndBody(): string {
212 return "</tbody>" . $this->getTfoot() . "</table>\n";
216 * Get markup for the "no results" UI. This is placed inside the tbody tag.
218 protected function getEmptyBody(): string {
219 $colspan = count( $this->getFieldNames() );
220 $msgEmpty = $this->msg( 'table_pager_empty' )->text();
221 return Html::rawElement( 'tr', [ 'class' => 'cdx-table__table__empty-state' ],
222 Html::element(
223 'td',
224 [ 'class' => 'cdx-table__table__empty-state-content', 'colspan' => $colspan ],
225 $msgEmpty )
230 * Add alignment per column.
232 * @param string $field The column
233 * @return string start (default), center, end, or number (always to the right)
235 protected function getCellAlignment( string $field ): string {
236 return 'start';
240 * Add extra attributes to be applied to the given cell.
242 * @stable to override
244 * @param string $field The column
245 * @param string $value The cell contents
246 * @return array Array of attr => value
248 protected function getCellAttrs( $field, $value ): array {
249 return [
250 'class' => [
251 'cdx-table-pager__col--' . $field,
252 'cdx-table__table__cell--align-' . $this->getCellAlignment( $field )
258 * Class for the `<table>` element.
260 * @stable to override
262 protected function getTableClass(): string {
263 return 'cdx-table__table';
267 * Class for the outermost element of the pager UI.
269 * @stable to override
271 protected function getNavClass(): string {
272 return 'cdx-table-pager';
276 * Class for th elements of sortable columns.
278 * @stable to override
280 protected function getSortHeaderClass(): string {
281 return 'cdx-table__table__cell--has-sort';
285 * Pager bar with per-page limit and pager buttons.
287 * @stable to override
289 * @return string HTML for the pager UI
291 public function getNavigationBar(): string {
292 if ( !$this->isNavigationBarShown() ) {
293 return '';
296 $types = [ 'first', 'prev', 'next', 'last' ];
297 $queries = $this->getPagingQueries();
298 $title = $this->getTitle();
300 $buttons = [];
302 foreach ( $types as $type ) {
303 // TODO: Update Codex class suffix for previous to 'prev' so we don't have to do this.
304 $classSuffix = $type === 'prev' ? 'previous' : $type;
305 $isDisabled = $queries[ $type ] === false;
306 $buttons[] = Html::rawElement( 'a',
308 'class' => [
309 'cdx-button',
310 'cdx-button--fake-button',
311 'cdx-button--fake-button--' . ( $isDisabled ? 'disabled' : 'enabled' ),
312 'cdx-button--weight-quiet',
313 'cdx-button--icon-only'
315 'role' => 'button',
316 'disabled' => $queries[ $type ] === false,
317 'aria-label' => $this->msg( 'table_pager_' . $type )->text(),
318 'href' => $queries[ $type ] ?
319 $title->getLinkURL( $queries[ $type ] + $this->getDefaultQuery() ) :
320 null,
322 Html::rawElement( 'span',
323 [ 'class' => [ 'cdx-button__icon', 'cdx-table-pager__icon--' . $classSuffix ] ]
328 return Html::openElement( 'div', [ 'class' => $this->getNavClass() ] ) . "\n" .
329 Html::rawElement( 'div', [ 'class' => 'cdx-table-pager__start' ], $this->getLimitForm() ) . "\n" .
330 Html::rawElement( 'div', [ 'class' => 'cdx-table-pager__end' ], implode( '', $buttons ) ) . "\n" .
331 Html::closeElement( 'div' );
335 * Get a `<select>` element with options for items-per-page limits.
337 * @param string[] $attribs Extra attributes to set
338 * @return string HTML fragment
340 public function getLimitSelect( array $attribs = [] ): string {
341 return parent::getLimitSelect( [ 'class' => 'cdx-select' ] );
345 * Get a list of items to show as options in the item-per-page select.
347 public function getLimitSelectList(): array {
348 $options = parent::getLimitSelectList();
350 foreach ( $options as $key => $option ) {
351 // Add new option with "rows" after the number.
352 $options[ $this->msg( 'cdx-table-pager-items-per-page-current', $option )->text() ] = $option;
353 // Remove the old option.
354 unset( $options[ $key ] );
357 return $options;
361 * Get a form with the items-per-page select.
363 * @return string HTML fragment
365 public function getLimitForm(): string {
366 return Html::rawElement(
367 'form',
369 'id' => 'cdx-table-pager-limit-form',
370 'method' => 'get',
371 'action' => wfScript(),
372 'class' => 'cdx-table-pager__limit-form'
374 "\n" . $this->getLimitSelect() . "\n" .
375 Html::element( 'button',
376 [ 'class' => [ 'cdx-button', 'cdx-table-pager__limit-form__submit' ] ],
377 $this->msg( 'table_pager_limit_submit' )->text()
378 ) . "\n" .
379 $this->getHiddenFields( [ 'limit' ] )
380 ) . "\n";
384 * @inheritDoc
386 public function getModuleStyles(): array {
387 return [ 'mediawiki.pager.codex.styles' ];
391 /** @deprecated class alias since 1.41 */
392 class_alias( CodexTablePager::class, 'CodexTablePager' );