tl;dr: This custom module adds a wrapper div around tables in content.
The Problem
Tables can be added within content through the CKEditor text field. Depending on other layout on the website, a very wide table might end up pushing the entire main content area down below a sidebar in order to make room. What would be better is if the table was scrollable in place, able to fit a lot of content but not all showing at once.
The Solution
The full module is in my Codeberg. Note that there may be updates there which are not reflected here.
The approach is to use a CKEditor filter to add a <div> with a certain table-wrapper class around the table. This wrapper is indexed for tabbing through the site, so that users can land on it, as well as set to scroll horizontally.
The Text Filter
The first essential file to making that happen is the CKEditor filter, which goes under src/Plugin/Filter:
<?php
namespace Drupal\ckeditor_table_wrapper\Plugin\Filter;
use Drupal\Component\Utility\Html;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a filter to wrap tables in table-wrapper div.
*
* @Filter(
* id = "ckeditor_table_wrapper_filter",
* title = @Translation("Wrap tables in a div with class table-wrapper."),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
* weight = -100
* )
*/
class FilterTableWrapper extends FilterBase implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
);
}
/**
* {@inheritdoc}
*/
public function process($text, $langcode): FilterProcessResult {
$dom = Html::load($text);
$tables = $dom->getElementsByTagName('table');
foreach ($tables as $table) {
$div = $dom->createElement('div');
$div->setAttribute('class', 'table-wrapper');
$div->setAttribute('tabindex', '0');
$parent_node = $table->parentNode;
if ($parent_node instanceof \DOMNode) {
$parent_node->replaceChild($div, $table);
$div->appendChild($table);
}
}
$dom->saveHTML();
$text = Html::serialize($dom);
return new FilterProcessResult($text);
}
}
The logic is pretty straightforward: find any tables and then wrap a div with class table-wrapper and tabindex around it.
The Styles
The CSS stylesheet provides the requirements for table-wrapper:
.table-wrapper {
width: fit-content;
max-width: 100%;
overflow-x: auto;
}
That locks it to maximum width of 100% and with a scroll bar to overflow horizontally if it gets longer than that, rather than pushing the whole region down.
Previous: Big Tech Update
Next: Google Gem Prompt