mCaption = $caption;
$this->getOutput()->addModules( 'mediawiki.pager.codex' );
parent::__construct( $context, $linkRenderer );
$this->getOutput()->addJsConfigVars( [
'wgCodexTablePagerLimit' => $this->mLimit,
] );
}
/**
* Get the entire Codex table markup, including the wrapper element, pagers, table wrapper
* (which enables horizontal scroll), and the table element.
*
* @since 1.44
*/
public function getFullOutput(): ParserOutput {
// Pagers.
$navigation = $this->getNavigationBar();
// `
` element and its contents.
$body = parent::getBody();
$pout = new ParserOutput();
$pout->setRawText(
Html::openElement( 'div', [ 'class' => 'cdx-table' ] ) . "\n" .
// In the future, a visible caption + header content could go here.
$navigation . "\n" .
Html::openElement( 'div', [ 'class' => 'cdx-table__table-wrapper' ] ) . "\n" .
$body . "\n" .
Html::closeElement( 'div' ) . "\n" .
// In the future, footer content could go here.
$navigation . "\n" .
Html::closeElement( 'div' )
);
$pout->addModuleStyles( $this->getModuleStyles() );
return $pout;
}
/**
* Generate the `` element.
*
* This creates a thead with a single tr and includes sort buttons if applicable. To customize
* the thead layout, override this method.
*
* @stable to override
*/
protected function getThead(): string {
$theadContent = '';
$fields = $this->getFieldNames();
// Build each th element.
foreach ( $fields as $field => $name ) {
if ( $name === '' ) {
// th with no label (not advised).
$theadContent .= Html::rawElement( 'th', $this->getCellAttrs( $field, $name ), "\u{00A0}" ) . "\n";
} elseif ( $this->isFieldSortable( $field ) ) {
// Sortable column.
$query = [ 'sort' => $field, 'limit' => $this->mLimit ];
$sortIconClasses = [ 'cdx-table__table__sort-icon' ];
if ( $this->mSort == $field ) {
// Set data for the currently sorted column.
if ( $this->mDefaultDirection == IndexPager::DIR_DESCENDING ) {
$sortIconClasses[] = 'cdx-table__table__sort-icon--desc';
$query['asc'] = '1';
$query['desc'] = '';
} else {
$sortIconClasses[] = 'cdx-table__table__sort-icon--asc';
$query['asc'] = '';
$query['desc'] = '1';
}
} else {
$sortIconClasses[] = 'cdx-table__table__sort-icon--unsorted';
}
// Build the label and icon span that go inside the link.
$linkContents = Html::rawElement( 'span',
[ 'class' => 'cdx-table__table__sort-label' ],
htmlspecialchars( $name )
) . "\n" .
Html::rawElement( 'span',
[ 'class' => $sortIconClasses, 'aria-hidden' => true ]
) . "\n";
// Build the link that goes inside the th.
$link = Html::rawElement( 'a',
[
'class' => [ 'cdx-table__table__sort-button' ],
'role' => 'button',
'href' => $this->getTitle()->getLinkURL( $query + $this->getDefaultQuery() ),
],
$linkContents
);
// Build the th.
$thAttrs = $this->getCellAttrs( $field, $name );
$thAttrs[ 'class' ][] = $this->getSortHeaderClass();
$theadContent .= Html::rawElement( 'th', $thAttrs, $link ) . "\n";
} else {
// Unsortable column.
$theadContent .= Html::element( 'th', $this->getCellAttrs( $field, $name ), $name ) . "\n";
}
}
return Html::rawElement( 'thead', [], Html::rawElement( 'tr', [], "\n" . $theadContent . "\n" ) );
}
/**
* Append text to the caption if any fields are sortable.
*
* @param string $captionText Caption provided for the table
*/
private function getFullCaption( string $captionText ): string {
$fields = $this->getFieldNames();
// Make table header
foreach ( $fields as $field => $name ) {
if ( $this->isFieldSortable( $field ) === true ) {
return $this->msg( 'cdx-table-sort-caption', $captionText )->text();
}
}
return $captionText;
}
/**
* Get the opening table tag through the opening tbody tag.
*
* This method should generally not be overridden: use getThead() to create a custom ``
* and getTableClass to set additional classes on the `` element.
*
* @stable to override
*/
protected function getStartBody(): string {
$ret = Html::openElement( 'table', [
'class' => $this->getTableClass() ]
);
$ret .= Html::rawElement( 'caption', [], $this->getFullCaption( $this->mCaption ) );
$ret .= $this->getThead();
$ret .= Html::openElement( 'tbody' ) . "\n";
return $ret;
}
/**
* Override to add a `` element.
*
* @stable to override
*/
protected function getTfoot(): string {
return '';
}
/**
* Get the closing tbody tag through the closing table tag.
*
* @stable to override
*/
protected function getEndBody(): string {
return "" . $this->getTfoot() . "
\n";
}
/**
* Get markup for the "no results" UI. This is placed inside the tbody tag.
*/
protected function getEmptyBody(): string {
$colspan = count( $this->getFieldNames() );
$msgEmpty = $this->msg( 'table_pager_empty' )->text();
return Html::rawElement( 'tr', [ 'class' => 'cdx-table__table__empty-state' ],
Html::element(
'td',
[ 'class' => 'cdx-table__table__empty-state-content', 'colspan' => $colspan ],
$msgEmpty )
);
}
/**
* Add alignment per column.
*
* @param string $field The column
* @return string start (default), center, end, or number (always to the right)
*/
protected function getCellAlignment( string $field ): string {
return 'start';
}
/**
* Add extra attributes to be applied to the given cell.
*
* @stable to override
*
* @param string $field The column
* @param string $value The cell contents
* @return array Array of attr => value
*/
protected function getCellAttrs( $field, $value ): array {
return [
'class' => [
'cdx-table-pager__col--' . $field,
'cdx-table__table__cell--align-' . $this->getCellAlignment( $field )
]
];
}
/**
* Class for the `` element.
*
* @stable to override
*/
protected function getTableClass(): string {
return 'cdx-table__table';
}
/**
* Class for the outermost element of the pager UI.
*
* @stable to override
*/
protected function getNavClass(): string {
return 'cdx-table-pager';
}
/**
* Class for th elements of sortable columns.
*
* @stable to override
*/
protected function getSortHeaderClass(): string {
return 'cdx-table__table__cell--has-sort';
}
/**
* Pager bar with per-page limit and pager buttons.
*
* @stable to override
*
* @return string HTML for the pager UI
*/
public function getNavigationBar(): string {
if ( !$this->isNavigationBarShown() ) {
return '';
}
$types = [ 'first', 'prev', 'next', 'last' ];
$queries = $this->getPagingQueries();
$title = $this->getTitle();
$buttons = [];
foreach ( $types as $type ) {
// TODO: Update Codex class suffix for previous to 'prev' so we don't have to do this.
$classSuffix = $type === 'prev' ? 'previous' : $type;
$isDisabled = $queries[ $type ] === false;
$buttons[] = Html::rawElement( 'a',
[
'class' => [
'cdx-button',
'cdx-button--fake-button',
'cdx-button--fake-button--' . ( $isDisabled ? 'disabled' : 'enabled' ),
'cdx-button--weight-quiet',
'cdx-button--icon-only'
],
'role' => 'button',
'disabled' => $queries[ $type ] === false,
'aria-label' => $this->msg( 'table_pager_' . $type )->text(),
'href' => $queries[ $type ] ?
$title->getLinkURL( $queries[ $type ] + $this->getDefaultQuery() ) :
null,
],
Html::rawElement( 'span',
[ 'class' => [ 'cdx-button__icon', 'cdx-table-pager__icon--' . $classSuffix ] ]
)
);
}
return Html::openElement( 'div', [ 'class' => $this->getNavClass() ] ) . "\n" .
Html::rawElement( 'div', [ 'class' => 'cdx-table-pager__start' ], $this->getLimitForm() ) . "\n" .
Html::rawElement( 'div', [ 'class' => 'cdx-table-pager__end' ], implode( '', $buttons ) ) . "\n" .
Html::closeElement( 'div' );
}
/**
* Get a `