Adding an advanced table block to Wagtail

I was tasked recently to implement a more advanced table block for Wagtail StreamField than the Handsontable-powered TableBlock that ships out of the box.

I'm a big fan of the native implementation, but it's limited when it comes to simple features such as cell merging and applying styles.

After some initial investigation into extending this block or attempting another implementation, we landed on using a customised TinyMCE rich text editor, with most of the controls stripped out to keep the editor tightly focused on table creation and editing.

This post is a simplified version of the docs that I wrote up to support the block.

Hopefully it will help someone wanting to do something similar, or more likely, it will remind my future, older, self how clever younger me was.

Introducing: AdvancedTableBlock

This block utilises the TinyMCE rich text editor to allow more control over formatting, cell merging, and cell alignment than the standard Wagtail TableBlock.

Rich text editor basics

A rich text editor in a CMS has to perform a number of functions to enable a consistent editing experience, and protect the data and front end visitors from badly formatted or malicious content.

In general, these steps are:

  • Enable pasting or entering of rich text content, preserving structure where possible (such as headings and paragraphs) and discarding unwanted or dangerous content (such as inline styles or script tags). This is usually handled by the JavaScript rich text component (the native Wagtail component is Draftail)
  • Process the entered data to a format that can be saved to the database. The format will generally be HTML, but can involve manipulating items such as links to reference internal page ids (which is what Wagtail does). This is handled at the Python/Wagtail layer
  • Upon loading a page editor, retrieve the stored HTML in the database, and process it to a format that the rich text editor can process and display for editing
  • Upon loading a front end page, the stored HTML in the database will be retrieved and processed again to be in a format that is correct for template render. For instance, this would involve transforming internal page id references in links to native HTML links. This is generally done by use of the Wagtail richtext filter

Component parts

There are three key parts to this component, all should be taken into account if extending or amending how the component behaves or what its allowed inputs and outputs are.

Wagtail Initialisation

By default, the HTML <table> element and child elements are stripped from content stored in a Wagtail RichTextBlock. To enable these elements to be stored to the database, the Wagtail hook register_rich_text_features is used to register these features and whitelist the allowed elements, and their allowed attributes.

Custom TinyMCERichTextArea

The TinyMCERichTextArea is subclassed to allow configuration and customisation of HTML handling.

We then override the getDefaultArgs method to perform the following configuration:

  • Configure the editor interface to restrict editing options to tables, pre-defined custom styles, bold text, and cell alignment
  • Remove advanced table and cell property controls
  • Inject a custom style sheet into the editor iframe to enable visualisation of custom styles

The __init__ method adds the whitelister rules that were previously defined via the register_rich_text_features hook.

We also override the value_from_datadict method to use a helper method - clean_data_for_db - to perform custom cleaning of the HTML data that is saved to the database.

AdvancedTableBlock

The AdvancedTableBlock defines a standard Wagtail RichTextBlock with the argument editor='timymce'.

This block will then use the custom rich text field editor as defined in the settings for the Wagtail app.

The get_context method on the AdvancedTableBlock passes the database content to another helper method - get_nodes_for_block - the template then loops through this list and outputs each table node.

Supporting utilities

Helpers

The helpers for this block contains functions that do the following:

  • get_style_formats creates a list of styles based on the pre-defined colour choices. These formats are then available to be applied in the editor, and are used in the final rendered HTML
  • get_allowed_attrs returns a duplicated list of allowed attributes on all whitelisted table elements, for use in db and block HTML cleaning
  • clean_data_for_db parses HTML from the rich text editor and:
    • removes any disallowed root nodes (only table elements are allowed)
    • removes any disallowed classes
    • removes any disallowed style properties
  • get_nodes_for_block parses HTML from the database and:
    • remove any disallowed style properties
    • splits tables nodes into a list

SCSS

Supporting CSS for the admin editor and front end templates is defined in an SCSS module using the base class .advanced-table.

Give me the code!

Here's a Wagtail 2.15.x compatible Github gist containing all the required code and implementation notes.